Jump to content
  • Advertisement
  • entries
    104
  • comments
    103
  • views
    262284

About this blog

Musings of a hobbyist

Entries in this blog

 

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

 

A C64 Game - Step 76

And yet another 10 stages. A few animation bugs show up with the water, in general there seems to be another fixing step coming up.



Have fun! step76.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 75

And more! Now the executable hit the load barrier, so Exomizer is the only way to go onwards (before it was optional). Added a new waterfall char with the same effect as low water.



At the same location the other char is initiated we add this: ldx #0 - lda $F800 + 143 * 8,x sta ANIM_TILE2_BYTES,x sta ANIM_TILE2_BYTES + 8,x inx dey cpx #8 bne -
At the location where we animate the first char we add this. Note that the second sta at $c000 is for the second char set. ;animate water tile lda DELAYED_GENERIC_COUNTER and #$03 bne + inc ANIM_POS lda ANIM_POS and #$07 tay lda ANIM_TILE_BYTES,y sta $F800 + 111 * 8 sta $C000 + 111 * 8 lda ANIM_TILE_BYTES + 8,y sta $F800 + 111 * 8 + 1 sta $C000 + 111 * 8 + 1 sty PARAM1 lda #7 sec sbc PARAM1 tay ldx #0 - lda ANIM_TILE2_BYTES,y sta $F800 + 143 * 8,x sta $C000 + 143 * 8,x inx iny cpx #8 bne - +
And finally, the actual check for the water char (143) is enhanced: ;check if we're in water ldy SPRITE_CHAR_POS_Y,x lda SCREEN_LINE_OFFSET_TABLE_LO,y sta ZEROPAGE_POINTER_1 lda SCREEN_BACK_LINE_OFFSET_TABLE_HI,y sta ZEROPAGE_POINTER_1 + 1 lda SPRITE_CHAR_POS_X,x tay lda (ZEROPAGE_POINTER_1),y cmp #111 beq .NextObject cmp #143 beq .NextObject
Another enhancement, due to packing. Now that the packer takes care of putting the sprites and characters sets at their target location we can comment them out (plus their copy routines).
With ACME or the C64 Studio assembler you do it like this: !ifndef CRUNCHED { CHARSET !binary "j.chr" CHARSET_2 !binary "j2.chr" SPRITES !binary "j.spr" END_OF_FILE }
That's all there is to this step, have fun! step75.zip
Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 74

Another boss!

And one, that works differently than the others before him.



This boss is spawning bats that fly left/right and vanish once hitting the screen border.
Thus we remove the path-8-flying bat and replace it by a simple attacking bat: !zone BehaviourBatAttacking BehaviourBatAttacking 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 #3 sta PARAM1 lda SPRITE_DIRECTION,x beq .MoveRight .MoveLeft jsr ObjectMoveLeft dec PARAM1 bne .MoveLeft jmp .XMoveDone .MoveRight jsr ObjectMoveRight dec PARAM1 bne .MoveRight .XMoveDone lda SPRITE_CHAR_POS_X,x cmp #255 beq .RemoveMe cmp #39 beq .RemoveMe rts .RemoveMe inc SPAWN_NO_ITEM jsr KillEnemy dec SPAWN_NO_ITEM rts
The boss itself is not really clever. It calculates a random y position, moves there, spawns bats and starts all over again. Note that it is only vulnerable while spawning bats though
Remember, vulnerability is controlled by having the MSB set in SPRITE_STATE,x. ;------------------------------------------------------------ ;boss #5 ;state 0 = find target Y ;state 1 = move towards target Y ;state 2 = attack ;state 3 = cool off ;------------------------------------------------------------ !zone BehaviourBoss5 BehaviourBoss5 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 ;make vulnerable again lda SPRITE_STATE,x cmp #128 bne .NoHitBack lda #0 sta SPRITE_STATE,x .NoHitBack lda DELAYED_GENERIC_COUNTER and #$03 bne .NoAnimUpdate .NoAnimUpdate lda SPRITE_STATE,x bne + jmp .FindTargetY + cmp #1 beq .MoveTowardsTarget cmp #2 beq .Attack cmp #3 beq .CoolOff lda #0 sta SPRITE_STATE,x rts .CoolOff inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #30 bne + lda #0 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 + inc 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 inc SPRITE_STATE,x lda #0 sta SPRITE_MODE_POS,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
For the boss routine we require a little utility function which returns a ranged random number. No division or module means manual work: create random number, subtract range until it's small enough, then add the lower range. ;lower end = PARAM5 ;higher end = PARAM6 GenerateRangedRandom lda PARAM6 sec sbc PARAM5 clc adc #1 sta PARAM6 jsr GenerateRandomNumber .CheckValue cmp PARAM6 bcc .ValueOk ;too high sec sbc PARAM6 jmp .CheckValue .ValueOk clc adc PARAM5 rts
Have fun!

step74.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 73

Nothing new code wise this time, but something necessary: Packing.

The normal file now hits the 40Kb mark, and it's slowly getting too big to simply load. Remember the memory layout?
There is free memory from 2048 to 49151. Above that address the Basic and Kernal are overlayed over RAM. And both are active by default when loading.

Here comes Exomizer to the rescue. It's a cross plattform packer tool targetting old computers with little memory. It compiles code parts, unpacks them to specific memory locations and even takes care of the ROM/RAM settings. Terrific!

In the .zip file there's the executable and a changed .c64 project file. In C64 Studio you'll now find a new build config "Default Crunched". This builds the file as normal and as a post build step calls Exomizer:
$(BuildTargetPath)/exomizer sfx 2064 $(BuildTargetFilename) -t64 -n -o $(BuildTargetPath)/gamecrunched.bin
..and copies over to the original output file name:
copy $(BuildTargetPath)\gamecrunched.bin $(BuildTargetPath)\jmain.prg /Y

The latter step allows us to still debug through the code as usual, since C64 Studio starts the file with the name given in the !to directive.

step73.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 72

A rather simple step, remember the story page? Now the next chapter text has been put in place, as well as a cool off text after beating the first boss.




