• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
  • entries
    104
  • comments
    101
  • views
    252457

About this blog

Musings of a hobbyist

Entries in this blog

Endurion
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
Post Mortem to "Building Blocks"

shot5.png

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

shot6.png

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
And thus this create a game series ends...

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

step100.png

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

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

Thank you for your encouragements throughout, and keep on coding ;)



Previous Step
Endurion
And of course lots of little bugs were found and fixed smile.png

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



Previous Step Next Step
Endurion
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 smile.png

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.



Previous Step Next Step
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.

step97.png

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 smile.png;------------------------------------------------------------;simply move diagonal;------------------------------------------------------------!zone BehaviourBatDiagonalBehaviourBatDiagonal 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 1PATH_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


Previous Step Next Step
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.

step96.png

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+ /code] 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 DisplaySfxModeDisplaySfxMode 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.



Previous Step Next Step
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.

step95.png


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 smile.png


Previous Step Next Step
Endurion
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.

step94.png

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 smile.png


Previous Step Next Step
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 smile.png

step93.png

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!



Previous Step Next Step
Endurion

A C64 Game - Step 92

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

step92.png

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 KillEnemyKillEnemy ;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!


Previous Step Next Step
Endurion

A C64 Game - Step 91

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


step91.png


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 smile.png


Previous Step Next Step
Endurion
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.

step90.png


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



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


step89.png


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 BehaviourBoss7BehaviourBoss7BOSS_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 rtsFinalAttack ;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!


Previous Step Next Step
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.

step88.png

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 HandleFinalBossIntroHandleFinalBossIntro 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 CheckIsPlayerCollidingWithYPosHCheckIsPlayerCollidingWithYPosH 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 CheckIsPlayerCollidingWithDiagonalLLURCheckIsPlayerCollidingWithDiagonalLLUR 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 CheckIsPlayerCollidingWithDiagonalULLRCheckIsPlayerCollidingWithDiagonalULLR 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 BehaviourBoss7BehaviourBoss7BOSS_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 DrawBeamDiagonalLLURDrawBeamDiagonalLLUR.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 RestoreBeamDiagonalLLURRestoreBeamDiagonalLLUR.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 DrawBeamDiagonalULLRDrawBeamDiagonalULLR.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 RestoreBeamDiagonalULLRRestoreBeamDiagonalULLR.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 BehaviourBossHelperBehaviourBossHelper 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!

Previous Step Next Step
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 smile.png



step86.png


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 0MOVE_STONE_POS_BACK !byte 0MOVE_STONE_POS_FRONT !byte 0

...and the actual code.STONE_BACK_DISTANCE = 20STONE_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



Previous Step Next Step
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.


step83.png


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 .EnemyKilled


Have fun!


Previous Step Next Step
Endurion

A C64 Game - Step 81

A new pickup! And it shines smile.png

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

step81.png

The changes are quite simple. In the PickupItem routine we add a handler for those new items: cmp #ITEM_SUPER_BULLET beq .EffectSuperBulletWe make sure only Dean can use it and increase the super bullet counter:



.EffectSuperBullet cpx #1 beq .SamDoesNotUseBullets inc SUPER_BULLETThe 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_COLORand 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 .EnemyKilledDone!

BTW, the super bullet also works on bosses!


Previous Step Next Step
Endurion

A C64 Game - Step 80

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

step80.png

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

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

Notice that we use a method which is not possible that easy in todays code: We have a local code loop until the door is completely opened.
Since there are no events to be handled, messages to be retrieved or other OS specific code required, we can do that.




;------------------------------------------------------------;open door animation;------------------------------------------------------------!zone DoorAnimDoorAnim lda #0 sta LEVEL_DONE_DELAY.DoorAnimLoop jsr WaitFrame inc LEVEL_DONE_DELAY lda LEVEL_DONE_DELAY and #$07 bne .DoorAnimLoop ;open door (16,11) lda #11 sta PARAM2 - ldy PARAM2 lda SCREEN_LINE_OFFSET_TABLE_LO,y sta ZEROPAGE_POINTER_1 lda SCREEN_LINE_OFFSET_TABLE_HI,y sta ZEROPAGE_POINTER_1 + 1 ldy #17 lda (ZEROPAGE_POINTER_1),y dey sta (ZEROPAGE_POINTER_1),y ldy #18 lda (ZEROPAGE_POINTER_1),y dey sta (ZEROPAGE_POINTER_1),y ldy #19 lda (ZEROPAGE_POINTER_1),y dey sta (ZEROPAGE_POINTER_1),y lda #32 ldy #19 sta (ZEROPAGE_POINTER_1),y ldy #22 lda (ZEROPAGE_POINTER_1),y iny sta (ZEROPAGE_POINTER_1),y ldy #21 lda (ZEROPAGE_POINTER_1),y iny sta (ZEROPAGE_POINTER_1),y ldy #20 lda (ZEROPAGE_POINTER_1),y iny sta (ZEROPAGE_POINTER_1),y lda #32 ldy #20 sta (ZEROPAGE_POINTER_1),y inc PARAM2 lda PARAM2 cmp #21 bne - ; lda LEVEL_DONE_DELAY lsr lsr lsr cmp #4 bne .DoorAnimLoop ;door is fully open now- jsr WaitFrame inc LEVEL_DONE_DELAY lda LEVEL_DONE_DELAY cmp #200 bne - rts


Also, since I'm going on vacation this tutorial takes a break until next year. Thanks for your support so far, have fun!


Previous Step Next Step
Endurion

A C64 Game - Step 79

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

step79.png

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

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

The ProcessSpawnSpots routine is enhanced thusly:


lda NUMBER_ENEMIES_ALIVE
ora NUMBER_SPAWN_SPOTS_ALIVE
bne .NoDelayedSpawnSpots

lda NUMBER_DELAYED_SPAWN_SPOTS_ALIVE
beq .NoDelayedSpawnSpots

;undelay them now
lda SPAWN_SPOT_LEVEL
clc
adc #16
sta SPAWN_SPOT_LEVEL

;check all spots
ldx #0
-
lda SPAWN_SPOT_ACTIVE,x
beq +

lda SPAWN_SPOT_SPAWN_COUNT,x
and #$f0
cmp SPAWN_SPOT_LEVEL
bne +

;undelay now
lda SPAWN_SPOT_SPAWN_COUNT,x
and #$0f
sta SPAWN_SPOT_SPAWN_COUNT,x

dec NUMBER_DELAYED_SPAWN_SPOTS_ALIVE
inc NUMBER_SPAWN_SPOTS_ALIVE

+
inx
cpx #SPAWN_SPOT_COUNT
bne -
.NoDelayedSpawnSpots


Obviously in the spawn spot update loop we need to skip any spawn spots where the upper four bits are set:


lda SPAWN_SPOT_SPAWN_COUNT,x
and #$f0
bne .NextSpawnSpot


Also, during level buildup we need to increment the correct counter depending on the upper four bits again:


;count
iny
lda (ZEROPAGE_POINTER_1),y
sta SPAWN_SPOT_SPAWN_COUNT,x

;upper 4 bits set? then it's a delayed spawn spot!
and #$f0
bne +
inc NUMBER_SPAWN_SPOTS_ALIVE
jmp ++
+
inc NUMBER_DELAYED_SPAWN_SPOTS_ALIVE
++


Have fun surviving!


Previous Step Next Step
Endurion

A C64 Game - Step 78

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

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

The aforementioned animation is a simple xor call on the sprite pointer:


lda SPRITE_POINTER_BASE,x
eor #1
sta SPRITE_POINTER_BASE,x


step78.png


Previous Step Next Step