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

About this blog

Musings of a hobbyist

Entries in this blog

 

The Week of Awesome II - Post Mortem

Post Mortem to "Building Blocks"



So I entered the Week of Awesome II competition last week. I simply love this tight deadline competitions. And I couldn't help it again.
The theme came up as "The Toys are alive". Yay.

Usually I don't jump into coding right away to think about the game to be. And as usual I always go for the nearest idea. It's very rare that I try to outwit the theme and do something really creative. And so it was decided, a simple Lemmings style game, with tin soldiers walking about building blocks.

The Good

Level design

The common start is to think of how to keep the level in memory. The base of the level is tile based. I did think of bigger blocks right away, and this worked out fine after a few tries. This led me to the next point pretty fast, the


Level editor



Esp. for these competitions, when I go level based, I need a way to churn out levels very fast. Since this competition lasted for a week I went out of my way to make a decent editor. It was a bit more work in the beginning, but helped me tremendously during the last two days to play with level design and add some more stages. I wouldn't have managed 10 stages without it.


Base Library

For Ludum Dare we have to provide the full source. This makes me hesitate to actually use my home grown engine and I work with a very simple cut down framework. For the GameDev compo I can use my full framework and it does help quite a lot. I've got game assets, GUI and game states from the start, and especially, I'm comfortable with it.


Generated Music

Previous to the last Ludum Dare I was on the search for music generators once more. I encountered cgMusic, which made some really nice songs. It's a bit piano centric, and I feel that some song parts to repeat, but it was good enough for me. Adding a midi to ogg converter I could generate songs in a few minutes.


Early Preview

As suggested I put up an early version. And it was a good idea. I had some very valuable feedback, as there were a few things I didn't even think of. I think I did add all suggestions.


The Bad

Collision system

The sloped tiles were made out as a first issue to tackle. For some reason I thought using polygons would work out nice. Since I have a math library with polygon collision code I wrote polygon creators for the different blocks. Collision with a unit's bounds rectangle (=polygon) worked very nicely.
However problems appeared when I actually implemented the movement system. I had my units move in pixel units. Obviously mixing pixel based movement with "exact" mathematics (think of the slope of a diagonal block) does not work out. Units fell one pixel, ended up inside the block polygon and were stuck.

I ended up adding a simple hack: return the pixel based height of a tile depending on the x position in the tile. Ugly, but worked.


Ugly bug