Analog to the ShowChapter routine we add a ShowChapterEnd routine. !zone ShowChapterEnd ShowChapterEnd ;clear screen lda #32 ldy #1 jsr ClearScreen jsr ResetObjects ldy CHAPTER lda CHAPTER_END_PAGES_LO,y sta ZEROPAGE_POINTER_1 lda CHAPTER_END_PAGES_HI,y sta ZEROPAGE_POINTER_1 + 1 lda #0 sta VIC_SPRITE_ENABLE sta VIC_SPRITE_X_EXTEND sta SPRITE_POS_X_EXTEND lda #1 sta PARAM1 lda #1 sta PARAM2 jsr DisplayText jmp StoryLoop
The StoryLoop routine is a part of the original ShowChapter routine doing the car movement and wait for button part. We simply reuse the code.

At the .GotoNextLevel label we add this to actually call the new code. lda LEVEL_NR cmp #10 beq .ShowStory cmp #21 beq .ShowStory jmp .NoStory .ShowStory jsr ShowChapterEnd inc CHAPTER jsr ShowChapter pla pla lda #$0b sta VIC_CONTROL_MODE jsr StartLevel inc LEVEL_NR jsr BuildScreen jsr CopyLevelToBackBuffer jsr DisplayGetReady lda #$1b sta VIC_CONTROL_MODE jmp DisplayScoreDisplay .NoStory
Have fun!

step72.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 71

Now a little polish for Sams dark powers, they were a tad too dark to see



Realistic colors are one thing, but if it hampers the gameplay they have to go. So dark powers are now white.
And animated.


Core routine is this, it redraws every char of the beam with one of four random characters: ;redraw force beam (randomly) !zone RedrawForceBeam RedrawForceBeam ldy SAM_FORCE_START_Y lda SCREEN_LINE_OFFSET_TABLE_LO,y sta ZEROPAGE_POINTER_1 lda SCREEN_LINE_OFFSET_TABLE_HI,y sta ZEROPAGE_POINTER_1 + 1 lda SAM_FORCE_LENGTH sta PARAM1 ldy SAM_FORCE_START_X - jsr GenerateRandomNumber and #$03 clc adc #252 sta (ZEROPAGE_POINTER_1),y iny dec PARAM1 bne - rts
Most of the other changes include a call to the routine above, some are fixes for crashes that were introduces.

For one, when removing the beam item images could be clipped, a simple call to

jsr RedrawItems

fixes that.


And a little change to gameplay, if Sam moves (falls/jumps) while holding an enemy and moves too far, he loses grip on the enemy.
This is done via a little comparison between the beam start Y pos and the players pos. Too far, and the enemy is released. ;release beam when moving lda SAM_FORCE_START_Y clc adc #1 cmp SPRITE_CHAR_POS_Y,x bne .SamNotFirePushed ;Sam needs to keep pressed jsr RedrawForceBeam
Have fun!

step71.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 70

And a new boss. Quite similar to the first actually, but supposed to be a bit more aggressive.




This boss needed a bit of refactoring since a few code parts were reusable from the first boss. First up, the find one player to hunt: ;looks for player ;x = own object index ;returns player index in y !zone FindPlayer FindPlayer txa and #$01 tay lda SPRITE_ACTIVE,y cmp #TYPE_PLAYER_DEAN beq .FoundPlayer cmp #TYPE_PLAYER_SAM beq .FoundPlayer ;check other player tya eor #1 tay lda SPRITE_ACTIVE,y cmp #TYPE_PLAYER_DEAN beq .FoundPlayer cmp #TYPE_PLAYER_SAM beq .FoundPlayer ;no player to hunt ldy #2 rts .FoundPlayer rts

The homing boss movement is put in a sub routine: BossFollowPlayer inc SPRITE_ANIM_DELAY,x lda SPRITE_ANIM_DELAY,x cmp #10 beq .DoCheckMove jmp .DoGhostMove .DoCheckMove lda #0 sta SPRITE_ANIM_DELAY,x jsr FindPlayer cmp #2 bne + ;no player to hunt rts + ;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 jmp .CheckYNow .AlreadyLookingLeft lda SPRITE_MOVE_POS,x cmp #BOSS_MOVE_SPEED beq .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_FOOT_R sta SPRITE_POINTER_BASE,x jmp .CheckYNow .AlreadyLookingRight lda SPRITE_MOVE_POS,x cmp #BOSS_MOVE_SPEED beq .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_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,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
And here's the actual boss behaviour. The boss homes in on the player and will directly attack if the player could be hit: ;------------------------------------------------------------ ;boss ;------------------------------------------------------------ !zone BehaviourBoss4 BehaviourBoss4 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 SPRITE_STATE,x cmp #128 bne .NoHitBack lda #0 sta SPRITE_STATE,x .NoHitBack lda DELAYED_GENERIC_COUNTER and #$03 bne .NoAnimUpdate .NoAnimUpdate lda SPRITE_STATE,x bne .NotFollowPlayer ;check if on same height as a player lda SPRITE_ACTIVE cmp #TYPE_PLAYER_DEAN bne ++ lda SPRITE_CHAR_POS_Y cmp SPRITE_CHAR_POS_Y,x beq .StartAttackMode ++ lda SPRITE_ACTIVE + 1 cmp #TYPE_PLAYER_SAM bne ++ lda SPRITE_CHAR_POS_Y + 1 cmp SPRITE_CHAR_POS_Y,x beq .StartAttackMode ++ ;anim lda SPRITE_DIRECTION,x beq + lda #SPRITE_BOSS_FOOT_L jmp ++ + lda #SPRITE_BOSS_FOOT_R ++ sta SPRITE_POINTER_BASE,x jmp BossFollowPlayer .StartAttackMode lda #1 sta SPRITE_STATE,x lda #0 sta SPRITE_MOVE_POS,x sta SPRITE_MODE_POS,x jmp .AttackMode .NotFollowPlayer cmp #1 beq .AttackMode rts .AttackMode ;Attack modes (more modes?) inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x cmp #4 beq .NextAttackStep rts .NextAttackStep lda SPRITE_CHAR_POS_X,x sta PARAM4 lda SPRITE_CHAR_POS_Y,x sta PARAM5 lda #0 sta SPRITE_MOVE_POS,x inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #11 bcc .BeamNotDangerous cmp #29 bcs .BeamNotDangerous ;does player hit beam? ldy #0 jsr CheckIsPlayerCollidingWithBeam ldy #1 jsr CheckIsPlayerCollidingWithBeam .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 beq .BeamEnd rts .HandleBeam lda BEAM_CHAR_H,y sta PARAM1 lda BEAM_CHAR_V,y sta PARAM2 lda BEAM_COLOR,y sta PARAM3 jsr DrawBeamH jsr DrawBeamV 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 .BeamEnd jsr RestoreBeamHV lda #0 sta SPRITE_STATE,x rts
Have fun! step70.zip

