Here comes our first boss. Nothing too difficult, but different. Let's see what this works out to
The boss moves quite similar to the ghost however he's got a special attack. If nothing else happens the boss is homing in on you. If you shoot him two times he goes into attack mode (and you better step back).
For performance reason the beams are made of characters, a horizontal and vertical line at the boss position.
;------------------------------------------------------------
;boss
;------------------------------------------------------------
!zone BehaviourBoss
BehaviourBoss
BOSS_MOVE_SPEED = 1
lda SPRITE_HITBACK,x
beq .NoHitBack
dec SPRITE_HITBACK,x
ldy SPRITE_HITBACK,x
lda BOSS_FLASH_TABLE,y
sta VIC_SPRITE_COLOR,x
cpy #0bne .NoHitBack
;make vulnerable again
lda SPRITE_STATE,x
cmp #128bne .NoHitBack
lda #0
sta SPRITE_STATE,x
.NoHitBack
lda DELAYED_GENERIC_COUNTER
and #$03bne .NoAnimUpdate
lda SPRITE_POINTER_BASE,x
eor #$01
sta SPRITE_POINTER_BASE,x
.NoAnimUpdate
lda SPRITE_STATE,x
and #$7f
bne .NotFollowPlayer
jmp .FollowPlayer
.NotFollowPlayer
cmp #1beq .AttackMode
rts
.AttackMode
;Attack modes (more modes?)
inc SPRITE_MOVE_POS,x
lda SPRITE_MOVE_POS,x
cmp #4beq .NextAttackStep
rts
.NextAttackStep
lda #0
sta SPRITE_MOVE_POS,x
inc SPRITE_MODE_POS,x
lda SPRITE_MODE_POS,x
cmp #11bcc .BeamNotDangerous
cmp #29bcs .BeamNotDangerous
;does player hit beam?
ldy #0
jsr CheckIsPlayerCollidingWithBeam
ldy #1
jsr CheckIsPlayerCollidingWithBeam
.BeamNotDangerous
lda SPRITE_MODE_POS,x
cmp #11beq .BeamStep1
cmp #12beq .BeamStep2
cmp #13beq .BeamStep3
cmp #16beq .BeamStep4
cmp #17beq .BeamStep3
cmp #18beq .BeamStep4
cmp #19beq .BeamStep3
cmp #20beq .BeamStep4
cmp #21beq .BeamStep3
cmp #22beq .BeamStep4
cmp #23beq .BeamStep3
cmp #24beq .BeamStep4
cmp #25beq .BeamStep3
cmp #26beq .BeamStep4
cmp #27beq .BeamStep3
cmp #28beq .BeamStep4
cmp #29beq .BeamStep3
cmp #30beq .BeamEnd
rts
.BeamStep1;beam
lda #BEAM_TYPE_DARK
jsr .DrawBeam
rts
.BeamStep2;beam
lda #BEAM_TYPE_MEDIUM
jsr .DrawBeam
rts
.BeamStep3;beam
lda #BEAM_TYPE_LIGHT
jsr .DrawBeam
rts
.BeamStep4;beam
lda #BEAM_TYPE_LIGHT2
jsr .DrawBeam
rts
.BeamEnd
jsr .RestoreBeam
lda #0
sta SPRITE_STATE,x
rts
.DrawBeam
tay
lda BEAM_CHAR_H,y
sta PARAM1
lda BEAM_CHAR_V,y
sta PARAM2
lda BEAM_COLOR,y
sta PARAM3
ldy SPRITE_CHAR_POS_Y,x
lda SCREEN_LINE_OFFSET_TABLE_LO,y
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
lda SCREEN_LINE_OFFSET_TABLE_HI,y
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
stx PARAM6
ldy #1
.HLoop
lda PARAM1
sta (ZEROPAGE_POINTER_1),y
lda PARAM3
sta (ZEROPAGE_POINTER_2),y
iny
cpy #39bne .HLoop
;vertical beam
ldy SPRITE_CHAR_POS_X,x
ldx #1
.NextLine
lda SCREEN_LINE_OFFSET_TABLE_LO,x
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
lda SCREEN_LINE_OFFSET_TABLE_HI,x
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
lda PARAM2
sta (ZEROPAGE_POINTER_1),y
lda PARAM3
sta (ZEROPAGE_POINTER_2),y
inx
cpx #22bne .NextLine
ldx PARAM6
rts
.RestoreBeam
ldy SPRITE_CHAR_POS_Y,x
lda SCREEN_LINE_OFFSET_TABLE_LO,y
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
sta ZEROPAGE_POINTER_3
sta ZEROPAGE_POINTER_4
lda SCREEN_LINE_OFFSET_TABLE_HI,y
sta ZEROPAGE_POINTER_1 + 1
sec
sbc #( ( SCREEN_CHAR - SCREEN_BACK_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_3 + 1
sec
sbc #( ( SCREEN_COLOR - SCREEN_BACK_COLOR ) >> 8 )
sta ZEROPAGE_POINTER_4 + 1
stx PARAM6
ldy #1
-
lda (ZEROPAGE_POINTER_2),y
sta (ZEROPAGE_POINTER_1),y
lda (ZEROPAGE_POINTER_4),y
sta (ZEROPAGE_POINTER_3),y
iny
cpy #39bne -
;vertical beam
ldy SPRITE_CHAR_POS_X,x
ldx #1
.NextLineR
lda SCREEN_LINE_OFFSET_TABLE_LO,x
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
sta ZEROPAGE_POINTER_3
sta ZEROPAGE_POINTER_4
lda SCREEN_LINE_OFFSET_TABLE_HI,x
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_BACK_CHAR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_3 + 1
sec
sbc #( ( SCREEN_COLOR - SCREEN_BACK_COLOR ) >> 8 )
sta ZEROPAGE_POINTER_4 + 1
lda (ZEROPAGE_POINTER_2),y
sta (ZEROPAGE_POINTER_1),y
lda (ZEROPAGE_POINTER_4),y
sta (ZEROPAGE_POINTER_3),y
inx
cpx #22bne .NextLineR
ldx PARAM6
rts
.FollowPlayer
inc SPRITE_ANIM_DELAY,x
lda SPRITE_ANIM_DELAY,x
cmp #10beq .DoCheckMove
jmp .DoGhostMove
.DoCheckMove
lda #0
sta SPRITE_ANIM_DELAY,x
txa
and #$01
tay
lda SPRITE_ACTIVE,y
cmp #TYPE_PLAYER_DEANbeq .FoundPlayer
cmp #TYPE_PLAYER_SAMbeq .FoundPlayer
;check other player
tya
eor #1
tay
lda SPRITE_ACTIVE,y
cmp #TYPE_PLAYER_DEANbeq .FoundPlayer
cmp #TYPE_PLAYER_SAMbeq .FoundPlayer
;no player to hunt
rts
.FoundPlayer;player index in y
lda SPRITE_CHAR_POS_X,y
cmp SPRITE_CHAR_POS_X,x
bpl .MoveRight
;move left
lda SPRITE_DIRECTION,x
bne .AlreadyLookingLeft
lda SPRITE_MOVE_POS,x
beq .TurnLNow
dec SPRITE_MOVE_POS,x
bne .CheckYNow
.TurnLNow
;turning now
lda #1
sta SPRITE_DIRECTION,x
lda #SPRITE_BOSS_L_1
sta SPRITE_POINTER_BASE,x
jmp .CheckYNow
.AlreadyLookingLeft
lda SPRITE_MOVE_POS,x
cmp #BOSS_MOVE_SPEEDbeq .CheckYNow
inc SPRITE_MOVE_POS,x
jmp .CheckYNow
.MoveRight
lda SPRITE_DIRECTION,x
beq .AlreadyLookingRight
lda SPRITE_MOVE_POS,x
beq .TurnRNow
dec SPRITE_MOVE_POS,x
bne .CheckYNow
;turning now
.TurnRNow
lda #0
sta SPRITE_DIRECTION,x
lda #SPRITE_BOSS_R_1
sta SPRITE_POINTER_BASE,x
jmp .CheckYNow
.AlreadyLookingRight
lda SPRITE_MOVE_POS,x
cmp #BOSS_MOVE_SPEEDbeq .CheckYNow
inc SPRITE_MOVE_POS,x
jmp .CheckYNow
.CheckYNow;player index in y
lda SPRITE_CHAR_POS_Y,y
cmp SPRITE_CHAR_POS_Y,x
bpl .MoveDown
;move left
lda SPRITE_DIRECTION_Y,x
bne .AlreadyLookingUp
lda SPRITE_MOVE_POS_Y,x
beq .TurnUNow
dec SPRITE_MOVE_POS_Y,x
bne .DoGhostMove
.TurnUNow
;turning now
lda #1
sta SPRITE_DIRECTION_Y,x
jmp .DoGhostMove
.AlreadyLookingUp
lda SPRITE_MOVE_POS_Y,x
cmp #BOSS_MOVE_SPEEDbeq .DoGhostMove
inc SPRITE_MOVE_POS_Y,x
jmp .DoGhostMove
.MoveDown
lda SPRITE_DIRECTION_Y,x
beq .AlreadyLookingDown
lda SPRITE_MOVE_POS_Y,x
beq .TurnDNow
dec SPRITE_MOVE_POS_Y,x
bne .DoGhostMove
;turning now
.TurnDNow
lda #0
sta SPRITE_DIRECTION_Y,x
jmp .DoGhostMove
.AlreadyLookingDown
lda SPRITE_MOVE_POS_Y,x
cmp #BOSS_MOVE_SPEEDbeq .DoGhostMove
inc SPRITE_MOVE_POS_Y,x
jmp .DoGhostMove
.DoGhostMove;move X times
ldy SPRITE_MOVE_POS,x
sty PARAM4
beq .DoY
lda SPRITE_DIRECTION,x
beq .DoRight
.MoveLoopL
jsr ObjectMoveLeftBlocking
dec PARAM4
bne .MoveLoopL
jmp .DoY
.DoRight
.MoveLoopR
jsr ObjectMoveRightBlocking
dec PARAM4
bne .MoveLoopR
.DoY
;move X times
ldy SPRITE_MOVE_POS_Y,x
sty PARAM4
beq .MoveDone
lda SPRITE_DIRECTION_Y,x
beq .DoDown
.MoveLoopU
jsr ObjectMoveUpBlocking
dec PARAM4
bne .MoveLoopU
jmp .MoveDone
.DoDown
.MoveLoopD
jsr ObjectMoveDownBlockingNoPlatform
dec PARAM4
bne .MoveLoopD
.MoveDone
rts
Checking the player for collision with the beam is heavily simplified due to the beam being horizontal/vertical. It's a cheap comparison of position values:
;------------------------------------------------------------
;check player vs. beam
; beam boss index in x
; player index in y
;------------------------------------------------------------
!zone CheckIsPlayerCollidingWithBeam
CheckIsPlayerCollidingWithBeam
lda SPRITE_ACTIVE,y
bne .PlayerIsActive
.PlayerNotActive
rts
.PlayerIsActive
lda SPRITE_STATE,y
cmp #128bcs .PlayerNotActive
;compare char positions in x
lda SPRITE_CHAR_POS_X,x
cmp SPRITE_CHAR_POS_X,y
beq .PlayerHit
clc
adc #1cmp SPRITE_CHAR_POS_X,y
beq .PlayerHit
sec
sbc #2cmp SPRITE_CHAR_POS_X,y
beq .PlayerHit
;compare char positions in y
lda SPRITE_CHAR_POS_Y,x
cmp SPRITE_CHAR_POS_Y,y
beq .PlayerHit
clc
adc #1cmp SPRITE_CHAR_POS_Y,y
beq .PlayerHit
sec
sbc #2cmp SPRITE_CHAR_POS_Y,y
beq .PlayerHit
;not hit
rts
.PlayerHit;player killed
lda #129
sta SPRITE_STATE,y
lda #SPRITE_PLAYER_DEAD
sta SPRITE_POINTER_BASE,y
lda #0
sta SPRITE_MOVE_POS,y
lda SPRITE_ACTIVE,y
cmp #TYPE_PLAYER_SAMbne .PlayerWasDean
;reset Sam specific variables
lda #0
sta SPRITE_HELD
.PlayerWasDean
rts
The boss only enters attack mode for every second hit, therefore he gets a special treatment in his hit function:
;------------------------------------------------------------
;hit behaviour for boss
;------------------------------------------------------------
!zone HitBehaviourBoss
HitBehaviourBoss
lda #8
sta SPRITE_HITBACK,x
;make invincible for a short while
lda SPRITE_STATE,x
ora #$80
sta SPRITE_STATE,x
;boss switches tactic
lda SPRITE_HP,x
and #$01
beq .SwitchToAttack
rts
.SwitchToAttack
lda #129
sta SPRITE_STATE,x
lda #0
sta SPRITE_MODE_POS,x
sta SPRITE_MOVE_POS,x
sta SPRITE_MOVE_POS_Y,x
rts
The less interesting parts are known, add new values for the boss object plus the color/character tables for the beam.
My only experience programming an 8-bit processor is with the Z80. Granted, the surrounding hardware was far from friendly (ugh!) and code was horribly time critical (processing events while also outputting PCM at 10650 Hz in the background without any sort of buffering is far from nice), but it certainly paid off.
I wonder how annoying is to program for the 6502, considering that it's add opcode always adds the carry... (no add without carry, seriously?)