Advertisement Jump to content
  • Advertisement
  • entries
    104
  • comments
    102
  • views
    260398

About this blog

Musings of a hobbyist

Entries in this blog

 

A C64 game - Step 5

Our next big step:

Obviously we want some play screen, and more obviously, we are not going to store full screens (we've got only 64Kb Ram after all). Therefore there's a level build routine that allows to build a screen with various building elements. For now we'll start out with vertical and horizontal lines. Since we always have a level border at the screen edges we'll have the border as a second screen data block (LEVEL_BORDER_DATA).
To allow faster screen building we also have a table with the precalculated char offsets of a vertical line (SCREEN_LINE_OFFSET_TABLE_LO and SCREEN_LINE_OFFSET_TABLE_HI). Note that with the C64s mnemonics the best way to get stuff done is tables, tables, tables.
The BuildScreen sub routine clears the play screen area, builds the level data and then the border. The level data is a collection of primitives which are then worked through until LD_END is hit.

The first addition is the call to the buildroutine:
  ;setup level lda #0 sta LEVEL_NR jsr BuildScreen   The BuildScreen routine sets up a level completely. It starts out with clearing the current play area (not the full screen). Then LEVEL_NR is used to look up the location of the level data in table SCREEN_DATA_TABLE.
.BuildLevel is jumped to to actually work through the level primitives and put them on screen. Then we use LEVEL_BORDER_DATA as second level to display a border on the screens edges. .BuildLevel uses Y as index through the data. Depending on the first byte different routines are called (.LineH, .LineV, .LevelComplete). Since I think levels may be complex and use more than 256 bytes we add Y to the level data pointers so we can start out with Y=0 for the next primitive.
  ;------------------------------------------------------------ ;BuildScreen ;creates a screen from level data ;------------------------------------------------------------ !zone BuildScreen BuildScreen lda #0 ldy #6 jsr ClearPlayScreen ;get pointer to real level data from table ldx LEVEL_NR lda SCREEN_DATA_TABLE,x sta ZEROPAGE_POINTER_1 lda SCREEN_DATA_TABLE + 1,x sta ZEROPAGE_POINTER_1 + 1 jsr .BuildLevel ;get pointer to real level data from table lda #<LEVEL_BORDER_DATA sta ZEROPAGE_POINTER_1 lda #>LEVEL_BORDER_DATA sta ZEROPAGE_POINTER_1 + 1 jsr .BuildLevel rts .BuildLevel ;work through data ldy #255 .LevelDataLoop iny lda (ZEROPAGE_POINTER_1),y cmp #LD_END beq .LevelComplete cmp #LD_LINE_H beq .LineH cmp #LD_LINE_V beq .LineV .LevelComplete rts .NextLevelData pla ;adjust pointers so we are able to access more ;than 256 bytes of level data clc adc #1 adc ZEROPAGE_POINTER_1 sta ZEROPAGE_POINTER_1 lda ZEROPAGE_POINTER_1 + 1 adc #0 sta ZEROPAGE_POINTER_1 + 1 ldy #255 jmp .LevelDataLoop   The primitive display routines .LineH and .LineV are rather straight forward. Read the parameters, and put the character and color values in place.
  .LineH ;X pos iny lda (ZEROPAGE_POINTER_1),y sta PARAM1 ;Y pos iny lda (ZEROPAGE_POINTER_1),y sta PARAM2 ;width iny lda (ZEROPAGE_POINTER_1),y sta PARAM3 ;char iny lda (ZEROPAGE_POINTER_1),y sta PARAM4 ;color iny lda (ZEROPAGE_POINTER_1),y sta PARAM5 ;store target pointers to screen and color ram ldx PARAM2 lda SCREEN_LINE_OFFSET_TABLE_LO,x sta ZEROPAGE_POINTER_2 sta ZEROPAGE_POINTER_3 lda SCREEN_LINE_OFFSET_TABLE_HI,x sta ZEROPAGE_POINTER_2 + 1 clc adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8 sta ZEROPAGE_POINTER_3 + 1 tya pha ldy PARAM1 .NextChar lda PARAM4 sta (ZEROPAGE_POINTER_2),y lda PARAM5 sta (ZEROPAGE_POINTER_3),y iny dec PARAM3 bne .NextChar jmp .NextLevelData .LineV ;X pos iny lda (ZEROPAGE_POINTER_1),y sta PARAM1 ;Y pos iny lda (ZEROPAGE_POINTER_1),y sta PARAM2 ;height iny lda (ZEROPAGE_POINTER_1),y sta PARAM3 ;char iny lda (ZEROPAGE_POINTER_1),y sta PARAM4 ;color iny lda (ZEROPAGE_POINTER_1),y sta PARAM5 ;store target pointers to screen and color ram ldx PARAM2 lda SCREEN_LINE_OFFSET_TABLE_LO,x sta ZEROPAGE_POINTER_2 sta ZEROPAGE_POINTER_3 lda SCREEN_LINE_OFFSET_TABLE_HI,x sta ZEROPAGE_POINTER_2 + 1 clc adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8 sta ZEROPAGE_POINTER_3 + 1 tya pha ldy PARAM1 .NextCharV lda PARAM4 sta (ZEROPAGE_POINTER_2),y lda PARAM5 sta (ZEROPAGE_POINTER_3),y ;adjust pointer lda ZEROPAGE_POINTER_2 clc adc #40 sta ZEROPAGE_POINTER_2 sta ZEROPAGE_POINTER_3 lda ZEROPAGE_POINTER_2 + 1 adc #0 sta ZEROPAGE_POINTER_2 + 1 clc adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8 sta ZEROPAGE_POINTER_3 + 1 dec PARAM3 bne .NextCharV jmp .NextLevelData
step5.zip

Previous Step 4 Next Step 6

Endurion

Endurion

 

A C64 Game - Final Step

And thus this create a game series ends...

We fix the last few bugs (score was not properly reset on replay, music was stuck on saving the scores). Now the game is complete and can be enjoyed as it was meant to be!



Oh joy, Smila (the graphician) actually touched up the game even more for a retail release. It has been out for a few weeks now, but if you didn't know, go here:
Psytronik (for digital download, tape or disk) or to RGCD (cartridge with nifty packaging and stickers).
Or simply marvel in all the nice games that are still being made for older computers.

There you can actually buy a severely enhanced version (more two player game modes, better graphic, gameplay enhancements) as either digital download for emulators, or as a real tape, disk and finally as cartridge!

Thank you for your encouragements throughout, and keep on coding

step100.zip

Previous Step  

Endurion

Endurion

 

A C64 Game - Step 99

And of course lots of little bugs were found and fixed

-Live number display was off on a new level.
-Beams would sometimes not be removed on the final boss
-Disable screen output when saving scores (IRQs go nuts if using kernal save routine)
-Cleaned up "extro" text


Have fun!

step99.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 98

And here's the final bit, the extro part. Never have the player play through your whole game and put "Game Over" there. At the minimum a nice message is required

And yes, it's symbolic at 98, since there's always some bugs left to fix. The last two steps will be mostly bug fixes.

Since it's the extro (and not even particularely impressing) I'll refrain to show the screenshot (and the code update)

Just for explanation, to hide the sprite behind some chars there's a priority bit. This is used to hide the main object when it goes behind the hills.

step98.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 97

And here's a little gameplay update, the bats. The diagonal movement was too predictable, so now there's more randomness to it.



The bat will move in curves. On every end of a curve the new direction will be decided randomly. Two tables are enough, however due to the C64 using two bit complement negative values are annoying to handle. Therefor I went the naive route and simply added code for every case. Ugly, but it works ;------------------------------------------------------------ ;simply move diagonal ;------------------------------------------------------------ !zone BehaviourBatDiagonal BehaviourBatDiagonal jsr HandleHitBack beq .NoHitBack rts .RandomDir jsr GenerateRandomNumber and #$07 sta SPRITE_DIRECTION,x inc SPRITE_DIRECTION,x lda #0 sta SPRITE_MOVE_POS,x rts .NoHitBack lda DELAYED_GENERIC_COUNTER and #$03 bne .NoAnimUpdate inc SPRITE_ANIM_POS,x lda SPRITE_ANIM_POS,x and #$03 sta SPRITE_ANIM_POS,x tay lda BAT_ANIMATION,y sta SPRITE_POINTER_BASE,x .NoAnimUpdate lda SPRITE_DIRECTION,x beq .RandomDir cmp #1 beq .MoveCCWWN cmp #2 beq .MoveCCWSW cmp #3 beq .MoveCCWES cmp #4 beq .MoveCCWNE cmp #5 bne + jmp .MoveCWWS + cmp #6 bne + jmp .MoveCWNW + cmp #7 bne + jmp .MoveCWEN + cmp #8 bne + jmp .MoveCWSE + .NextStep inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x cmp #16 bne + lda #0 sta SPRITE_DIRECTION,x sta SPRITE_MOVE_POS,x + rts .MoveCCWWN ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveUp ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveRight jmp .NextStep .MoveCCWSW ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveLeft ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveUp jmp .NextStep .MoveCCWES ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveDown ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveLeft jmp .NextStep .MoveCCWNE ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveRight ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveDown jmp .NextStep .MoveCWWS ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveDown ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveRight jmp .NextStep .MoveCWNW ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveLeft ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveDown jmp .NextStep .MoveCWEN ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveUp ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveLeft jmp .NextStep .MoveCWSE ldy SPRITE_MOVE_POS,x lda PATH_CURVE,y sta PARAM3 jsr .TryMoveRight ldy SPRITE_MOVE_POS,x lda PATH_CURVE_R,y sta PARAM3 jsr .TryMoveUp jmp .NextStep .Blocked lda #0 sta SPRITE_DIRECTION,x rts .TryMoveUp beq + jsr ObjectMoveUpBlocking beq .Blocked dec PARAM3 jmp .TryMoveUp + rts .TryMoveDown beq + jsr ObjectMoveDownBlocking beq .Blocked dec PARAM3 jmp .TryMoveDown + rts .TryMoveLeft beq + jsr ObjectMoveLeftBlocking beq .Blocked dec PARAM3 jmp .TryMoveLeft + rts .TryMoveRight beq + jsr ObjectMoveRightBlocking beq .Blocked dec PARAM3 jmp .TryMoveRight + rts

The table are simple delta updates per frame, one being the reverse of the other: PATH_CURVE !byte 0 !byte 0 !byte 1 !byte 0 !byte 0 !byte 1 !byte 0 !byte 1 !byte 0 !byte 1 !byte 1 !byte 1 !byte 1 !byte 1 !byte 1 !byte 1 PATH_CURVE_R !byte 1 !byte 1 !byte 1 !byte 1 !byte 1 !byte 1 !byte 1 !byte 0 !byte 1 !byte 0 !byte 1 !byte 0 !byte 0 !byte 1 !byte 0 !byte 0
The other thing is a little fix for a bug I found with the vanishing bats for the last portal stages. The bats could appear outside the playing area. The fix are two border values for left and right which will be set to be farther inside the screen if a portal level is run: ;set default lda #10 sta SPAWN_LEFT_BORDER lda #30 sta SPAWN_RIGHT_BORDER ;adjust spawn border on portal level lda LEVEL_CONFIG and #$04 beq + lda #15 sta SPAWN_LEFT_BORDER lda #25 sta SPAWN_RIGHT_BORDER +
...and adjust the spawn code inside BehaviourBatVanishing: ;position diagonal above/below player lda SPRITE_CHAR_POS_X cmp #SPAWN_LEFT_BORDER ;was #10 bcc .SpawnOnRight cmp #SPAWN_RIGHT_BORDER ;was #30 bcs .SpawnOnLeft
step97.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 96

And a nice little update, Richard Bayliss added sounds effects. Now there's a SFX mode, toggle in the title screen with left/right.



The effects are integrated in the player code as separate "songs". So we add a variable SFX_MODE and check it's value when we want to play an effect or start the music:

No music in title when SFX mode enabled: ;initialise music player ldx #0 ldy #0 lda SFX_MODE bne + lda #MUSIC_TITLE_TUNE jsr MUSIC_PLAYER
Play an effect is similar: lda SFX_MODE beq + lda #MUSIC_PICKUP jsr MUSIC_PLAYER +   To toggle sfx mode move the joystick left/right in the title screen and display its state: ;switch through music/sfx mode lda #$04 bit JOYSTICK_PORT_II bne .NotLeftPressed lda LEFT_RELEASED beq .LeftPressed lda SFX_MODE eor #$01 sta SFX_MODE jsr DisplaySfxMode lda SFX_MODE beq + lda #MUSIC_PICKUP jmp ++ + lda #MUSIC_TITLE_TUNE ++ jsr MUSIC_PLAYER lda #0 jmp .LeftPressed .NotLeftPressed lda #1 .LeftPressed sta LEFT_RELEASED lda #$08 bit JOYSTICK_PORT_II bne .NotRightPressed lda RIGHT_RELEASED beq .RightPressed lda SFX_MODE eor #$01 sta SFX_MODE jsr DisplaySfxMode lda SFX_MODE beq + lda #MUSIC_PICKUP jmp ++ + lda #MUSIC_TITLE_TUNE ++ jsr MUSIC_PLAYER lda #0 jmp .RightPressed .NotRightPressed lda #1 .RightPressed sta RIGHT_RELEASED !zone DisplaySfxMode DisplaySfxMode lda SFX_MODE bne + lda #TEXT_MUSIC jmp .DisplaySfxMode + lda #TEXT_SFX .DisplaySfxMode sta ZEROPAGE_POINTER_1 + 1 lda #34 sta PARAM1 lda #24 sta PARAM2 jmp DisplayText Due to technical limitations in the player code there are not too many sounds, but there are enough to make it worthwile. step96.zip Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 95

Now a little update that adds a change that was long overdue: Zombies do not wake up all of a sudden, but peek out of the ground before. Now players should be able to escape if they're keeping an eye out.




We change the .WakeUp part of BehaviourZombie to show up, look left/right a few times and only then rise, like good zombies do: ;only animate head to warn player inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x cmp #20 beq .ReallyWakeUp and #$07 bne ++ ;show head lda SPRITE_DIRECTION,x eor #1 sta SPRITE_DIRECTION,x lda #SPRITE_ZOMBIE_COLLAPSE_R_2 clc adc SPRITE_DIRECTION,x sta SPRITE_POINTER_BASE,x ++ rts .ReallyWakeUp
Also, having the spawn animation playing but then "appearing" underground is awkward the start state of zombies is now fully alive. Which is simply a change in the TYPE_START_STATE table.

That was simple now step95.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 94

Now we have a real two player coop mode. It's not both players playing by themselves, but true teamwork. Sam needs to capture, and only then Dean can shoot enemies.



To makes things easier we add a new flag to check if two player mode is active (TWO_PLAYER_MODE_ACTIVE): ;set two player mode active flag lda #0 sta TWO_PLAYER_MODE_ACTIVE lda GAME_MODE cmp #GT_COOP bne + inc TWO_PLAYER_MODE_ACTIVE +
Remove the flag if one of the player dies on his last life: .OtherPlayerStillAlive ;remove 2 player active flag lda #0 sta TWO_PLAYER_MODE_ACTIVE jsr RemoveObject rts
If Sam is firing away and the enemy would be hurt, we bail out if our flag is set. The flag set means having a value of 0. So beq actually checks if it is not set: ;Sam needs to keep pressed jsr RedrawForceBeam ldy SPRITE_HELD dey ldx SPRITE_ACTIVE,y lda IS_TYPE_ENEMY,x cmp #1 bne .NormalHurtByForce ;in 2p mode? lda TWO_PLAYER_MODE_ACTIVE beq .NormalHurtByForce ;no further action jmp .NoEnemyHeld .NormalHurtByForce
In Deans shot routine we check if the enemy is held, if not bail out: .EnemyHit ;enemy hit! ;is two player enemy? ldy SPRITE_ACTIVE,x lda IS_TYPE_ENEMY,y cmp #1 bne .HitEnemy ;in 2p mode? lda TWO_PLAYER_MODE_ACTIVE beq .HitEnemy ;is the player held? ldy SPRITE_HELD dey sty PARAM1 cpx PARAM1 beq .HitEnemy ;enemy would be hit, but is not held jmp .ShotDone .HitEnemy
Simple addon, but bound to get complicated step94.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 93

An addon to make the other bosses a bit stronger against Sam. Now you need to re-grab the boss after every hit you place.
Previously Sam only had to stand there and keep fire pressed. Hardly a challenge



First of all, a new enemy type is added. Normal enemies stay type 1, bosses are now type 3.

Most changes are in the FireSam routine. Add in the PLAYER_FIRE_RELEASED check: .FireSam ldy PLAYER_JOYSTICK_PORT,x lda JOYSTICK_PORT_II,y and #$10 beq + ;not fire pressed lda #1 sta PLAYER_FIRE_RELEASED,x jmp .SamNotFirePushed + lda #1 sta PLAYER_FIRE_PRESSED_TIME,x stx PARAM6 lda SPRITE_HELD bne .NoFireReleasedCheck jsr SamUseForce beq .NoEnemyHeld ldx CURRENT_INDEX lda PLAYER_FIRE_RELEASED,x bne + jmp .SamNotFirePushed + lda #0 sta PLAYER_FIRE_RELEASED,x .NoFireReleasedCheck

In the enemy hurt routine we check if a boss was hurt (by checking the type). If it is, release the enemy from the force grip: ;enemy was hurt ldy SPRITE_HELD dey lda SPRITE_ACTIVE,y tay lda IS_TYPE_ENEMY,y cmp #3 ;if boss, auto-release bne + lda #0 sta PLAYER_FIRE_RELEASED,x jmp .SamNotFirePushed + Have fun!

step93.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 92

Poor Sam was left out again. Now he can kill the boss too.



Since the boss is a special beast you wouldn't want Sam just to stand there and kill him without any reaction.

We add a new variable BOSS_HELD, similar to the SPRITE_HELD value.
So if SAM hurts the enemy, and it's the boss, the boss is released from Sam's grip: dec SPRITE_HP,x beq .EnemyKilled ;enemy was hurt lda BOSS_HELD beq .EnemyWasHurt ;release if end boss jmp .SamNotFirePushed

BOSS_HELD is set to 1 if Sam has the boss in his force grip. We check if the sprite caught is the last boss or one of his parts: .EnemyHit ;enemy hit! stx SPRITE_HELD ldy SPRITE_HELD inc SPRITE_HELD lda SPRITE_ACTIVE,y cmp #TYPE_BOSS7 beq .HoldingBoss cmp #TYPE_BOSS_PART beq .HoldingBoss jmp .NotHoldingBoss .HoldingBoss sty BOSS_HELD inc BOSS_HELD .NotHoldingBoss
Therefore we also need to clear the bit in case the enemy or Sam is killed: !zone KillEnemy KillEnemy ;is the enemy currently held? ldy SPRITE_HELD dey sty PARAM4 cpx PARAM4 bne .WasNotHeld lda #0 sta SPRITE_HELD sta BOSS_HELD
Obviously the boss should not move when being caught, so in BehaviourBoss7 we add an early bail out: .NoHitBack lda BOSS_HELD beq + rts +
The step also adds a few bug fixes, as in the boss not auto-moving the bats he spawned (as if they were body parts).

Have fun! step92.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 91

Aaaand the torso gets to fight back too, not only sit put.





Time to reuse existing code again. The torso will spit out bats just like the last two bossed did. SPRITE_MODE_POS is used to stop the attacking mode and revert back to movement.

A contains the number of boss parts killed (so 4 = 2 legs plus 2 arms): cmp #4 bne + ;attack with bats lda SPRITE_CHAR_POS_X,x sta PARAM1 lda SPRITE_CHAR_POS_Y,x clc adc #4 sta PARAM2 inc PARAM2 stx PARAM10 jsr GenerateRandomNumber and #$01 beq .NoBatLeft jsr FindEmptySpriteSlot beq ++ lda #TYPE_BAT_ATTACKING sta PARAM3 jsr SpawnObject lda #0 sta SPRITE_DIRECTION,x .NoBatLeft jsr GenerateRandomNumber and #$01 beq .NoBatRight jsr FindEmptySpriteSlot beq ++ jsr SpawnObject lda #1 sta SPRITE_DIRECTION,x ++ .NoBatRight ldx CURRENT_INDEX lda SPRITE_MODE_POS,x cmp #20 bne +++ dec SPRITE_STATE,x lda #0 sta SPRITE_MODE_POS,x +++ rts +

That's all there is for this step step91.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 90

And the boss got a bit more lively (visually), it was quite stiff previously. Moving body parts and the head screams (also visually) when getting hurt.




The boss helper code is enhanced by a routine doing circling movements: jsr GenerateRandomNumber and #$03 bne .DoY inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x and #$0f sta SPRITE_MOVE_POS,x ldy SPRITE_MOVE_POS,x lda BOSS_DELTA_TABLE_X,y beq .DoY sta PARAM1 bmi .Left .Right jsr MoveSpriteRight dec PARAM1 bne .Right jmp .DoY .Left jsr MoveSpriteLeft inc PARAM1 bne .Left .DoY jsr GenerateRandomNumber and #$03 bne .Done inc SPRITE_MOVE_POS_Y,x lda SPRITE_MOVE_POS_Y,x and #$0f sta SPRITE_MOVE_POS_Y,x ldy SPRITE_MOVE_POS_Y,x lda BOSS_DELTA_TABLE_Y,y beq .Done sta PARAM1 bmi .Up .Down jsr MoveSpriteDown dec PARAM1 bne .Down jmp .Done .Up jsr MoveSpriteUp inc PARAM1 bne .Up .Done rts
Plus a rather simple swinging table: BOSS_DELTA_TABLE_X !byte 0, 1, 0, 1, 1, 0, 1, 0 BOSS_DELTA_TABLE_Y !byte 0, $ff, 0, $ff, $ff, 0, $ff, 0 !byte 0, 1, 0, 1, 1, 0, 1, 0  
To make the boss' head scream we adjust its hurt routine:   ;------------------------------------------------------------ ;hit behaviour getting hurt ;------------------------------------------------------------ !zone HitBehaviourBoss7 HitBehaviourBoss7 lda #SPRITE_BOSS_HEAD_HURT sta SPRITE_POINTER_BASE,x jmp HitBehaviourHurt   step90.zip
Previous Step Next Step    

Endurion

Endurion

 

A C64 Game - Step 89

Now you can kill the last part. Beware, it will fight back though!




All kind of changes are added to the boss 7 behaviour routine, as it now gets a new state. First of all, handle getting hit ;------------------------------------------------------------ ;boss #7 ;state = 0, 128 -> random movements ;state = 129 -> attack with beams ;------------------------------------------------------------ !zone BehaviourBoss7 BehaviourBoss7 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 #0 bne .NoHitBack ;make vulnerable again lda #0 sta SPRITE_STATE,x lda #2 sta VIC_SPRITE_COLOR,x .NoHitBack lda SPRITE_STATE,x beq .RandomMovements cmp #1 beq + cmp #129 beq + jmp .RandomMovements +
In AttackWithBeams we add a special case when all body parts have been destroyed: lda BOSS_PARTS_KILLED cmp #5 bne + jmp FinalAttack +
FinalAttack is the meat of the new part. Shooting a beam and rotating it over the screen! (Subtly reusing existing beam helper routines) !zone FinalAttack .BeamStep1 ;left arm lda SPRITE_CHAR_POS_Y,x sta PARAM3 lda #0 sta PARAM1 lda SPRITE_CHAR_POS_X,x sta PARAM2 ldy #0 jsr CheckIsPlayerCollidingWithYPosH ldy #1 jsr CheckIsPlayerCollidingWithYPosH ldy #BEAM_TYPE_DARK lda BEAM_CHAR_H,y sta PARAM1 lda #1 sta PARAM2 lda #0 sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 lda SPRITE_CHAR_POS_X,x sec sbc #2 sta PARAM5 jsr DrawBeamHSegment rts .BeamStep1End ;remove beam lda #0 sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 lda #39 sta PARAM5 jsr RestoreBeamHSegment jsr RedrawItems rts .BeamStep2 lda SPRITE_CHAR_POS_Y,x sta PARAM2 lda SPRITE_CHAR_POS_X,x sta PARAM1 ldy #0 jsr CheckIsPlayerCollidingWithDiagonalLLUR ldy #1 jsr CheckIsPlayerCollidingWithDiagonalLLUR ldy #3 lda BEAM_CHAR_NESW,y sta PARAM1 lda #1 sta PARAM2 lda SPRITE_CHAR_POS_X,x sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 jsr DrawBeamDiagonalLLUR rts .BeamStep2End lda SPRITE_CHAR_POS_X,x sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 jsr RestoreBeamDiagonalLLUR jsr RedrawItems rts FinalAttack ;mode 5, 6 = left ; 7, 8 = diagonal left down ; 9, 10 = down ; 11, 12 = diagonal right down ; 13, 14 = right lda SPRITE_MODE_POS,x cmp #5 bne + jmp .BeamStep1 + cmp #6 beq .BeamStep1End cmp #7 beq .BeamStep2 cmp #8 beq .BeamStep2End cmp #9 beq .BeamStep3 cmp #10 beq .BeamStep3End cmp #11 beq .BeamStep4 cmp #12 bne + jmp .BeamStep4End + cmp #13 bne + jmp .BeamStep5 + cmp #14 bne + ; .BeamStep5End jmp .BeamStep1End + cmp #15 bne + lda #0 sta SPRITE_MODE_POS,x sta SPRITE_STATE,x + rts .BeamStep3 ;does player hit beam? ldy #0 jsr CheckIsPlayerCollidingWithBeamV ldy #1 jsr CheckIsPlayerCollidingWithBeamV ldy #BEAM_TYPE_DARK lda BEAM_CHAR_H,y sta PARAM1 lda BEAM_CHAR_V,y sta PARAM2 lda #1 sta PARAM3 lda SPRITE_CHAR_POS_X,x sta PARAM4 lda SPRITE_CHAR_POS_Y,x sta PARAM5 stx PARAM6 jsr DrawBeamV rts .BeamStep3End lda SPRITE_CHAR_POS_X,x sta PARAM4 lda SPRITE_CHAR_POS_Y,x sta PARAM5 jsr RestoreBeamHV jsr RedrawItems rts .BeamStep4 lda SPRITE_CHAR_POS_X,x sta PARAM1 lda SPRITE_CHAR_POS_Y,x sta PARAM2 ldy #0 jsr CheckIsPlayerCollidingWithDiagonalULLR ldy #1 jsr CheckIsPlayerCollidingWithDiagonalULLR ldy #3 lda BEAM_CHAR_NWSE,y sta PARAM1 lda #1 sta PARAM2 lda SPRITE_CHAR_POS_X,x sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 jsr DrawBeamDiagonalULLR rts .BeamStep4End lda SPRITE_CHAR_POS_X,x sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 jsr RestoreBeamDiagonalULLR jsr RedrawItems rts .BeamStep5 ;right arm lda SPRITE_CHAR_POS_Y,x sta PARAM3 lda SPRITE_CHAR_POS_X,x sta PARAM1 lda #39 sta PARAM2 ldy #0 jsr CheckIsPlayerCollidingWithYPosH ldy #1 jsr CheckIsPlayerCollidingWithYPosH ldy #BEAM_TYPE_DARK lda BEAM_CHAR_H,y sta PARAM1 lda #1 sta PARAM2 lda SPRITE_CHAR_POS_X,x sta PARAM3 lda SPRITE_CHAR_POS_Y,x sta PARAM4 lda #39 sta PARAM5 jsr DrawBeamHSegment rts
Have fun and look out! step89.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 88

And finally, here's the big boss. Expect him to put up quite a fight once he is completed. Note that currently you cannot kill the last part.



First of all, the boss is not just simply there, it is entering with a few flashes. We reuse Dean's shot flash code for this. To mark the final boss entry we use a new bit in the level config byte: ;final boss intro lda LEVEL_CONFIG and #$08 beq ++ jsr HandleFinalBossIntro jmp .NotDoneYet ++

The boss is compiled of several sprites which we create once the flashes are done. Also, the boss bit is removed to avoid restarting the boss intro. !zone HandleFinalBossIntro HandleFinalBossIntro inc FINAL_INTRO_TIMER_DELAY lda FINAL_INTRO_TIMER_DELAY and #$03 beq ++ rts ++ inc FINAL_INTRO_TIMER lda FINAL_INTRO_TIMER cmp #5 beq .Flash cmp #8 beq .Flash cmp #10 beq .Flash cmp #11 beq .SpawnBoss rts .Flash ;use dean's shot flash lda #5 sta PLAYER_RELOAD_FLASH_POS lda #1 sta VIC_BACKGROUND_COLOR rts .SpawnBoss ;disable intro flag lda LEVEL_CONFIG and #$f7 sta LEVEL_CONFIG ;spawn boss lda #0 sta BOSS_PARTS_KILLED lda #19 sta PARAM1 lda #6 sta PARAM2 lda #TYPE_BOSS7 sta PARAM3 jsr FindEmptySpriteSlot jsr SpawnObject stx PARAM10 ;torso lda #19 sta PARAM1 lda #10 sta PARAM2 lda #TYPE_BOSS5 sta PARAM3 jsr FindEmptySpriteSlot jsr SpawnObject lda #TYPE_BOSS_PART sta SPRITE_ACTIVE,x lda #128 sta SPRITE_STATE,x jsr MoveSpriteUp jsr MoveSpriteUp lda PARAM10 sta SPRITE_VALUE,x lda #2 sta VIC_SPRITE_COLOR,x ;left arm lda #17 sta PARAM1 lda #9 sta PARAM2 lda #TYPE_BOSS3 sta PARAM3 jsr FindEmptySpriteSlot jsr SpawnObject lda #TYPE_BOSS_PART sta SPRITE_ACTIVE,x lda #0 sta SPRITE_STATE,x lda PARAM10 sta SPRITE_VALUE,x lda #2 sta VIC_SPRITE_COLOR,x ;right arm lda #21 sta PARAM1 lda #9 sta PARAM2 lda #TYPE_BOSS4 sta PARAM3 jsr FindEmptySpriteSlot jsr SpawnObject lda #TYPE_BOSS_PART sta SPRITE_ACTIVE,x lda #0 sta SPRITE_STATE,x lda PARAM10 sta SPRITE_VALUE,x lda #2 sta VIC_SPRITE_COLOR,x ;left foot lda #18 sta PARAM1 lda #13 sta PARAM2 lda #TYPE_BOSS2 sta PARAM3 jsr FindEmptySpriteSlot jsr SpawnObject lda #TYPE_BOSS_PART sta SPRITE_ACTIVE,x lda #0 sta SPRITE_STATE,x lda PARAM10 sta SPRITE_VALUE,x lda #2 sta VIC_SPRITE_COLOR,x ;right foot lda #20 sta PARAM1 lda #13 sta PARAM2 lda #TYPE_BOSS sta PARAM3 jsr FindEmptySpriteSlot jsr SpawnObject lda #TYPE_BOSS_PART sta SPRITE_ACTIVE,x lda #0 sta SPRITE_STATE,x lda PARAM10 sta SPRITE_VALUE,x lda #2 sta VIC_SPRITE_COLOR,x jmp .Flash
Also, the boss is required to be killed in several steps. First the limbs, followed by torso and finally the head. This is done by setting states accordingly (remember, states >= 128 mark invincibility) increasing the killed body part count and making other parts vulnerable: cpy #TYPE_BOSS_PART bne ++ inc BOSS_PARTS_KILLED lda BOSS_PARTS_KILLED cmp #5 beq .MakeBossHeadVulnerable cmp #4 bne ++ ;make boss torso vulnerable ldy #1 - lda SPRITE_ACTIVE,y cmp #TYPE_BOSS_PART beq + iny bne - + lda #0 sta SPRITE_STATE,y jmp ++ .MakeBossHeadVulnerable ldy #1 - lda SPRITE_ACTIVE,y cmp #TYPE_BOSS7 beq + iny bne - + lda #0 sta SPRITE_STATE,y ++
Since the boss object is controlled by the head but bigger with all parts attached the possible movement area needs to be limited (the boss body should not move outside the screen). Therefore a little check is added to all movement routines: lda SPRITE_CHAR_POS_X,x cmp MOVE_BORDER_LEFT beq .BlockedLeft

..and similar to the other three directions.

The bosses limbs are shooting beams horizontally and diagonally. These require a few changes to the existing collision check routines to allow for partial beams: ;------------------------------------------------------------ ;check player vs. beam H ; YPos in PARAM3 ; player index in y ;------------------------------------------------------------ !zone CheckIsPlayerCollidingWithYPosH CheckIsPlayerCollidingWithYPosH lda SPRITE_ACTIVE,y bne .PlayerIsActive .PlayerNotActive rts .PlayerIsActive cmp #TYPE_PLAYER_DEAN beq + cmp #TYPE_PLAYER_SAM beq + rts + lda SPRITE_STATE,y cmp #128 bcs .PlayerNotActive lda SPRITE_CHAR_POS_X,y cmp PARAM1 bcc .PlayerNotActive cmp PARAM2 bcs .PlayerNotActive ;compare char positions in y lda PARAM3 cmp SPRITE_CHAR_POS_Y,y beq .PlayerHit clc adc #1 cmp SPRITE_CHAR_POS_Y,y beq .PlayerHit sec sbc #2 cmp SPRITE_CHAR_POS_Y,y beq .PlayerHit ;not hit rts .PlayerHit ;player killed jmp KillPlayer ;------------------------------------------------------------ ;check player vs. diagonal beam ; X start in PARAM1 ; Y start in PARAM2 ; player index in y ;------------------------------------------------------------ !zone CheckIsPlayerCollidingWithDiagonalLLUR CheckIsPlayerCollidingWithDiagonalLLUR lda SPRITE_ACTIVE,y bne .PlayerIsActive .PlayerNotActive rts .PlayerIsActive lda SPRITE_STATE,y cmp #128 bcs .PlayerNotActive ;compare char positions in x lda PARAM1 sec sbc SPRITE_CHAR_POS_X,y bpl .PositiveX ;player is to the right rts .PositiveX sta PARAM3 lda PARAM2 sec sbc SPRITE_CHAR_POS_Y,y bpl .PositiveY lda SPRITE_CHAR_POS_Y,y sec sbc PARAM2 .PositiveY sta PARAM4 lda PARAM3 cmp PARAM4 beq .PlayerHit lda PARAM3 sec sbc PARAM4 bpl .PositiveDelta lda PARAM4 sec sbc PARAM3 .PositiveDelta cmp #1 beq .PlayerHit ;not hit rts .PlayerHit ;player killed jmp KillPlayer ;------------------------------------------------------------ ;check player vs. diagonal beam ; X start in PARAM1 ; Y start in PARAM2 ; player index in y ;------------------------------------------------------------ !zone CheckIsPlayerCollidingWithDiagonalULLR CheckIsPlayerCollidingWithDiagonalULLR lda SPRITE_ACTIVE,y bne .PlayerIsActive .PlayerNotActive rts .PlayerIsActive lda SPRITE_STATE,y cmp #128 bcs .PlayerNotActive ;compare char positions in x lda PARAM1 sec sbc SPRITE_CHAR_POS_X,y bpl .PlayerNotActive ;player is to the right lda SPRITE_CHAR_POS_X,y sec sbc PARAM1 sta PARAM3 lda PARAM2 sec sbc SPRITE_CHAR_POS_Y,y bpl .PositiveY lda SPRITE_CHAR_POS_Y,y sec sbc PARAM2 .PositiveY sta PARAM4 lda PARAM3 cmp PARAM4 beq .PlayerHit lda PARAM3 sec sbc PARAM4 bpl .PositiveDelta lda PARAM4 sec sbc PARAM3 .PositiveDelta cmp #1 beq .PlayerHit ;not hit rts .PlayerHit ;player killed jmp KillPlayer

All the beams are controlled by the boss head. Attack states, firing and collision checks: ;------------------------------------------------------------ ;boss #7 ;state = 128 -> random movements ;state = 129 -> attack with beams ;------------------------------------------------------------ !zone BehaviourBoss7 BehaviourBoss7 BOSS_MOVE_SPEED = 1 lda SPRITE_STATE,x beq .RandomMovements cmp #129 bne + jmp .AttackWithBeams + .RandomMovements inc SPRITE_MODE_POS,x bne + ;attack mode lda #129 sta SPRITE_STATE,x rts + lda SPRITE_MOVE_POS,x bne .DoMove ;find new random dir lda #25 sta SPRITE_MOVE_POS,x jsr GenerateRandomNumber and #$01 sta SPRITE_DIRECTION,x jsr GenerateRandomNumber and #$01 sta SPRITE_DIRECTION_Y,x .DoMove dec SPRITE_MOVE_POS,x lda #12 sta MOVE_BORDER_LEFT lda #39 - 12 sta MOVE_BORDER_RIGHT lda #4 sta MOVE_BORDER_TOP lda #12 sta MOVE_BORDER_BOTTOM lda SPRITE_DIRECTION,x beq ++ jsr ObjectMoveLeftBlocking beq .DoMoveY ;move other parts lda #5 sta PARAM10 - inx jsr ObjectMoveLeft dec PARAM10 bne - jmp .DoMoveY ++ jsr ObjectMoveRightBlocking beq .DoMoveY ;move other parts lda #5 sta PARAM10 - inx jsr ObjectMoveRight dec PARAM10 bne - .DoMoveY ldx CURRENT_INDEX lda SPRITE_DIRECTION_Y,x beq ++ jsr ObjectMoveUpBlocking beq .DoMoveDone ;move other parts lda #5 sta PARAM10 - inx jsr ObjectMoveUp dec PARAM10 bne - jmp .DoMoveDone ++ jsr ObjectMoveDownBlocking beq .DoMoveDone ;move other parts lda #5 sta PARAM10 - inx jsr ObjectMoveDown dec PARAM10 bne - .DoMoveDone lda #0 sta MOVE_BORDER_LEFT sta MOVE_BORDER_TOP lda #39 sta MOVE_BORDER_RIGHT lda #23 sta MOVE_BORDER_BOTTOM rts .AttackWithBeams inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x and #$03 beq + rts + inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #5 bcs + jmp .BeamNotDangerous + cmp #12 bcc + jmp .BeamNotDangerous + ;does player hit beam? ;modify x to point to arm object ;TODO - only check left/right segment! lda SPRITE_ACTIVE + 2,x beq ++ ;left arm lda SPRITE_CHAR_POS_Y,x clc adc #3 sta PARAM3 lda #0 sta PARAM1 lda SPRITE_CHAR_POS_X,x sec sbc #2 sta PARAM2 ldy #0 jsr CheckIsPlayerCollidingWithYPosH ldy #1 jsr CheckIsPlayerCollidingWithYPosH ++ lda SPRITE_ACTIVE + 3,x beq ++ ;right arm lda SPRITE_CHAR_POS_Y,x clc adc #3 sta PARAM3 lda SPRITE_CHAR_POS_X,x clc adc #2 sta PARAM1 lda #39 sta PARAM2 ldy #0 jsr CheckIsPlayerCollidingWithYPosH ldy #1 jsr CheckIsPlayerCollidingWithYPosH ++ lda SPRITE_ACTIVE + 4,x beq ++ lda SPRITE_CHAR_POS_Y,x clc adc #8 sta PARAM2 lda SPRITE_CHAR_POS_X,x sec sbc #1 sta PARAM1 ldy #0 jsr CheckIsPlayerCollidingWithDiagonalLLUR ldy #1 jsr CheckIsPlayerCollidingWithDiagonalLLUR ++ lda SPRITE_ACTIVE + 5,x beq ++ lda SPRITE_CHAR_POS_X,x clc adc #2 sta PARAM1 lda SPRITE_CHAR_POS_Y,x clc adc #8 sta PARAM2 ldy #0 jsr CheckIsPlayerCollidingWithDiagonalULLR ldy #1 jsr CheckIsPlayerCollidingWithDiagonalULLR ++ .BeamNotDangerous lda SPRITE_MODE_POS,x cmp #5 beq .BeamStep1 cmp #6 beq .BeamStep2 cmp #7 beq .BeamStep3 cmp #8 beq .BeamStep4 cmp #9 beq .BeamStep3 cmp #10 beq .BeamStep4 cmp #11 beq .BeamStep3 cmp #12 bne + lda #128 sta SPRITE_STATE,x lda #0 sta SPRITE_MODE_POS,x ;remove beam lda #0 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #3 sta PARAM4 lda #39 sta PARAM5 jsr RestoreBeamHSegment lda SPRITE_CHAR_POS_X,x sec sbc #1 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #8 sta PARAM4 jsr RestoreBeamDiagonalLLUR lda SPRITE_CHAR_POS_X,x clc adc #2 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #8 sta PARAM4 jsr RestoreBeamDiagonalULLR + rts .BeamStep1 ;beam ldy #BEAM_TYPE_DARK jmp .HandleBeam .BeamStep2 ;beam ldy #BEAM_TYPE_MEDIUM jmp .HandleBeam .BeamStep3 ;beam ldy #BEAM_TYPE_LIGHT jmp .HandleBeam .BeamStep4 ;beam ldy #BEAM_TYPE_LIGHT2 jmp .HandleBeam .HandleBeam ;PARAM1 = beam h char ;PARAM2 = beam color ;PARAM3 = x char pos ;PARAM4 = y char pos ;PARAM5 = x end pos tya pha lda BEAM_CHAR_H,y sta PARAM1 lda BEAM_COLOR,y sta PARAM2 ;left arm lda SPRITE_ACTIVE + 2,x beq ++ lda #0 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #3 sta PARAM4 lda SPRITE_CHAR_POS_X,x sec sbc #2 sta PARAM5 jsr DrawBeamHSegment ++ lda SPRITE_ACTIVE + 3,x beq ++ lda SPRITE_CHAR_POS_X,x clc adc #3 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #3 sta PARAM4 lda #39 sta PARAM5 jsr DrawBeamHSegment ++ ;diagonal left pla pha tay lda SPRITE_ACTIVE + 4,x beq ++ lda BEAM_CHAR_NESW,y sta PARAM1 lda SPRITE_CHAR_POS_X,x sec sbc #1 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #8 sta PARAM4 jsr DrawBeamDiagonalLLUR ++ pla tay lda SPRITE_ACTIVE + 5,x beq ++ lda BEAM_CHAR_NWSE,y sta PARAM1 lda SPRITE_CHAR_POS_X,x clc adc #2 sta PARAM3 lda SPRITE_CHAR_POS_Y,x clc adc #8 sta PARAM4 jsr DrawBeamDiagonalULLR ++ rts
Firing the beams (and restoring background) is handled by new sub routines: ;PARAM1 = beam h char ;PARAM2 = beam color ;PARAM3 = x char pos ;PARAM4 = y char pos !zone DrawBeamDiagonalLLUR DrawBeamDiagonalLLUR .NextLine ldy PARAM4 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 ;left ldy PARAM3 lda PARAM1 sta (ZEROPAGE_POINTER_1),y lda PARAM2 sta (ZEROPAGE_POINTER_2),y inc PARAM4 lda PARAM4 cmp #22 beq .LowerPartDone ;left border reached? lda PARAM3 beq .LowerPartDone dec PARAM3 jmp .NextLine .LowerPartDone rts ;PARAM3 = x char pos ;PARAM4 = y char pos !zone RestoreBeamDiagonalLLUR RestoreBeamDiagonalLLUR .NextLine ldy PARAM4 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 clc adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 ) sta ZEROPAGE_POINTER_2 + 1 sec sbc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) >> 8 ) sta ZEROPAGE_POINTER_3 + 1 sec sbc #( ( SCREEN_BACK_CHAR - SCREEN_BACK_COLOR ) >> 8 ) sta ZEROPAGE_POINTER_4 + 1 ;left ldy PARAM3 lda (ZEROPAGE_POINTER_3),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_2),y inc PARAM4 lda PARAM4 cmp #22 beq .LowerPartDone ;left border reached? lda PARAM3 beq .LowerPartDone dec PARAM3 jmp .NextLine .LowerPartDone rts ;PARAM1 = beam h char ;PARAM2 = beam color ;PARAM3 = x char pos ;PARAM4 = y char pos !zone DrawBeamDiagonalULLR DrawBeamDiagonalULLR .NextLine ldy PARAM4 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 ldy PARAM3 lda PARAM1 sta (ZEROPAGE_POINTER_1),y lda PARAM2 sta (ZEROPAGE_POINTER_2),y inc PARAM4 lda PARAM4 cmp #22 beq .LowerPartDone ;left border reached? lda PARAM3 cmp #39 beq .LowerPartDone inc PARAM3 jmp .NextLine .LowerPartDone rts ;PARAM3 = x char pos ;PARAM4 = y char pos !zone RestoreBeamDiagonalULLR RestoreBeamDiagonalULLR .NextLine ldy PARAM4 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 clc adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 ) sta ZEROPAGE_POINTER_2 + 1 sec sbc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) >> 8 ) sta ZEROPAGE_POINTER_3 + 1 sec sbc #( ( SCREEN_BACK_CHAR - SCREEN_BACK_COLOR ) >> 8 ) sta ZEROPAGE_POINTER_4 + 1 ldy PARAM3 lda (ZEROPAGE_POINTER_3),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_2),y inc PARAM4 lda PARAM4 cmp #22 beq .LowerPartDone ;left border reached? lda PARAM3 cmp #39 beq .LowerPartDone inc PARAM3 jmp .NextLine .LowerPartDone rts

The behaviour of the boss parts is surprisingly simple, just react on being hit: ;------------------------------------------------------------ ;boss helper ;------------------------------------------------------------ !zone BehaviourBossHelper BehaviourBossHelper 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 #0 bne .NoHitBack ;make vulnerable again lda #0 sta SPRITE_STATE,x lda #2 sta VIC_SPRITE_COLOR,x .NoHitBack rts

Have fun!   step88.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 86

And something different for a change: Added road side stones to the story pages to make it look a bit neater.

Looks better in motion



It's actually pretty simple: We add a stone in front and one in the back. Store positions and update them every frame with different deltas.

Start with the position value variables: MOVE_STONES !byte 0 MOVE_STONE_POS_BACK !byte 0 MOVE_STONE_POS_FRONT !byte 0
...and the actual code. STONE_BACK_DISTANCE = 20 STONE_FRONT_DISTANCE = 24 lda MOVE_STONES bne + jmp .NoStones + ;background ldy #19 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 ldy MOVE_STONE_POS_BACK - lda #32 sta (ZEROPAGE_POINTER_1),y tya clc adc #STONE_BACK_DISTANCE tay cpy #39 bcc - lda MOVE_STONE_POS_BACK clc adc #1 - cmp #STONE_BACK_DISTANCE bcc + sec sbc #STONE_BACK_DISTANCE jmp - + sta MOVE_STONE_POS_BACK ldy MOVE_STONE_POS_BACK - lda #149 sta (ZEROPAGE_POINTER_1),y lda #8 sta (ZEROPAGE_POINTER_2),y tya clc adc #STONE_BACK_DISTANCE tay cpy #39 bcc - ;foreground ldy #22 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 ldy MOVE_STONE_POS_FRONT - lda #32 sta (ZEROPAGE_POINTER_1),y tya clc adc #STONE_FRONT_DISTANCE tay cpy #39 bcc - lda MOVE_STONE_POS_FRONT clc adc #2 - cmp #STONE_FRONT_DISTANCE bcc + sec sbc #STONE_FRONT_DISTANCE jmp - + sta MOVE_STONE_POS_FRONT ldy MOVE_STONE_POS_FRONT - lda #148 sta (ZEROPAGE_POINTER_1),y lda #9 sta (ZEROPAGE_POINTER_2),y tya clc adc #STONE_FRONT_DISTANCE tay cpy #39 bcc - .NoStones
step86.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 84

And here's the next portal.



I hope you don't mind the slower pace, I'm working on a bigger step in the background.

Have fun!

step84.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 83

And here's a new extra for Sam. It works similar to the super bullet. For every demon blood picked Sam can destroy an enemy with one touch.




The code is quite similar to the super bullet. Add a new counter variable (DEMON_BLOOD), a new item image, make it possible for the item to spawn as well.

At the item pickup we add this. If the player is Sam (means x = 1) increase the counter. .EffectDemonBlood cpx #1 bne .DeanDoesNotUseForce inc DEMON_BLOOD jmp .RemoveItem

At the hurt enemy part we add this little snippet. If we have a DEMON_BLOOD, decrease the count and kill the enemy right away. lda DEMON_BLOOD beq + dec DEMON_BLOOD jmp .EnemyKilled + dec SPRITE_HP,x bne .EnemyWasHurt jmp .EnemyKilled
Have fun! step83.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 82

A few very small updates coming up, here's the next portal stage. Have fun!


  step82.zip
Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 81

A new pickup! And it shines

Sometimes one of those wondrous bullets of Samuel Colt will be dropped. Those can kill any enemy with one shot, so use them well!



The changes are quite simple. In the PickupItem routine we add a handler for those new items: cmp #ITEM_SUPER_BULLET beq .EffectSuperBullet   We make sure only Dean can use it and increase the super bullet counter: .EffectSuperBullet cpx #1 beq .SamDoesNotUseBullets inc SUPER_BULLET   The super bullet shot will flash red instead of white, so we add: ;red flash for super bullet lda SUPER_BULLET beq + lda #2 jmp ++ + lda #1 ++ sta VIC_BACKGROUND_COLOR   and finally, at the enemy shot routine we add lda SUPER_BULLET beq + ;directly kill enemy dec SUPER_BULLET jmp .EnemyKilled + lda SPRITE_HP,x dec SPRITE_HP,x lda SPRITE_HP,x beq .EnemyKilled Done!

BTW, the super bullet also works on bosses!
  step81.zip
Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 80

Did you notice the huge door in the background? It's about time it opens!



That's actually pretty simple. At the jump-to-next level code we add a check. Whether the open door animation is shown depends on a bit in the LEVEL_CONFIG byte. GoToNextLevel lda LEVEL_CONFIG and #$04 beq .NoDoorAnim jsr DoorAnim .NoDoorAnim

The actual animation is straight forward. The door location is hard coded, and we simply copy the characters one step to the outer side and replace the innermost characters with empty space.

Notice that we use a method which is not possible that easy in todays code: We have a local code loop until the door is completely opened.
Since there are no events to be handled, messages to be retrieved or other OS specific code required, we can do that. ;------------------------------------------------------------ ;open door animation ;------------------------------------------------------------ !zone DoorAnim DoorAnim lda #0 sta LEVEL_DONE_DELAY .DoorAnimLoop jsr WaitFrame inc LEVEL_DONE_DELAY lda LEVEL_DONE_DELAY and #$07 bne .DoorAnimLoop ;open door (16,11) lda #11 sta PARAM2 - ldy PARAM2 lda SCREEN_LINE_OFFSET_TABLE_LO,y sta ZEROPAGE_POINTER_1 lda SCREEN_LINE_OFFSET_TABLE_HI,y sta ZEROPAGE_POINTER_1 + 1 ldy #17 lda (ZEROPAGE_POINTER_1),y dey sta (ZEROPAGE_POINTER_1),y ldy #18 lda (ZEROPAGE_POINTER_1),y dey sta (ZEROPAGE_POINTER_1),y ldy #19 lda (ZEROPAGE_POINTER_1),y dey sta (ZEROPAGE_POINTER_1),y lda #32 ldy #19 sta (ZEROPAGE_POINTER_1),y ldy #22 lda (ZEROPAGE_POINTER_1),y iny sta (ZEROPAGE_POINTER_1),y ldy #21 lda (ZEROPAGE_POINTER_1),y iny sta (ZEROPAGE_POINTER_1),y ldy #20 lda (ZEROPAGE_POINTER_1),y iny sta (ZEROPAGE_POINTER_1),y lda #32 ldy #20 sta (ZEROPAGE_POINTER_1),y inc PARAM2 lda PARAM2 cmp #21 bne - ; lda LEVEL_DONE_DELAY lsr lsr lsr cmp #4 bne .DoorAnimLoop ;door is fully open now - jsr WaitFrame inc LEVEL_DONE_DELAY lda LEVEL_DONE_DELAY cmp #200 bne - rts
Also, since I'm going on vacation this tutorial takes a break until next year. Thanks for your support so far, have fun!
  step80.zip
Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 79

Almost a new feature: For the final level range the enemies spawn in waves. In this level, after beating the wolfmen two more waves of other enemies come in. And all without any more memory in the level data!



The key is in the previously used SPAWN_SPOT_SPAWN_COUNT. We use the upper 4 bits to contain the wave the spawn spots is part of. This limits us to 15 enemies per spawn spot which we were never even close to.

We add a new counter named NUMBER_DELAYED_SPAWN_SPOTS_ALIVE. This contains the number of spawn spots that are currently not active (future waves). SPAWN_SPOT_LEVEL holds the wave number (in the upper for bits, that's why we add 16)

The ProcessSpawnSpots routine is enhanced thusly: lda NUMBER_ENEMIES_ALIVE ora NUMBER_SPAWN_SPOTS_ALIVE bne .NoDelayedSpawnSpots lda NUMBER_DELAYED_SPAWN_SPOTS_ALIVE beq .NoDelayedSpawnSpots ;undelay them now lda SPAWN_SPOT_LEVEL clc adc #16 sta SPAWN_SPOT_LEVEL ;check all spots ldx #0 - lda SPAWN_SPOT_ACTIVE,x beq + lda SPAWN_SPOT_SPAWN_COUNT,x and #$f0 cmp SPAWN_SPOT_LEVEL bne + ;undelay now lda SPAWN_SPOT_SPAWN_COUNT,x and #$0f sta SPAWN_SPOT_SPAWN_COUNT,x dec NUMBER_DELAYED_SPAWN_SPOTS_ALIVE inc NUMBER_SPAWN_SPOTS_ALIVE + inx cpx #SPAWN_SPOT_COUNT bne - .NoDelayedSpawnSpots
Obviously in the spawn spot update loop we need to skip any spawn spots where the upper four bits are set: lda SPAWN_SPOT_SPAWN_COUNT,x and #$f0 bne .NextSpawnSpot
Also, during level buildup we need to increment the correct counter depending on the upper four bits again: ;count iny lda (ZEROPAGE_POINTER_1),y sta SPAWN_SPOT_SPAWN_COUNT,x ;upper 4 bits set? then it's a delayed spawn spot! and #$f0 bne + inc NUMBER_SPAWN_SPOTS_ALIVE jmp ++ + inc NUMBER_DELAYED_SPAWN_SPOTS_ALIVE ++
Have fun surviving! step79.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 78

Added and streamlined all story pages. Now we have locations (mentioned) and a crude "story arc". Plus a little animation on the last two bosses. There's no higher meaning behind the locations, I just wandered about USA with Google maps

Showing no code this time, since it's merely added text to existing tables.

The aforementioned animation is a simple xor call on the sprite pointer: lda SPRITE_POINTER_BASE,x eor #1 sta SPRITE_POINTER_BASE,x

step78.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 77

And another boss. This one is different, as it spawns bats to the left and right. And is only vulnerable during that part



The boss routine is a bit bigger this time. The attacking bat code is already there, so nothing to add on that part.
As behaviours got increasingly complex I started to write the state values on top of the routines. Remember, any states with MSB set (>=128) mark the object as invincible. ;------------------------------------------------------------ ;boss #5 ;state 128 = find target Y ;state 129 = move towards target Y ;state 0 = attack ;state 130 = cool off ;------------------------------------------------------------ !zone BehaviourBoss6 BehaviourBoss6 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 #0 bne .NoHitBack lda #1 sta VIC_SPRITE_COLOR,x ;keep invulnerable .NoHitBack lda DELAYED_GENERIC_COUNTER and #$03 bne .NoAnimUpdate .NoAnimUpdate lda SPRITE_STATE,x beq .Attack cmp #128 bne + jmp .FindTargetY + cmp #129 beq .MoveTowardsTarget cmp #130 beq .CoolOff lda #0 sta SPRITE_STATE,x rts .CoolOff inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #30 bne + lda #128 sta SPRITE_STATE,x + rts .Attack inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x and #$1f cmp #$1f beq + rts + ;free bats inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #5 bne + lda #130 sta SPRITE_STATE,x lda #0 sta SPRITE_MODE_POS,x rts + lda SPRITE_CHAR_POS_X,x sta PARAM1 lda SPRITE_CHAR_POS_Y,x sta PARAM2 inc PARAM2 stx PARAM10 jsr FindEmptySpriteSlot beq ++ lda #TYPE_BAT_ATTACKING sta PARAM3 jsr SpawnObject lda #0 sta SPRITE_DIRECTION,x jsr FindEmptySpriteSlot beq ++ jsr SpawnObject lda #1 sta SPRITE_DIRECTION,x ++ ldx PARAM10 rts .MoveTowardsTarget ;player index in y lda SPRITE_VALUE,x cmp SPRITE_CHAR_POS_Y,x bne + ;arrived at target Y lda #0 sta SPRITE_MODE_POS,x sta SPRITE_STATE,x rts + bpl .MoveDown ;move up? 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_SPEED beq .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_SPEED beq .DoGhostMove inc SPRITE_MOVE_POS_Y,x jmp .DoGhostMove .DoGhostMove ;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 .FindTargetY lda #4 sta PARAM5 lda #18 sta PARAM6 jsr GenerateRangedRandom sta SPRITE_VALUE,x inc SPRITE_STATE,x rts
Have fun!

Previous Step Next Step step77.zip

Endurion

Endurion

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!