Previous Step Next Step  

Endurion

Endurion

 

A C64 Game - Step 69

And yet again, the next bunch of stages, this time a deserty area.



Have fun! step69.zip
Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 68

Added a first version of Sams dark force, making it visible. It's not looking quite what I wanted it, but it is getting there.



To make things faster for removal of the beam we calc the required length and direction of the beam: ;redraw black beam ldx PARAM6 ldy SPRITE_CHAR_POS_Y,x dey sty SAM_FORCE_START_Y 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 SPRITE_CHAR_POS_X,x lda SPRITE_DIRECTION,x sta PARAM1 lda #0 sta SAM_FORCE_LENGTH ldx SPRITE_HELD dex - lda #253 sta (ZEROPAGE_POINTER_1),y lda #6 sta (ZEROPAGE_POINTER_2),y inc SAM_FORCE_LENGTH lda PARAM1 bne ++ iny jmp + ++ dey + tya cmp SPRITE_CHAR_POS_X,x bne - ;store for later removal ldx PARAM6 lda PARAM1 bne + ;going right lda SPRITE_CHAR_POS_X,x sta SAM_FORCE_START_X jmp ++ + ;going left sty SAM_FORCE_START_X inc SAM_FORCE_START_X ++ ldy SPRITE_HELD dey

The beam needs to be removed once Sam stops pressing the fire button or he or his enemy dies. Aren't we glad that we already have a back buffer we can use to restore the background properly? ;restore play field from force beam !zone RemoveForceBeam RemoveForceBeam ldy SAM_FORCE_START_Y 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 ) & 0xff00 ) >> 8 sta ZEROPAGE_POINTER_2 + 1 sec sbc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) & 0xff00 ) >> 8 sta ZEROPAGE_POINTER_3 + 1 sec sbc #( ( SCREEN_BACK_CHAR - SCREEN_BACK_COLOR ) & 0xff00 ) >> 8 sta ZEROPAGE_POINTER_4 + 1 ldy SAM_FORCE_START_X - lda (ZEROPAGE_POINTER_3),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_2),y iny dec SAM_FORCE_LENGTH bne - rts
So when Sam dies we modify existing code to: lda SPRITE_HELD beq + jsr RemoveForceBeam lda #0 sta SPRITE_HELD +
Voila! Carnage made more visible   step68.zip

Previous Step Next Step

Endurion

Endurion

 

A 64 Game - Step 67

And per popular request now we also have a Game Over sequence. We add the missing letters and add a short loop displaying and animating the sprites.



At the beginning of CheckForHighscore (which is called on game over) we add a short loop. This snippet calls a sub routine to set up the sprites and then animates the letter for a short period. ;display game over jsr DisplayGameOver ;animate lda #0 sta SPRITE_STATE .GameOverDelay jsr WaitFrame lda SPRITE_STATE and #$1f tax ldy #0 lda #8 sta PARAM2 - lda PATH_8_DY,x and #$80 bne + lda PATH_8_DY,x clc adc #110 -- sta VIC_SPRITE_Y_POS,y jmp ++ + lda PATH_8_DY,x and #$7f sta PARAM1 lda #110 sec sbc PARAM1 jmp -- ++ iny iny inx inx txa and #$1f tax dec PARAM2 bne - inc SPRITE_STATE bne .GameOverDelay ;check for highscore lda #0 sta VIC_SPRITE_ENABLE

Here's the sub routine to set up the sprites: ;------------------------------------------------------------ ;displays game over ;------------------------------------------------------------ !zone DisplayGameOver DisplayGameOver ldx #0 ldy #0 - lda GETREADY_SPRITE_X_POS,x sta VIC_SPRITE_X_POS,y lda GETREADY_SPRITE_COLOR,x sta VIC_SPRITE_COLOR,x iny iny inx cpx #8 bne - lda #110 sta VIC_SPRITE_Y_POS sta VIC_SPRITE_Y_POS + 2 sta VIC_SPRITE_Y_POS + 4 sta VIC_SPRITE_Y_POS + 6 sta VIC_SPRITE_Y_POS + 8 sta VIC_SPRITE_Y_POS + 10 sta VIC_SPRITE_Y_POS + 12 sta VIC_SPRITE_Y_POS + 14 lda #SPRITE_G sta SPRITE_POINTER_BASE lda #SPRITE_A sta SPRITE_POINTER_BASE + 1 lda #SPRITE_M sta SPRITE_POINTER_BASE + 2 lda #SPRITE_E sta SPRITE_POINTER_BASE + 3 sta SPRITE_POINTER_BASE + 6 lda #SPRITE_O sta SPRITE_POINTER_BASE + 4 lda #SPRITE_V sta SPRITE_POINTER_BASE + 5 lda #SPRITE_R sta SPRITE_POINTER_BASE + 7 lda #0 sta VIC_SPRITE_MULTICOLOR lda #$80 sta VIC_SPRITE_X_EXTEND lda #$ff sta VIC_SPRITE_ENABLE sta VIC_SPRITE_EXPAND_X sta VIC_SPRITE_EXPAND_Y rts

Well, that's that
step67.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 66

Nothing new code wise again, just the next 10 stages!



Have fun!
  step66.zip
Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 65

I had several complaints as there was no bullet. Well, after this step there is still none, but you do get a flash 


When shooting we add this: lda #5 sta PLAYER_RELOAD_FLASH_POS lda #1 sta VIC_BACKGROUND_COLOR
Below the call to ObjectControl we add this snippet to update the flash via the color fade table: ;shot flash lda PLAYER_RELOAD_FLASH_POS beq + dec PLAYER_RELOAD_FLASH_POS lda VIC_BACKGROUND_COLOR and #$0f tay lda COLOR_FADE_OUT_TABLE,y sta VIC_BACKGROUND_COLOR +
And the color fade table itself: COLOR_FADE_IN_TABLE !byte 11,1,10,1,10,13,14,1,7,8,1,12,15,1,1,1 COLOR_FADE_OUT_TABLE !byte 0,15,9,14,6,6,11,10,9,11,2,0,11,5,6,12
step65.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 64

Bug fixing!

Note how the sprites would flicker about before/after Get Ready?
Fixed. Also, Sam got borked again, esp. during a boss fight. Now that's also fixed.



First part is pretty easy, we disable the screen during build up of the level. Fortunately the VIC allows exactly this. So we add

