8bitworkshop/presets/examples/collisions.a

477 lines
12 KiB
Plaintext

processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
; In this example, we're going to tackle collision detection,
; which is one thing in the VCS that's easier than expected.
; The TIA has 15 different collision flags that can detect a
; collision between any of the 2 players, 2 missiles, ball,
; and playfield. You can check these flags at any time (at the
; end of the frame is pretty common). When you're done checking
; you clear them all at once by writing to CXCLR.
; For this example we'll use the ball object, and detect collisions
; between it and the playfield and the player. We only know
; the Y position of the ball and player (the X position is in
; the TIA chip) so we'll base our bounce decisions on the Y position
; of the ball (for playfield bounces) or the relative Y position of
; ball and player (for player bounces).
; Note: You can press the button to capture the ball.
; We're going to also include sound, which is generated by writing
; a volume register, a frequency register, and a mode register for
; one of two channels.
counter byte ; increments each frame
yplyr byte ; player y pos
yball byte ; ball y pos
animofs byte ; sprite data offset, used for animation
ysprofs byte ; temp sprite offset
yballvel byte ; ball Y velocity
xballvel byte ; ball X velocity
xballerr byte ; ball X fractional error
captured byte ; ball capture flag
avol0 byte ; shadow register for AVOL0
; Color constants
BGCOLOR equ $80
PLCOLOR equ $6c
GNDCOLOR equ $c0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Enable ball if it is on this scanline (in X register)
; Modifies A.
MAC DRAW_BALL
lda #%00000000
cpx yball
bne .noball
lda #%00000010 ; for ENAM0 the 2nd bit is enable
.noball
sta ENABL ; enable ball
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
; Initialize and set initial offsets of objects.
Start CLEAN_START
lda #185-SpriteHeight
sta yplyr ; player Y position, top to bottom
; Set player 0 horizontal position
lda #80
ldx #0
jsr SetHorizPos2
; Set ball horizontal position
lda #84
ldx #4
jsr SetHorizPos2
sta WSYNC
sta HMOVE
; Set ball initial velocity
lda #1
sta yballvel
lda #$30
sta xballvel
; Next frame loop
NextFrame
VERTICAL_SYNC
; in case the ball is on screen
lda ColorFrame0 ; load 1st entry of color data
sta COLUP0 ; set sprite 0 color
; Set up playfield
lda #BGCOLOR ; set the background color
sta COLUBK
lda #PLCOLOR ; set the playfield color
sta COLUPF
lda #%00010101 ; playfield reflection and ball size/priority
sta CTRLPF
lda #0 ; blank out the playfield
sta PF0
sta PF1
sta PF2
; 37 lines of VBLANK
ldx #37
Underscan
sta WSYNC
dex
bne Underscan
; Draw 192 scanlines
; First, 20 lines at top of screen
ldx #0
ScanLoop1
sta WSYNC
stx PF1
inx
cpx #20
bne ScanLoop1
; Top border of screen (8 lines)
ScanLoop2
; Fetch 1st part of bitmap data from table (we start on 20th line)
lda #TopBorder0-20,x
sta WSYNC
sta PF0
DRAW_BALL ; draw the ball on this line?
; (only for collision purposes)
; Fetch 2nd and 3rd parts of bitmap
lda #TopBorder1-20,x
sta PF1
lda #TopBorder2-20,x
sta PF2
inx
cpx #28
bne ScanLoop2
; Top half of screen (100 pixels)
lda #0
sta PF0
sta PF1
sta PF2
ScanLoop3
sta WSYNC
DRAW_BALL ; draw the ball on this line?
inx
cpx #128
bne ScanLoop3
; Bottom half of screen with sprite
; Setup 'ysprofs' which is the calculated offset into
; sprite lookup tables (it can exceed bounds, we'll test)
; Since the sprite table is reversed, the starting offset is
; Yplyr - Ystart - SpriteHeight
lda yplyr
sec
sbc #128-SpriteHeight
sta ysprofs
ScanLoop4
; Is this scanline within sprite bounds?
dec ysprofs
lda ysprofs
cmp #SpriteHeight ; sprite is 16 pixels high + padding
bcc InSprite
lda #0 ; no sprite, draw the padding
InSprite
tay
lda ColorFrame0,y ; load color data
pha ; push color data onto stack
tya
clc ; clear carry flag
adc animofs ; add animation offset (not for color though)
tay
lda Frame0,y ; load bitmap data
sta WSYNC ; wait for next scanline (as late as possible!)
sta GRP0 ; set sprite 0 pixels
pla ; pull bitmap data from stack
sta COLUP0 ; set sprite 0 color
DRAW_BALL ; draw the ball on this line?
inx
cpx #184
bne ScanLoop4 ; repeat next scanline until finished
; 8 more pixels for bottom border
lda #GNDCOLOR ; set the background color
sta COLUPF
ScanLoop5
lda #$ff
sta WSYNC
sta PF0
sta PF1
sta PF2
lda #0
sta GRP0
inx
cpx #192
bne ScanLoop5
; Disable ball
lda #0
sta ENABL
; 30 lines of overscan needed, but we have lots of logic to do.
; So we're going to use the PIA timer to let us know when
; almost 30 lines of overscan have passed.
; This handy macro does a WSYNC and then sets up the timer.
TIMER_SETUP 30
; Check for collisions
lda #%01000000
bit CXP0FB ; collision between player 0 and ball?
bne PlayerCollision
lda #%10000000
bit CXBLPF ; collision between playfield and ball?
bne PlayfieldCollision
beq NoCollision
; Now we bounce the ball depending on where it is
PlayerCollision
; Is the button pressed? if so, just capture the ball
lda INPT4 ;read button input
bmi ButtonNotPressed ;skip if button not pressed
inc captured ; set capture flag
lda #SpriteHeight
sta animofs ; use different bitmap when captured
bne NoCollision
ButtonNotPressed
lda #0
sta captured ; clear capture flag
sta animofs ; use regular bitmap
; See if we bounce off of top half or bottom half of player
; (yplyr + height/2 - yball)
ldx #1
lda yplyr
clc
adc #SpriteHeight/2
sec
sbc yball
bmi StoreVel ; bottom half, bounce down
ldx #$ff ; top half, bounce up
bne StoreVel
PlayfieldCollision
; If bouncing off top of playfield, bounce down
ldx #1
lda yball
bpl StoreVel
; Otherwise bounce up
ldx #$ff
StoreVel
; Store final velocity
stx yballvel
; Make a little sound
txa
adc #45
sta AUDF0 ; frequency
lda #6
sta avol0 ; shadow register for volume
NoCollision
; Clear collision registers for next frame
sta CXCLR
; Ball captured? if so, no motion
lda captured
bne ballCaptured
; Move ball vertically
lda yball
clc
adc yballvel
sta yball
; Move ball horizontally
; We use an fractional counter for the ball, and we have to
; set a different HMOVE value depending on if it's left or right
lda xballvel
bmi ballMoveLeft
clc
adc xballerr
sta xballerr
bcc ballCaptured
lda #$f0
sta HMBL
bne ballCaptured
ballMoveLeft
sec
sbc xballerr
sta xballerr
bcs ballCaptured
lda #$10
sta HMBL
ballCaptured
sta WSYNC
sta HMOVE ; apply the move(s)
sta HMCLR
; Joystick movement
; For up and down, we INC or DEC the Y Position
lda #%00010000 ;Up?
bit SWCHA
bne SkipMoveUp
ldx yplyr
cpx #129
bcc SkipMoveUp
dex
stx yplyr
lda captured ; captured? move the ball too
beq SkipMoveUp
dec yball
SkipMoveUp
lda #%00100000 ;Down?
bit SWCHA
bne SkipMoveDown
ldx yplyr
cpx #185-SpriteHeight
bcs SkipMoveDown
inx
stx yplyr
lda captured ; captured? move the ball too
beq SkipMoveDown
inc yball
SkipMoveDown
; Note that the horizontal position is not contained in RAM,
; but inaccessibly inside the TIA's registers! Some games can
; get away with this if they use the collision registers.
ldx #0 ; assume speed is 0 if no movement
lda #%01000000 ;Left?
bit SWCHA
bne SkipMoveLeft
ldx #$10 ;a 1 in the left nibble means go left
SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne SkipMoveRight
ldx #$F0 ;a -1 in the left nibble means go right...
SkipMoveRight
stx HMP0 ; set the move for player 0
lda captured ; captured? move the ball too
beq NoCaptureMove
stx HMBL ; set ball move register
NoCaptureMove
sta WSYNC
sta HMOVE ; apply the move(s)
; Play audio from shadow register
ldx avol0
beq NoAudio
dex ; decrement volume every frame
stx AUDV0 ; store in volume hardware register
stx avol0 ; store in shadow register
lda #3
sta AUDC0 ; shift counter mode 3 for weird bounce sound
NoAudio
; Wait until our timer expires and then WSYNC, so then we'll have
; passed 30 scanlines. This handy macro does this.
TIMER_WAIT
; Goto next frame
jmp NextFrame
; SetHorizPos2 - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC both before and after, followed by
; a HMOVE and HMCLR. So it takes two scanlines to complete.
SetHorizPos2
sta WSYNC ; start a new line
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
sta WSYNC
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
rts ; return to caller
; Height of our sprite in lines
SpriteHeight equ 17
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF ; ball color if not sharing line with player sprite
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Playfield top border bitmap
TopBorder0
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11110000
.byte #%11000000
.byte #%10000000
.byte #%00000000
TopBorder1
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11111000
.byte #%11100000
.byte #%11000000
.byte #%10000000
TopBorder2
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000000
; Epilogue
org $fffc
.word Start
.word Start