When I had the first preview out someone mentioned that the game crashes on exit. I despise things like that (Gamemaker games, I'm looking at you). In the end the real bug was a bug in my level block handling. I managed to access out of bounds memory and as usual this crashed at a totally unrelated position. I had the luck that some code changes led to the bug appearing right away after the wrong access. It took a few seconds to fix, but I noticed that due to that bug some stages had broken data saved.

I added a simple hack: On loading levels I auto fixed these broken parts.



The Ugly

Windows 7 auto volume adjustment

Windows 7 does some automatic volume adjusting. If there's only very low volume music, it turns it up. But if the music suddenly gets louder it gets adjusted down. I do understand the reasoning behind this feature, but it doesn't lend itself to gaming too well. I wish there was a way for my game to tell Windows to not mess with my volume.

This feature is really really annoying. I'm not entirely sure how to work around it beside starting to play a loud sound effect at the start.



TL;DR

As final thought I'm pretty happy how the game turned out. It's not my best, but also not my worst. I wish I had more stages, as the first 5 are simple tutorials. At least the last two stages are a bit of a puzzle though.


Soooo, when does the next compo start?

Endurion

Endurion

 

First step explained in detail

As threatened the first step detailed. I reckon that the first step is overwhelming if it's your first voyage into C64 game programming. There's quite a few assumptions made about the viewer's knowledge and the later steps won't get exactly easier.

A note about the !zone macro. ACME allows for global and local labels. A local label is starting with a . and is only visible inside a zone. This allows for easier reuse of common names like loop or retry.

This snippet tells the ACME cross compiler to assemble the result into the file "jmain.prg" with the cbm (Commodore Business Machines) type. This basically boils down to the well known .prg format which contains a word with the loading address at the start followed by the assembly. ;compile to this filename !to "jmain.prg",cbm   The next snipped just defines a constant. I try to use them throughout so you can understand where I'm putting bytes at. The value 52224 is the address of the screen buffer, where 25 lines a 40 characters are stored continously. This is not the default memory location for the screen, a part of this base code relocates the screen. ;define constants here ;address of the screen buffer SCREEN_CHAR = 52224   Now a very interesting piece which took me longer to work out than it should have. A C64 has two types of files, Basic files and machine code files. A Basic file can be started by RUN, a machine code file just contains the code and usually must be jumped at with the SYS command. Any half decent game will provide a proper Basic kick start that jumps directly at the machine code.

To allow for this we set the file start address to $801 (2049), the default Basic start. The file content starts out with the tokenized bytes of a simple Basic line calling SYS for us. The line is built by a word containing the address of the next Basic line. Following is a word with the line number (10 in our sample). After that the token for the SYS command ($9e) followed by a space ($20) and the ASCII representation of the target address (2064 in our sample). After that there is one zero byte marking the end of the line. The next zero word represents the end of the Basic file. I've got some extra zero bytes which are actually wrong but also don't really hurt. ;this creates a basic start *=$801 ;SYS 2064 !byte $0C,$8,$0A,$00,$9E,$20,$32,$30,$36,$34,$00,$00,$00,$00,$00   The next snippet disables any visible sprites, relocates the VICs memory bank (resulting in a relocated screen buffer and charset address). ;init sprite registers ;no visible sprites lda #0 sta VIC_SPRITE_ENABLE ;set charset lda #$3c sta VIC_MEMORY_CONTROL ;VIC bank lda CIA_PRA and #$fc sta CIA_PRA   This piece is the main game loop. It's rather easy, we increase the border color (resulting in flashing), increase the top left character on the screen, wait for the vertical blank (not exactly but to the effect) and rerun the loop. ;the main game loop GameLoop ;border flashing inc VIC_BORDER_COLOR ;top left char inc SCREEN_CHAR jsr WaitFrame jmp GameLoop   This snippet is quite interesting. The C64 allows you to read the current raster line on the screen that is currently being redrawn. The code checks for a certain raster position at the bottom of the screen to sync the game to the computer's display speed.

In detail we're waiting for the raster line to NOT be the position we want to wait for. Once we are on any line but the wanted we now really wait for our raster line to appear. This avoids the problem when the routine is called too fast in succession and we end up on the same raster line. !zone WaitFrame ;wait for the raster to reach line $f8 ;this is keeping our timing stable ;are we on line $F8 already? if so, wait for the next full screen ;prevents mistimings if called too fast WaitFrame lda $d012 cmp #$F8 beq WaitFrame ;wait for the raster to reach line $f8 (should be closer to the start of this line this way) .WaitStep2 lda $d012 cmp #$F8 bne .WaitStep2 rts
step1.zip

Previous Step 1 Next Step 2

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

 

Adventurous Adventure

I'm currently attending yet another C64 game creation competition: Forum64.de's Adventure Competition 2015.

I always wondered how the guys/gals at Lucasfilm Games managed to cram Maniac Mansion and Zak McKracken onto a few small disks. So this competition was the perfect opportunity to test drive my ideas.


How they did it

Fortunately today the internet provides all kind of technical info at my finger tips. So it's no problem finding some of the old programmers spilling their beans. They actually created a Lisp like language that allowed them scripting even with a kind of multi threading. Incredible! Obviously the script was compiled to byte code which barely fit into memory. So that's the way I chose to follow.
Fun link of the day: Ron Gilbert on Scumm (http://grumpygamer.com/scumm_notes)


How I faked it

Tables. Tables with pointers to more tables. All verbs are represented by indices; so are items and static objects (items on screen)
Every item/object has a list of verbs to scripts. Item 5 with verb 7? Run this script. No entry for object 14 and verb 9? Show the dreaded "That doesn't work."

And there's action areas with type "room exit" or "script trigger". Room exits are handled directly while script triggers start their attached script.


Where's the beef?

It's in the script! All puzzles are handled by various script actions. A script action can set/clear flags, run a different script depending on a flag state. Characters can be "walked", get or lose an item, branch if a certain combination of items/objects is used, if the controlled character is anybody specific, etc.

This script together with rooms made up of elements (of which some may depend on certain flag states) makes for a quite small memory footprint. At the current state there's 65 flags, 39 rooms, 55 static objects and 18 items making up a total of 30355 bytes (graphics included).

Since that's all pretty abstract, here's a few excerpts.


Action table for a static object (badge reader in elevator)
SOA_BADGE_BUTTON_IN_LIFT !byte PA_EXAMINE, CS_EXAMINE_BADGE_BUTTON !byte PA_LAST_ENTRY_FLAG | PA_USE, CS_USE_BADGE_BUTTON_IN_LIFT
Starts a sequence when a button is used in the elevator, referenced in the table above

CS_USE_BADGE_BUTTON_IN_LIFT !byte CSA_SET_HAPPEN,2 !byte CSA_REDRAW_SCREEN !byte CSA_WAIT,50 !byte CSA_IF_HAPPEN_SET,4,CS_ACCIDENT !byte CSA_CLEAR_HAPPEN,2 !byte CSA_TOGGLE_HAPPEN,3 !byte CSA_LAST_ENTRY_FLAG | CSA_REDRAW_SCREEN

Endurion

Endurion

 

A c64 game in several steps (lots of 'em)

Welcome!
Today's development is heaps and bounds beyond imagination from 20 years ago. I've always had a soft spot for the C64 after all this years. So I sat down and tried to start assembly programming on a C64.

Today I'll start with a sort of tutorial on how to write a C64 game. I have prepared 36 steps for now, planned are probably a few more.

I'll start out very small but there will be bigger steps later on. The code is supposed to be heavily commented but is probably not clear for everyone. I'll be happy to answer questions regarding the code. The code is written for the ACME cross compiler, which allows to compile the code on any bigger OS.


Step #1 is a simple base for a game. It provides a Basic start (10 SYS 2064), sets up the VIC relocation and shows a simple synchronized game loop.

To show the loop running the border color is flashed and the top left char is rotating throughout all characters.

The not too eye-popping result looks like this:


Find here the source code and binary for use in an emulator of your choice (I recommend WinVICE):
step1.zip

Next Step 1b

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 9

What are enemies if they just sit put and don't move at all? Therefore we now add the sub routine ObjectControl. ObjectControl loops through all objects (even the player) and jumps to the behaviour function depending on the object type. This incurs that the behaviour is tied to the object type. We provide a table with function pointers to every object's behaviour code (including the player).

ObjectControl takes the object type as index into the table and jumps to the target address. For now we have two enemy types, dumb moving up/down or left/right. For moving we reuse the previously created functions we already use for the player, namely ObjectMoveLeft/ObjectMoveRight etc.

Loop over all active objects and jump at their behaviour code. Note that we apply a nasty trick. Since jsr doesn't allow for indirect jumps we manually push the return address on the stack and then call indirect jmp. This allows for the behaviour code to return with rts.
  ;------------------------------------------------------------ ;Enemy Behaviour ;------------------------------------------------------------ !zone ObjectControl ObjectControl ldx #0 .ObjectLoop ldy SPRITE_ACTIVE,x beq .NextObject ;enemy is active dey lda ENEMY_BEHAVIOUR_TABLE_LO,y sta ZEROPAGE_POINTER_2 lda ENEMY_BEHAVIOUR_TABLE_HI,y sta ZEROPAGE_POINTER_2 + 1 ;set up return address for rts lda #>( .NextObject - 1 ) pha lda #<( .NextObject - 1 ) pha jmp (ZEROPAGE_POINTER_2) .NextObject inx cpx #8 bne .ObjectLoop rts   The main game loop is now changed; removed the call of PlayerControl and added the call to ObjectControl: ;------------------------------------------------------------ ;the main game loop ;------------------------------------------------------------ GameLoop jsr WaitFrame jsr ObjectControl jmp GameLoop   The behaviour table is built from the behaviour code addresses. Actually we use two tables for high and low byte, this way we don't have to mess with the index. The < and > operators return the low and high byte of a 16 bit value.
  ENEMY_BEHAVIOUR_TABLE_LO !byte <PlayerControl !byte <BehaviourDumbEnemyLR !byte <BehaviourDumbEnemyUD ENEMY_BEHAVIOUR_TABLE_HI !byte >PlayerControl !byte >BehaviourDumbEnemyLR !byte >BehaviourDumbEnemyUD
step9.zip



Previous Step 8 Next Step 10

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

  • 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!