to disable: lda #$0b sta VIC_CONTROL_MODE
to enable: lda #$1b sta VIC_CONTROL_MODE
We already saved all sprite information, but we only restored the first two sprites (the players) and rely on the usual object initialisation for the other objects. Unfortunately some levels start out with more.

So we modify the RemoveGetReady method to restore all sprites: ;turn off sprite expansion bits ldx #0 stx VIC_SPRITE_EXPAND_X stx VIC_SPRITE_EXPAND_Y ;restore all sprite positions and colors - txa asl tay lda SPRITE_POS_X,x sta VIC_SPRITE_X_POS,y lda SPRITE_POS_Y,x sta VIC_SPRITE_Y_POS,y lda SPRITE_COLOR,x sta VIC_SPRITE_COLOR,x ldy SPRITE_ACTIVE,x cpy #0 bne + -- inx cpx #8 bne - jmp GameLoop + lda TYPE_START_SPRITE,y sta SPRITE_POINTER_BASE,x jmp --
Things can be so easy to fix! step64.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 63

Ever noticed how the music gets stuck when the next stage is built? Especially notable if you use '1' to skip through stages.
That's amended in this step. It boils down to having a raster interrupt which calls the music player.



In the current version the music player routine was called in the game main loop routine. This is all fine and dandy as long as it happens once per frame. However building a new screen may take more time than that. And that's when the music gets out of sync.

The solution is rather easy here: Install a raster IRQ and put the music player call in there. Now it doesn't matter how long the screen buildup takes, the music routine will be called at a steady pace.

We start by replacing the call to ReleaseTitleIRQ with SetGameIRQ.

SetGameIRQ looks like the average IRQ setup routine: !zone SetGameIRQ SetGameIRQ ;wait for exact frame so we do not end up on the wrong ;side of the raster jsr WaitFrame sei lda #$36 ; make sure that IO regs at $dxxx are visible sta PROCESSOR_PORT lda #$ff ;nr of rasterline we want our irq occur at sta $d012 lda #$1b ;MSB of d011 is the MSB of the requested rasterline sta $d011 ;as rastercounter goes from 0-312 ;set irq vector to point to our routine lda # sta $0314 lda #>IrqInGame sta $0315 ;acknowledge any pending cia timer interrupts ;this is just so we are 100% safe lda $dc0d lda $dd0d cli rts
The IrqInGame routine looks like this, simply call the player, acknowledge the IRQ and finish via kernal. !zone IrqInGame IrqInGame !ifdef MUSIC_PLAYING{ ;play music jsr MUSIC_PLAYER + 3 } ;acknowledge VIC irq lda $d019 sta $d019 JMP $ea31
Obviously we also must remove the call to the music player in the WaitFrame routine.

Done! step63.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 62

Time to get rid of the old ugly Get Ready display. And put up a new ugly one.



The changes are pretty easy, we modify our DisplayGetReady routine to display the sprites now (and store the old sprite settings): ;------------------------------------------------------------ ;displays get ready ;------------------------------------------------------------ !zone DisplayGetReady DisplayGetReady lda #40 sta LEVEL_START_DELAY lda #0 sta SPRITE_JUMP_POS lda VIC_SPRITE_ENABLE sta SPRITE_ENABLED lda VIC_SPRITE_MULTICOLOR sta SPRITE_MULTICOLOR ldx #0 ldy #0 - lda GETREADY_SPRITE_X_POS,x sta VIC_SPRITE_X_POS,y lda VIC_SPRITE_COLOR,x sta SPRITE_COLOR,x lda GETREADY_SPRITE_COLOR,x sta VIC_SPRITE_COLOR,x iny iny inx cpx #8 bne - lda #110 sta VIC_SPRITE_Y_POS sta VIC_SPRITE_Y_POS + 2 sta VIC_SPRITE_Y_POS + 4 sta VIC_SPRITE_Y_POS + 6 sta VIC_SPRITE_Y_POS + 8 sta VIC_SPRITE_Y_POS + 10 sta VIC_SPRITE_Y_POS + 12 sta VIC_SPRITE_Y_POS + 14 lda #SPRITE_G sta SPRITE_POINTER_BASE lda #SPRITE_E sta SPRITE_POINTER_BASE + 1 sta SPRITE_POINTER_BASE + 4 lda #SPRITE_T sta SPRITE_POINTER_BASE + 2 lda #SPRITE_R sta SPRITE_POINTER_BASE + 3 lda #SPRITE_A sta SPRITE_POINTER_BASE + 5 lda #SPRITE_D sta SPRITE_POINTER_BASE + 6 lda #SPRITE_Y sta SPRITE_POINTER_BASE + 7 lda #0 sta VIC_SPRITE_MULTICOLOR lda #$80 sta VIC_SPRITE_X_EXTEND lda #$ff sta VIC_SPRITE_ENABLE sta VIC_SPRITE_EXPAND_X sta VIC_SPRITE_EXPAND_Y rts
...and then the remove routine to restore the previous sprite setup: lda SPRITE_ENABLED sta VIC_SPRITE_ENABLE lda SPRITE_MULTICOLOR sta VIC_SPRITE_MULTICOLOR lda SPRITE_POS_X_EXTEND sta VIC_SPRITE_X_EXTEND lda SPRITE_POS_X sta VIC_SPRITE_X_POS lda SPRITE_POS_X + 1 sta VIC_SPRITE_X_POS + 2 lda SPRITE_POS_Y sta VIC_SPRITE_Y_POS lda SPRITE_POS_Y + 1 sta VIC_SPRITE_Y_POS + 2 ldx #0 stx VIC_SPRITE_EXPAND_X stx VIC_SPRITE_EXPAND_Y - ldy SPRITE_ACTIVE,x cpy #0 bne + -- lda SPRITE_COLOR,x sta VIC_SPRITE_COLOR,x inx cpx #2 bne - jmp GameLoop + lda TYPE_START_SPRITE,y sta SPRITE_POINTER_BASE,x jmp --
Also, remember, tables, tables, tables?

One more for the sprite setup: GETREADY_SPRITE_X_POS !byte 32,67,102,147,182,217,252,48 - 16 GETREADY_SPRITE_COLOR !byte 2,3,4,5,6,7,8,9

step62.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 61

And a little gimmick: Did you notice the black sewage in some of the later stages? Now it affects everything that's inside, as it's slowing down enemies and the players.




