mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-12 10:07:00 +00:00
477 lines
12 KiB
Plaintext
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
|