step 11 - add the ball object
The 2LK has been revised to display the ball object. Like player0, the ball needs to be primed before the ArenaLoop begins:
; prime ENABL so ball can appear on topmost scanline of Arena
ldx #1 ; 2 2 - D1=0, so ball will be off
lda #BOX_HEIGHT-1 ; 2 4 - height of box graphic
dcp BallDraw ; 5 9 - Decrement BallDraw and compare with height
bcs DoEnablPre ; 2 11 - (3 12) if Carry is Set, then ball is on current scanline
.byte $24 ; 3 14 - $24 = BIT with zero page addressing, trick that
; causes the inx to be skipped
DoEnablPre: ; 12 - from bcs DoEnablPre
inx ; 2 14 - D1=1, so ball will be ON
stx ENABL ; 3 17
; prime GRP0 so player0 can appear on topmost scanline of the Arena
lda #HUMAN_HEIGHT-1 ; 2 19 - height of player0 graphics,
dcp Player0Draw ; 5 24 - Decrement Player0Draw and compare with height
bcs DoDrawGrp0pre ; 2 26 - (3 27) if Carry is Set, then player0 is on current scanline
lda #0 ; 2 28 - otherwise use 0 to turn off player0
.byte $2C ; 4 32 - $2C = BIT with absolute addressing, trick that
; causes the lda (Player0Ptr),y to be skipped
DoDrawGrp0pre: ; 27 - from bcs DoDrawGRP0pre
lda (Player0Ptr),y ; 5 32 - load the shape for player0
sta GRP0 ; 3 35 - @0-22, update player0 graphics
dey ; 2 37
ArenaLoop: ; 37 - (currently 11 from bpl ArenaLoop)
tya ; 2 39 - 2LK loop counter in A for testing
and #%11 ; 2 41 - test for every 4th time through the loop,
bne SkipX ; 2 43 - (3 44) branch if not 4th time
inc ArenaIndex ; 5 48 - if 4th time, increase index so new playfield data is used
SkipX: ; 48 - use 48 as it's the longest path here
; continuation of line 2 of the 2LK
; this precalculates data that's used on line 1 of the 2LK
lda #HUMAN_HEIGHT-1 ; 2 50 - height of the humanoid graphics, subtract 1 due to starting with 0
dcp Player1Draw ; 5 55 - Decrement Player1Draw and compare with height
bcs DoDrawGrp1 ; 2 57 - (3 58) if Carry is Set, then player1 is on current scanline
lda #0 ; 2 59 - otherwise use 0 to turn off player1
.byte $2C ; 4 63 - $2C = BIT with absolute addressing, trick that
; causes the lda (Player1Ptr),y to be skipped
DoDrawGrp1: ; 58 - from bcs DoDrawGrp1
lda (Player1Ptr),y ; 5 63 - load the shape for player1
sta WSYNC ; 3 66
;---------------------------------------
; start of line 1 of the 2LK
sta GRP1 ; 3 3 - @0-22, update player1 graphics
ldx ArenaIndex ; 3 6
lda ArenaPF0,x ; 4 10 - get current scanline's playfield pattern
sta PF0 ; 3 13 - @0-22 and update it
lda ArenaPF1,x ; 4 17 - get current scanline's playfield pattern
sta PF1 ; 3 20 - @71-28 and update it
lda ArenaPF2,x ; 4 24 - get current scanline's playfield pattern
sta PF2 ; 3 27 - @60-39
; precalculate data that's needed for line 2 of the 2LK
ldx #1 ; 2 29 - D1=0, so ball will be off
lda #BOX_HEIGHT-1 ; 2 31 - height of box graphic
dcp BallDraw ; 5 36 - Decrement BallDraw and compare with height
bcs DoEnabl ; 2 38 - (3 39) if Carry is Set, then ball is on current scanline
.byte $24 ; 3 41 - $24 = BIT with zero page addressing, trick that
; causes the inx to be skipped
DoEnabl: ; 39 - from bcs DoEnablPre
inx ; 2 41 - D1=1, so ball will be ON
lda #HUMAN_HEIGHT-1 ; 2 43 - height of the box graphics,
dcp Player0Draw ; 5 48 - Decrement Player0Draw and compare with height
bcs DoDrawGrp0 ; 2 50 - (3 51) if Carry is Set then player0 is on current scanline
lda #0 ; 2 52 - otherwise use 0 to turn off player0
.byte $2C ; 4 56 - $2C = BIT with absolute addressing, trick that
; causes the lda (Player0Ptr),y to be skipped
DoDrawGrp0: ; 51 - from bcs DoDrawGRP0
lda (Player0Ptr),y ; 5 56 - load the shape for player0
sta WSYNC ; 3 59
;---------------------------------------
; start of line 2 of the 2LK
sta GRP0 ; 3 3 - @0-22, update player0 graphics
stx ENABL ; 3 6 - @0-22, update ball graphics
dey ; 2 8 - decrease the 2LK loop counter
bne ArenaLoop ; 2 10 - (3 11) branch if there's more Arena to draw
sty PF0 ; 3 13 - Y is 0, blank out playfield
sty PF1 ; 3 16 - Y is 0, blank out playfield
sty PF2 ; 3 19 - Y is 0, blank out playfield
rts ; 6 25 - ReTurn from Subroutine
I then modified RandomLocation to set all objects to the same location for comparision:
RandomLocation:
...
; for alignment test, set to (100, 100)
lda #100
sta ObjectX,x
sta ObjectY,x
rts
This revealed a minor quirk with TIA - namely that when objects are set to the same X position, missiles and the ball end up 1 pixel to the left of where a player ends up (player1 is the green square and it's directly on top of the red ball).
This is a known issue and the solution is to increase the X value by 1 to compensate. I did so by adding this to the end of RandomLocation:
RandomLocation:
...
cpx #2
bcc RLdone
inc ObjectX,x ; missile and ball objects need their X adjusted
RLdone:
rts
And now player1 and the ball line up:
The NewGame routine revised last time already sets the ball to a random X-Y location, so all that's left to make it show up is to revise PositionObjects. Two changes are needed, first is to set the X position of the ball object by starting the POloop with X=4 (in the prior build X was initialized to 1), then prep a new variable BallDraw that's used by the 2LK to draw the ball object on the correct scanlines.
PositionObjects:
ldx #4 ; position all objects
POloop
lda ObjectX,x ; get the object's X position
jsr PosObject ; set coarse X position and fine-tune amount
dex ; DEcrement X
bpl POloop ; Branch PLus so we position all objects
sta WSYNC ; wait for end of scanline
sta HMOVE ; use fine-tune values to set final X positions
...
; prep ball's Y position for 2LK
ldx #1 ; preload X for setting VDELBL
lda ObjectY+4 ; get the balls's Y position
clc
adc #1 ; add 1 to compensate for priming of ball
lsr ; divide by 2 for the 2LK position
sta Temp ; save for position calculations
bcs NoDelayBL ; if carry is set we don't need Vertical Delay
stx VDELBL ; carry was clear, so set Vertical Delay
NoDelayBL:
; BallDraw = ARENA_HEIGHT + BOX_HEIGHT - Y position + 1
; the + 1 compensates for priming of ENABL
lda #(ARENA_HEIGHT + BOX_HEIGHT + 1)
sec
sbc Temp
sta BallDraw
rts
Lastly, Overscan's been updated to process collisions with the ball.
OverScan:
...
TestCollisions:
; Test left player collisions
...
bit CXP0FB ; N = player0/playfield, V=player0/ball
...
notP0PF: ; oVerflow flag is not affected by lda or sta
bvc notP0BL ; if V is off, then player0 did not collide with ball
ldx #4 ; which box was collected
jsr CollectBox ; update score and reposition box
notP0BL:
...
RightPlayer:
; Test right player collisions
...
bit CXP1FB ; N = player1/playfield, V=player1/ball
...
notP1PF: ; oVerflow flag is not affected by lda or sta
bvc notP1BL ; if V is off, then player1 did not collide with ball
ldx #4 ; which box was collected
jsr CollectBox ; update score and reposition box
notP1BL:
2 player games are now playable:
and 1 player games have 2 boxes to collect:
ROM
Source

3 Comments
Recommended Comments