The change is surprisingly simple. We have a function named ObjectControl, which is called every frame to update all objects. We simply check if an object is inside sewage. If it is, we skip every fourth update: lda DELAYED_GENERIC_COUNTER and #$03 bne + ;check if we're in water ldy SPRITE_CHAR_POS_Y,x lda SCREEN_LINE_OFFSET_TABLE_LO,y sta ZEROPAGE_POINTER_1 lda SCREEN_BACK_LINE_OFFSET_TABLE_HI,y sta ZEROPAGE_POINTER_1 + 1 lda SPRITE_CHAR_POS_X,x tay lda (ZEROPAGE_POINTER_1),y cmp #111 beq .NextObject + ldy SPRITE_ACTIVE,x ... update object
The update calls are still fast enough so no joystick movements are skipped. Yay! step61.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 60

And here's boss #3. Good luck!



A note to the latter code additions: If you wonder about the +(+) and -(-) labels, they are relative labels. They basically work like this: If a jmp or other reference is encountered, the compiler looks forward (+) or backward(-) for the first matching location. The +/- labels can be reused.
This avoids all annoying loop23, otherloop14b, etc. labels.

This boss has a vertical beam again, but this time only downwards. So here's a specialised check routine: ;------------------------------------------------------------ ;check player vs. beam ; beam boss index in x ; player index in y ; PARAM5 = beam start Y ;------------------------------------------------------------ !zone CheckIsPlayerCollidingWithBeamV CheckIsPlayerCollidingWithBeamV 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 ;compare char positions in x lda SPRITE_CHAR_POS_X,x cmp SPRITE_CHAR_POS_X,y beq .XMatch clc adc #1 cmp SPRITE_CHAR_POS_X,y beq .XMatch sec sbc #2 cmp SPRITE_CHAR_POS_X,y beq .XMatch ;not hit rts .XMatch ;compare char positions in y lda SPRITE_CHAR_POS_Y,x cmp SPRITE_CHAR_POS_Y,y bmi .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_SAM bne .PlayerWasDean ;reset Sam specific variables lda #0 sta SPRITE_HELD .PlayerWasDean rts
Code reuse and refactoring from the first boss gives us a routine to draw a vertical beam: !zone DrawBeamV DrawBeamV ldy PARAM4 ldx PARAM5 .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 #22 bne .NextLine ldx PARAM6 rts
And the rather complicated behaviour. The boss has two major states. One, where it flies horizontally and tries to home in on a player, and second, once a player has been seen, shoot downwards. ;------------------------------------------------------------ ;boss 3 ;state 1 = shoot ;state 128 = follow player ;state 129 = shoot, hitback active ;------------------------------------------------------------ !zone BehaviourBoss3 BehaviourBoss3 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 SPRITE_STATE,x cmp #129 bne .NoHitBack lda #1 sta SPRITE_STATE,x .NoHitBack lda SPRITE_STATE,x and #$7f beq .FollowPlayer ;shoot state jmp .ShootDown .FollowPlayer ;above player? txa and #$01 tay lda SPRITE_ACTIVE,y cmp #TYPE_PLAYER_DEAN beq .FoundPlayer cmp #TYPE_PLAYER_SAM beq .FoundPlayer ;check other player tya eor #1 tay lda SPRITE_ACTIVE,y cmp #TYPE_PLAYER_DEAN beq .FoundPlayer cmp #TYPE_PLAYER_SAM beq .FoundPlayer ;no player to hunt rts .FoundPlayer ;player index in y lda SPRITE_CHAR_POS_X,y cmp SPRITE_CHAR_POS_X,x bne + ;enter attack mode lda #1 sta SPRITE_STATE,x lda #0 sta SPRITE_MODE_POS,x rts + lda DELAYED_GENERIC_COUNTER and #$01 beq + rts + ;y swing inc SPRITE_MOVE_POS_Y,x lda SPRITE_MOVE_POS_Y,x and #15 sta SPRITE_MOVE_POS_Y,x ldy SPRITE_MOVE_POS_Y,x lda PATH_DY,y beq .NoYMoveNeeded sta PARAM1 and #$80 beq .MoveDown ;move up lda PARAM1 and #$7f sta PARAM1 .MoveUp jsr ObjectMoveUp dec PARAM1 bne .MoveUp jmp BossFollowPlayerX .MoveDown jsr ObjectMoveDown dec PARAM1 bne .MoveDown .NoYMoveNeeded jmp BossFollowPlayerX .ShootDown ;Attack modes (more modes?) inc SPRITE_ANNOYED,x lda SPRITE_ANNOYED,x cmp #4 beq .NextAttackStep rts .NextAttackStep lda #0 sta SPRITE_ANNOYED,x lda SPRITE_CHAR_POS_X,x sta PARAM4 lda SPRITE_CHAR_POS_Y,x sta PARAM5 inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #11 bcc .BeamNotDangerous cmp #29 bcs .BeamNotDangerous ;does player hit beam? ldy #0 jsr CheckIsPlayerCollidingWithBeamV ldy #1 jsr CheckIsPlayerCollidingWithBeamV .BeamNotDangerous lda SPRITE_MODE_POS,x cmp #11 beq .BeamStep1 cmp #12 beq .BeamStep2 cmp #13 beq .BeamStep3 cmp #16 beq .BeamStep4 cmp #17 beq .BeamStep3 cmp #18 beq .BeamStep4 cmp #19 beq .BeamStep3 cmp #20 beq .BeamStep4 cmp #21 beq .BeamStep3 cmp #22 beq .BeamStep4 cmp #23 beq .BeamStep3 cmp #24 beq .BeamStep4 cmp #25 beq .BeamStep3 cmp #26 beq .BeamStep4 cmp #27 beq .BeamStep3 cmp #28 beq .BeamStep4 cmp #29 beq .BeamStep3 cmp #30 beq .BeamEnd rts .HandleBeam lda BEAM_CHAR_H,y sta PARAM1 lda BEAM_CHAR_V,y sta PARAM2 lda BEAM_COLOR,y sta PARAM3 txa pha jsr DrawBeamV pla tax 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 .BeamEnd jsr RestoreBeamHV lda #128 sta SPRITE_STATE,x rts
Plus the usual table additions which I don't list here.

Have fun! step60.zip

Previous Step Next Step

Endurion

Endurion

 

C64 Interlude - Emulator Handling

