Useful
Inventions
Favorite
Quotes
Game
Design
Atari
Memories
Personal
Pages

Atari 2600 Programming for Newbies

Session 22: Sprites, Horizontal Positioning (Part 2)

Thanks to Matt Jernigan for compiling and contributing this page.

(Adapted by Duane Alan Hahn, a.k.a. Random Terrain.)

As an Amazon Associate I earn from qualifying purchases.

Page Table of Contents

Original Session

For those looking for the missing part 2, here is Matt Jernigan's interpretation of what it was set to cover (more or less).

 

 

 

 

 

Fine Tuning Horizontal Position

This section is taken from the Stella Programmer's Guide by Steve Wright 12/03/79.

 

 

 

 

Horizontal Motion Registers

AddressName76543210Function
20 HMP0 1 1 1 1 . . . . Horizontal Motion Player 0
21 HMP1 1 1 1 1 . . . . Horizontal Motion Player 1
22 HMM0 1 1 1 1 . . . . Horizontal Motion Missile 0
23 HMM1 1 1 1 1 . . . . Horizontal Motion Missile 1
24 HMBL 1 1 1 1 . . . . Horizontal Motion Ball

 

 

 

 

Horizontal Motion

Horizontal motion allows the programmer to move any of the 5 graphics objects relative to their current horizontal position. Each object has a 4 bit horizontal motion register (HMP0, HMP1, HMM0, HMM1, HMBL) that can be loaded with a value in the range of +7 to -8 (negative values are expressed in two's complement from). This motion is not executed until the HMOVE register is written to, at which time all motion registers move their respective objects. Objects can be moved repeatedly by simply executing HMOVE. Any object that is not to move must have a 0 in its motion register. With the horizontal positioning command confined to positioning objects at 15 color clock intervals, the motion registers fills in the gaps by moving objects +7 to -8 color clocks. Objects can not be placed at any color clock position across the screen. All 5 motion registers can be set to zero simultaneously by writing to the horizontal motion clear register (HMCLR).

 

There are timing constraints for the HMOVE command. The HMOVE command must immediately follow a WSYNC (Wait for SYNC) to insure the HMOVE operation occurs during horizontal blanking. This is to allow sufficient time for the motion registers to do their thing before the electron beam starts drawing the next scan line. Also, for mysterious internal hardware considerations, the motion registers should not be modified for at least 24 machine cycles after an HMOVE command.

 

 

 

 

HMP0 (HMP1, HMM0, HMM1, HMBL)

These addresses write data (horizontal motion values) into the horizontal motion registers. These registers will cause horizontal motion only when commanded to do so by the horiz. move command HMOVE. The motion values are coded as shown below:

 

D7 D6 D5 D4 Clocks Effect
0 1 1 1 +7 Move left indicated number of clocks
0 1 1 0 +6
0 1 0 1 +5
0 1 0 0 +4
0 0 1 1 +3
0 0 1 0 +2
0 0 0 1 +1
0 0 0 0 0 No Motion
1 1 1 1 -1 Move right indicated number of clocks
1 1 1 0 -2
1 1 0 1 -3
1 1 0 0 -4
1 0 1 1 -5
1 0 1 0 -6
1 0 0 1 -7
1 0 0 0 -8

 

Warning: These motion registers should not be modified during the 24 computer cycles immediately following an HMOVE command. Unpredictable motion values may result.

 

 

 

 

 

Undocumented Quirk

Not all objects position exactly the same. Double-wide and quad-wide player sprites shift +1 color clock. Missiles and the ball shift -1 color clock.

 

For example, to compensate for this, the quad-wide bridge sprite in River Raid is curiously set to an x-position of 63 (instead of 64) before its fine-positioning subroutine is called. This quirk will shift the bridge +1 color clock and draw it at the intended x-position of 64.

 

This quirk is discussed in more detail here:

 

RESPx, NUSIZx, and Player Positioning

 

 

 

 

 

River Raid Example Code

Below is example code from River Raid with comments from Matt Jernigan and Thomas Jentzsch:

 

       INX                      ; 2             x = 0! (player jet)
...
       LDY    playerX           ; 3             y = player's x-pos
...
       TYA                      ; 2             a = y
       JSR    SetPosX           ; 6             x-position player jet
       INX                      ; 2             x = 1 (enemy object)
...
       INX                      ; 2             x = 2 (player missile)
       LDA    missileX          ; 3             a = missile's x-pos
       JSR    SetPosX           ; 6             x-position missile
       JSR    DoHMove           ; 6             do HMOVE

...

SetPosX SUBROUTINE
; calculates the values and positions objects:
; INPUT:
;   A = x-position
;   X = object to position (0=P0, 1=P1, 2=M0, 3=M1, 4=BL)
       JSR    CalcPosX          ; 6 $FAEF
SetPosX2:
       STA    HMP0,X            ; 4             fine position the object specified in X
       INY                      ; 2             get past the 68 pixels of horizontal blank
       INY                      ; 2
       INY                      ; 2
       STA    WSYNC             ; 3             wait for sync; wait for TIA to finish line
.waitPos:
       DEY                      ; 2
       BPL    .waitPos          ; 2/3           5-cycle loop = 15 TIA color clocks (pixels)
       STA    RESP0,X           ; 4
       RTS                      ; 6

...

CalcPosX SUBROUTINE
; calculates values for fine x-positioning:
;
; Basically, divides pos by 15 and stores the int result of that.
; Then, the mod of that is adjusted to equal 6 + HMP1.
;   HMPx bits 4..7: Offset value:
;     0000 ($00): No offset
;     0001 ($10): Left 1 clock
;     0010 ($20): Left 2 clocks
;     0011 ($30): Left 3 clocks
;     0100 ($40): Left 4 clocks
;     0101 ($50): Left 5 clocks
;     0110 ($60): Left 6 clocks
;     0111 ($70): Left 7 clocks
;     1000 ($80): Right 8 clocks
;     1001 ($90): Right 7 clocks
;     1010 ($A0): Right 6 clocks
;     1011 ($B0): Right 5 clocks
;     1100 ($C0): Right 4 clocks
;     1101 ($D0): Right 3 clocks
;     1110 ($E0): Right 2 clocks
;     1111 ($F0): Right 1 clock
;
; INPUT:
;   A = x-position
; RETURN:
;   Y = coarse value for delay loop (shifted to lower 4 bits), 1 loop = 5 clocks = 15 pixels
;   A = fine value for HMPx, HMMx, or HMBL
    TAY             ; 2 $FDD8 Y = A
    INY             ; 2       Y++ (setup for div 15; causes 15 to overflow)
    TYA             ; 2       A = Y
    AND    #$0F     ; 2       A chopped to lower 4 bits 00001111 (fine pos value + 1)
    STA    temp2    ; 3  $ED  temp2 = A  (fine pos value + 1)
    TYA             ; 2       A = Y
    LSR             ; 2       A chopped to upper 4 bits and shifted to lower 4 bits (A div 16)
    LSR             ; 2
    LSR             ; 2
    LSR             ; 2
    TAY             ; 2   Y = A, backup shifted bits to Y
    CLC             ; 2   CF = 0
    ADC    temp2    ; 3   A=A+temp2 (shifted bits added to change this from div 16 to div 15)
    CMP    #15      ; 2   is A < 15? (look for div 15 overflow)
    BCC    .skipIny ; 2   yes, skip to EOR  (no div 15 overflow)
    SBC    #15      ; 2   no, A = A - 15  (CF = 1 but not needed here)
    INY             ; 2   Y++ (yes on the div 15 overflow so one-up Y)
.skipIny:
    EOR    #$07     ; 2   A = A XOR 00000111, 7's complement, sets offset for 6 + HMPx
; NOTE: Any JSR here will use 20 cycles instead of 8
; but saves 1 byte of code overall (3 bytes instead of 4).
Mult16:
    ASL             ; 2   A's lower 4 bits shifted to upper 4
    ASL             ; 2
    ASL             ; 2
    ASL             ; 2
Wait12:
    RTS             ; 6

...

DoHMove:
    STA    WSYNC    ; 3   wait for sync; wait for TIA to finish line
    STA    HMOVE    ; 3   must follow WSYNC if horizontal motion desired
    RTS             ; 6

 

 

 

 

Summary

Hopefully, after this, Session 24 will make more sense, which discusses other algorithms for horizontal positioning.

 

 

 

Other Assembly Language Tutorials

Be sure to check out the other assembly language tutorials and the general programming pages on this web site.

 

Amazon Stuff

 

< Previous Session

 

 

Next Session >

 

 

 

 

Session Links

Session 1: Start Here

Session 2: Television Display Basics

Sessions 3 & 6: The TIA and the 6502

Session 4: The TIA

Session 5: Memory Architecture

Session 7: The TV and our Kernel

Session 8: Our First Kernel

Session 9: 6502 and DASM - Assembling the Basics

Session 10: Orgasm

Session 11: Colorful Colors

Session 12: Initialization

Session 13: Playfield Basics

Session 14: Playfield Weirdness

Session 15: Playfield Continued

Session 16: Letting the Assembler do the Work

Sessions 17 & 18: Asymmetrical Playfields (Parts 1 & 2)

Session 19: Addressing Modes

Session 20: Asymmetrical Playfields (Part 3)

Session 21: Sprites

Session 22: Sprites, Horizontal Positioning (Part 1)

Session 22: Sprites, Horizontal Positioning (Part 2)

Session 23: Moving Sprites Vertically

Session 24: Some Nice Code

Session 25: Advanced Timeslicing

 

 

 

 

Back to Top

 

Disclaimer

View this page and any external web sites at your own risk. I am not responsible for any possible spiritual, emotional, physical, financial or any other damage to you, your friends, family, ancestors, or descendants in the past, present, or future, living or dead, in this dimension or any other.

 

Use any example programs at your own risk. I am not responsible if they blow up your computer or melt your Atari 2600. Use assembly language at your own risk. I am not responsible if assembly language makes you cry or gives you brain damage.

 

Home Inventions Quotations Game Design Atari Memories Personal Pages About Site Map Contact Privacy Policy Tip Jar