2018-08-13 22:17:36 +00:00
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
processor 6502
|
|
|
|
include "vcs.h"
|
|
|
|
include "macro.h"
|
|
|
|
include "xmacro.h"
|
|
|
|
|
|
|
|
seg.u Variables
|
|
|
|
org $80
|
|
|
|
|
2018-10-01 01:07:45 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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).
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; Note: You can press the button to capture the ball.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2018-08-26 13:19:09 +00:00
|
|
|
; 29 lines of overscan needed, but we have lots of logic to do.
|
2016-12-16 01:21:51 +00:00
|
|
|
; So we're going to use the PIA timer to let us know when
|
2018-08-26 13:19:09 +00:00
|
|
|
; almost 29 lines of overscan have passed.
|
2016-12-16 01:21:51 +00:00
|
|
|
; This handy macro does a WSYNC and then sets up the timer.
|
2018-08-26 13:19:09 +00:00
|
|
|
TIMER_SETUP 29
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
; 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
|
2019-03-26 00:39:53 +00:00
|
|
|
;;{w:8,h:16,brev:1,flip:1};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Bitmap data "throwing" position
|
|
|
|
;;{w:8,h:16,brev:1,flip:1};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Color data for each line of sprite
|
|
|
|
;;{pal:"vcs"};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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;
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Playfield top border bitmap
|
|
|
|
;;{w:8,h:8,count:3,xtra:"vcspf"};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
2019-03-26 00:39:53 +00:00
|
|
|
;;
|
2016-12-16 01:21:51 +00:00
|
|
|
; Epilogue
|
|
|
|
org $fffc
|
|
|
|
.word Start
|
|
|
|
.word Start
|