A few comments show that people do have problems getting the actual game to run. As to expect, most people won't have a real C64 anymore (or - gasp! - never had one). Those who still have one surely know how to run the game. For all others this little guide should explain things:

A little history lesson

The C64 is a simple computer that would be attached to a TV. The casing contained the full computer and the keyboard. Programs could be loaded from tape, disk or a cartridge. Two joystick ports were available to attach joysticks, mouses, etc.

The emulators need to stick to the media types. Which is obviously accurate, but inconvenient for the end user, since different media need to be accessed differently. For every media type there's a emulator file format. For disk most commonly used is .d64, for tape .tap or .t64, for cartridge .crt.

Now the files from this tutorial are always delivered via .prg. What's that? It's a special emulator format for a raw file. It only contains the start/load address (2 bytes) and the data. All emulators I encountered are able to handle this format.


Which emulators to use?

There are a few emulators out there, pretty much all of them are accurate enough for the simple things I'm doing. I usually recommend VICE (WinVICE) since it has a consistent UI. Other known emulators are CCS64, Micro64, C64Emu.
AFAIK all of them allow a simple drag/drop of the media file onto the window.


What to look out for?

Remember the two joystick ports? Emulators allow to use different entry devices as joystick replacement. Simplest way would be a gamepad or real joystick connected to the PC. Usually you can also assign keys as the joystick. This is most cases overlaps with otherwise used keys, so take that into account.

Also, some games prefer a joystick in port 1, others in port 2 (I do the latter). You may have to swap the used joystick port via the emulator.


Consensus

1) Download an emulator of your choice.
2) Configure joystick for port 2
3) Drag jmain.prg file on emulator window
4) Fun!

Endurion

Endurion

 

A C64 Game - Step 59

And yet another boss, this time a bit different than the previous two.

;------------------------------------------------------------ ;boss 3 ;------------------------------------------------------------ !zone BehaviourBoss3 BehaviourBoss3 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 SPRITE_STATE,x ;cmp #128 ;bne .NoHitBack lda #0 sta SPRITE_STATE,x .NoHitBack lda DELAYED_GENERIC_COUNTER and #$01 beq + rts + ;y swing inc SPRITE_MOVE_POS_Y,x lda SPRITE_MOVE_POS_Y,x and #15 sta SPRITE_MOVE_POS_Y,x ldy SPRITE_MOVE_POS_Y,x lda PATH_DY,y beq .NoYMoveNeeded sta PARAM1 and #$80 beq .MoveDown ;move up lda PARAM1 and #$7f sta PARAM1 .MoveUp jsr ObjectMoveUp dec PARAM1 bne .MoveUp jmp BossFollowPlayerX .MoveDown jsr ObjectMoveDown dec PARAM1 bne .MoveDown .NoYMoveNeeded jmp BossFollowPlayerX
The boss is supposed to move quite alike the first boss horizontally. Time to make that code piece a sub routine: !zone BossFollowPlayerX BOSS3_MOVE_SPEED = 2 .DoGhostMove ;move X times ldy SPRITE_MOVE_POS,x sty PARAM4 beq + lda SPRITE_DIRECTION,x beq .DoRight .MoveLoopL jsr ObjectMoveLeftBlocking dec PARAM4 bne .MoveLoopL + rts .DoRight .MoveLoopR jsr ObjectMoveRightBlocking dec PARAM4 bne .MoveLoopR rts BossFollowPlayerX inc SPRITE_ANIM_DELAY,x lda SPRITE_ANIM_DELAY,x cmp #10 beq .DoCheckMove jmp .DoGhostMove .DoCheckMove lda #0 sta SPRITE_ANIM_DELAY,x txa and #$01 tay lda SPRITE_ACTIVE,y cmp #TYPE_PLAYER_DEAN beq .FoundPlayer cmp #TYPE_PLAYER_SAM beq .FoundPlayer ;check other player tya eor #1 tay lda SPRITE_ACTIVE,y cmp #TYPE_PLAYER_DEAN beq .FoundPlayer cmp #TYPE_PLAYER_SAM beq .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 .CheckXDone .TurnLNow ;turning now lda #1 sta SPRITE_DIRECTION,x lda #SPRITE_BOSS_ARM_L sta SPRITE_POINTER_BASE,x jmp .CheckXDone .AlreadyLookingLeft lda SPRITE_MOVE_POS,x cmp #BOSS3_MOVE_SPEED beq .CheckXDone inc SPRITE_MOVE_POS,x jmp .CheckXDone .MoveRight lda SPRITE_DIRECTION,x beq .AlreadyLookingRight lda SPRITE_MOVE_POS,x beq .TurnRNow dec SPRITE_MOVE_POS,x bne .CheckXDone ;turning now .TurnRNow lda #0 sta SPRITE_DIRECTION,x lda #SPRITE_BOSS_ARM_R sta SPRITE_POINTER_BASE,x jmp .CheckXDone .AlreadyLookingRight lda SPRITE_MOVE_POS,x cmp #BOSS3_MOVE_SPEED beq .CheckXDone inc SPRITE_MOVE_POS,x jmp .CheckXDone .CheckXDone rts
When the boss is hit he should be invulnerable for a short while, remember that we reserved the MSB in SPRITE_STATE for invincibility. HitBehaviourBoss3 lda #8 sta SPRITE_HITBACK,x ;make invincible for a short while lda SPRITE_STATE,x ora #$80 sta SPRITE_STATE,x rts
The rest boils down to the usual additions for a new object type.

Oh, and the new boss can only be hit if he shoots downwards!

Have fun! step59.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 58

Another entry with no code change at all. Richard Bayliss created custom music for the game.

Code wise there is no change, just the music player binary included is a different one.

Enjoy!

step58.zip
Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 57

This is a technically more sophisticated step. We'll add the possibility of a borderless level, and also a second charset.



Especially the latter requires a thorough planning of where to put things in memory. Remember, the VIC (the C64s graphic chip) can only see a 16K range at once. This means, all visible characters, sprite and screen memory must reside inside one 16K bank.

The new memory layout looks like this:

;screen back color $BC00 to $BFFF
;charset2 $C000 to $C800
;screen back char $C800 to $CBFF
;screen $CC00 to $CFFF
;sprites $D000 to $F7FF
;charset $F800 to $FFFF

Once that's down (and fits) the code changes are rather simple. A new byte is added per screen to allow for 8 flags. Bit 0 means no border, bit 1 means second charset.

Implementing borderless is easy, check the bit, and it set, skip the level border drawing: lda LEVEL_CONFIG and #$01 bne .SkipBorder ;draw level border lda # sta ZEROPAGE_POINTER_1 lda #>LEVEL_BORDER_DATA sta ZEROPAGE_POINTER_1 + 1 jsr .BuildLevel .SkipBorder sta VIC_MEMORY_CONTROL
Actually, the second char set is quite similar to implement: ;.. snip lda LEVEL_CONFIG and #$02 beq .SetCharSet1 ;set charset 2 lda #$30 - sta VIC_MEMORY_CONTROL jsr DisplayLevelNumber rts .SetCharSet1 lda #$3e jmp -
Setting the second char set is as simple as adding another copy routine: ;take source address from CHARSET 2 LDA # STA ZEROPAGE_POINTER_1 LDA #>CHARSET_2 STA ZEROPAGE_POINTER_1 + 1 ;now copy jsr CopyCharSet2 ;------------------------------------------------------------ ;copies charset from ZEROPAGE_POINTER_1 to ZEROPAGE_POINTER_2 ;------------------------------------------------------------ !zone CopyCharSet2 CopyCharSet2 ;set target address ($F000) lda #$00 sta ZEROPAGE_POINTER_2 lda #$C0 sta ZEROPAGE_POINTER_2 + 1 ldx #$00 ldy #$00 lda #0 sta PARAM2 .NextLine lda (ZEROPAGE_POINTER_1),Y sta (ZEROPAGE_POINTER_2),Y inx iny cpx #$08 bne .NextLine cpy #$00 bne .PageBoundaryNotReached ;we have reached the next 256 bytes, inc high byte inc ZEROPAGE_POINTER_1 + 1 inc ZEROPAGE_POINTER_2 + 1 .PageBoundaryNotReached ;only copy 254 chars to keep irq vectors intact inc PARAM2 lda PARAM2 cmp #254 beq .CopyCharsetDone ldx #$00 jmp .NextLine .CopyCharsetDone rts
The last one is a subtle change, due to the border not always used the ClearPlayScreen routine is adjusted to clear one more line.

Have fun! step57.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 56

And here's the next boss. A striking similarity to the first boss, and yet a little bit different.

The main behaviour of this boss is the same as the previous, only the beam is diagonal this time.



Making a diagonal beam is somewhat more difficult, as you need to check for both playfield borders. The beams are started from the boss position and then all 4 directions are tackled after each other. ;------------------------------------------------------------ ;boss ;------------------------------------------------------------ !zone BehaviourBoss2 BehaviourBoss2 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 SPRITE_STATE,x cmp #128 bne .NoHitBack lda #0 sta SPRITE_STATE,x .NoHitBack lda DELAYED_GENERIC_COUNTER and #$03 bne .NoAnimUpdate ;lda SPRITE_POINTER_BASE,x ;eor #$01 ;sta SPRITE_POINTER_BASE,x .NoAnimUpdate lda SPRITE_STATE,x and #$7f bne .NotFollowPlayer jmp BossFollowPlayer .NotFollowPlayer cmp #1 beq .AttackMode rts .AttackMode ;Attack modes (more modes?) inc SPRITE_MOVE_POS,x lda SPRITE_MOVE_POS,x cmp #4 beq .NextAttackStep rts .NextAttackStep lda #0 sta SPRITE_MOVE_POS,x inc SPRITE_MODE_POS,x lda SPRITE_MODE_POS,x cmp #11 bcc .BeamNotDangerous cmp #29 bcs .BeamNotDangerous ;does player hit beam? ldy #0 jsr CheckIsPlayerCollidingWithDiagonalBeam ldy #1 jsr CheckIsPlayerCollidingWithDiagonalBeam .BeamNotDangerous lda SPRITE_MODE_POS,x cmp #11 beq .BeamStep1 cmp #12 beq .BeamStep2 cmp #13 beq .BeamStep3 cmp #16 beq .BeamStep4 cmp #17 beq .BeamStep3 cmp #18 beq .BeamStep4 cmp #19 beq .BeamStep3 cmp #20 beq .BeamStep4 cmp #21 beq .BeamStep3 cmp #22 beq .BeamStep4 cmp #23 beq .BeamStep3 cmp #24 beq .BeamStep4 cmp #25 beq .BeamStep3 cmp #26 beq .BeamStep4 cmp #27 beq .BeamStep3 cmp #28 beq .BeamStep4 cmp #29 beq .BeamStep3 cmp #30 beq .BeamEnd rts .BeamStep1 ;beam lda #BEAM_TYPE_DARK jsr .DrawBeamDiagonal rts .BeamStep2 ;beam lda #BEAM_TYPE_MEDIUM jsr .DrawBeamDiagonal rts .BeamStep3 ;beam lda #BEAM_TYPE_LIGHT jsr .DrawBeamDiagonal rts .BeamStep4 ;beam lda #BEAM_TYPE_LIGHT2 jsr .DrawBeamDiagonal rts .BeamEnd jsr RestoreBeamDiagonal lda #0 sta SPRITE_STATE,x rts .DrawBeamDiagonal tay lda BEAM_CHAR_NWSE,y sta PARAM1 lda BEAM_CHAR_NESW,y sta PARAM2 lda BEAM_COLOR,y sta PARAM3 ldy SPRITE_CHAR_POS_Y,x sty PARAM9 stx PARAM6 lda SPRITE_CHAR_POS_X,x sta PARAM7 sta PARAM8 .NextUpperLine ldy PARAM9 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 ;upper part ;left ldy PARAM7 beq .NoLeftPart lda PARAM1 sta (ZEROPAGE_POINTER_1),y lda PARAM3 sta (ZEROPAGE_POINTER_2),y .NoLeftPart ;right ldy PARAM8 beq .NoRightPart lda PARAM2 sta (ZEROPAGE_POINTER_1),y lda PARAM3 sta (ZEROPAGE_POINTER_2),y .NoRightPart dec PARAM9 beq .UpperPartDone ;left border reached? lda PARAM7 beq .LeftDone dec PARAM7 .LeftDone lda PARAM8 beq .RightEndReached cmp #38 beq .RightEndReached inc PARAM8 jmp .NextUpperLine .RightEndReached lda #0 sta PARAM8 jmp .NextUpperLine .UpperPartDone ;lower part ldy SPRITE_CHAR_POS_Y,x sty PARAM9 stx PARAM6 lda SPRITE_CHAR_POS_X,x sta PARAM7 sta PARAM8 .NextUpperLineBottom ldy PARAM9 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 ;upper part ;left ldy PARAM7 beq .NoLeftPartBottom lda PARAM2 sta (ZEROPAGE_POINTER_1),y lda PARAM3 sta (ZEROPAGE_POINTER_2),y .NoLeftPartBottom ;right ldy PARAM8 beq .NoRightPartBottom lda PARAM1 sta (ZEROPAGE_POINTER_1),y lda PARAM3 sta (ZEROPAGE_POINTER_2),y .NoRightPartBottom inc PARAM9 lda PARAM9 cmp #22 beq .LowerPartDone ;left border reached? lda PARAM7 beq .LeftDoneBottom dec PARAM7 .LeftDoneBottom lda PARAM8 beq .RightEndReachedBottom cmp #38 beq .RightEndReachedBottom inc PARAM8 jmp .NextUpperLineBottom .RightEndReachedBottom lda #0 sta PARAM8 jmp .NextUpperLineBottom .LowerPartDone ldx PARAM6 rts
Obviously all the same is repeated for the removal of the beam. !zone RestoreBeamDiagonal RestoreBeamDiagonal ldy SPRITE_CHAR_POS_Y,x stx PARAM6 ldy SPRITE_CHAR_POS_Y,x sty PARAM9 stx PARAM6 lda SPRITE_CHAR_POS_X,x sta PARAM7 sta PARAM8 .NextUpperLine ldy PARAM9 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 ;upper part ;left ldy PARAM7 beq .NoLeftPart lda (ZEROPAGE_POINTER_2),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_3),y .NoLeftPart ;right ldy PARAM8 beq .NoRightPart lda (ZEROPAGE_POINTER_2),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_3),y .NoRightPart dec PARAM9 beq .UpperPartDone ;left border reached? lda PARAM7 beq .LeftDone dec PARAM7 .LeftDone lda PARAM8 beq .RightEndReached cmp #38 beq .RightEndReached inc PARAM8 jmp .NextUpperLine .RightEndReached lda #0 sta PARAM8 jmp .NextUpperLine .UpperPartDone ;lower part ldy SPRITE_CHAR_POS_Y,x sty PARAM9 stx PARAM6 lda SPRITE_CHAR_POS_X,x sta PARAM7 sta PARAM8 .NextUpperLineBottom ldy PARAM9 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 ;upper part ;left ldy PARAM7 beq .NoLeftPartBottom lda (ZEROPAGE_POINTER_2),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_3),y .NoLeftPartBottom ;right ldy PARAM8 beq .NoRightPartBottom lda (ZEROPAGE_POINTER_2),y sta (ZEROPAGE_POINTER_1),y lda (ZEROPAGE_POINTER_4),y sta (ZEROPAGE_POINTER_3),y .NoRightPartBottom inc PARAM9 lda PARAM9 cmp #22 beq .LowerPartDone ;left border reached? lda PARAM7 beq .LeftDoneBottom dec PARAM7 .LeftDoneBottom lda PARAM8 beq .RightEndReachedBottom cmp #38 beq .RightEndReachedBottom inc PARAM8 jmp .NextUpperLineBottom .RightEndReachedBottom lda #0 sta PARAM8 jmp .NextUpperLineBottom .LowerPartDone ldx PARAM6 rts
Checking if the player is hit by the beam as a bit different as well. The previous boss beam was checked simply by comparing X and Y pos. Now we take the distances between X and Y pos and compare them. They must be either equal or only differ by one to count as colliding. ;------------------------------------------------------------ ;check player vs. diagonal beam ; beam boss index in x ; player index in y ;------------------------------------------------------------ !zone CheckIsPlayerCollidingWithDiagonalBeam CheckIsPlayerCollidingWithDiagonalBeam lda SPRITE_ACTIVE,y bne .PlayerIsActive .PlayerNotActive rts .PlayerIsActive lda SPRITE_STATE,y cmp #128 bcs .PlayerNotActive ;compare char positions in x lda SPRITE_CHAR_POS_X,x sec sbc SPRITE_CHAR_POS_X,y bpl .PositiveX lda SPRITE_CHAR_POS_X,y sec sbc SPRITE_CHAR_POS_X,x .PositiveX sta PARAM1 lda SPRITE_CHAR_POS_Y,x sec sbc SPRITE_CHAR_POS_Y,y bpl .PositiveY lda SPRITE_CHAR_POS_Y,y sec sbc SPRITE_CHAR_POS_Y,x .PositiveY sta PARAM2 lda PARAM1 cmp PARAM2 beq .PlayerHit lda PARAM1 sec sbc PARAM2 and #$7f cmp #1 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_SAM bne .PlayerWasDean ;reset Sam specific variables lda #0 sta SPRITE_HELD .PlayerWasDean rts

Play the first boss, then stumble upon this one. It's not really difficult as well, but annoying to get the habit of moving out of the way for the first boss out of your mind

Have fun! step56.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 55

Yet another step without much details. The next 10 stages await you.

If you want to check them out, don't forget, that you can press '1' to advance to the next stage.




Since this step is somewhat short, find the level editor I'm using attached as well. Open the file "Supernatural.elementeditorproject". The editor requires the charset and spriteset files in the same folder as the editor project.

The editor is a quick hack job and therefore a bit buggy. Save often. It worked good enough for Soulless though
If you add new stages and want to test them, go to the export tab, chose the proper location for the export file (level_data.asm) and press "Export".
A simple recompile with C64Studio will incorporate the new stages.

Careful, do not have elements going outside the screen boundaries!   step55.zip

Previous Step Next Step

Endurion

Endurion

 

A C64 Game - Step 54

A direly needed step, bug fixing. Usually if I encounter a bug and for whatever reason can't fix it right away I'll note it down (it's just too easy to forget). This step fixes:

Spawn count bug fixed (a stage would not be detected as done)
Major slowdown when playing with Sam (bad bad bug)
Car sprite error in story page (failure to properly reset)
Left over char N in mode choice (tiny oversight)
Other player spawing although no lives left (failure of proper two player testing)

Most of these bugs are trivial and so it's not much use going into details here.


It can't be stressed enough. Often I catch myself encountering a bug while playing and just thinking "I'll fix that later". Well, in a lot of cases I forget. If you write a game, test play it, encounter a bug: Break the game, fix the bug. It'll move you forward faster. step54.zip Previous Step Next Step

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!