The end is in sight! We are now adding the final touches to the game to make it feel better to the player and add some form of customization to the screen colors. The player interface with the game is very important and can make or break the experience.
So now we introduce some sound fixes, input modifications and graphic options.
As usual, the code can be found on my GitHub page for you to see and play with.
The music and sound effects were played at the end of the game play loop, and because of that we were having issues with the timing of the audio. As the time required to run the game loop can change each frame the sound would slow down sometimes. Highly annoying.
So a raster interrupt was introduced to run BEFORE the game loop to avoid any interference. This setup route is called when the game starts:
I stole this routine from the Kick Assembler example files, and adapted it slightly. Instead of changing the IRQ vector at $FFFE, I use the vector in $0314, as I want kernal IRQs to continue. We are depending on those for keyboard scans, etc.
The above screenshot shows the moment when the various program parts are called. The top white border color change is when the music plays and the bottom color change is when the game code runs. All screen updates are done in the game update code, as you know, while the raster bar is at the bottom of the screen so we will have no flicker in the screen buildup or strange artifacts. This was already the case.
Also, to stop sound effects from overlapping and blocking each other out, a timer was needed to introduce a delay before a new sound can be played. I added a table with the delays for each sound. Playsound can be called when a sound effect or music is required:
// set accumulator before calling this
// it will not play when the sounddelayCounter is not 0
// 0 1 2 3 4 5 6 7 8 9
The sounddelayCounter value is changed in the play loop in play.asm, once per frame. It counts down to zero:
Simple as that.
Some people have good taste, and like the color combination I've put in. Some may want to wander from perfection and change the colors. This can now be done by pressing F3 (screen colors) and F5 (character colors)
This is achieved by scanning for these key presses in the main loop.
Changing the screen color is easy:
Changing the character color is a bit more involved: only changing the color RAM is not enough. When you print something to the screen, this is done in the current cursor color and has nothing to do with the screen RAM values. We need to change the cursor color to the correct value and we can to that by printing an ASCII code.
The ASCII table of the C64 is a bit weird, but there is a table in the Programmers' Reference Guide. These ASCII values don't line up AT ALL with the colour values that are in the color RAM, so we need a little table to map it to the color RAM values:
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
When the color RAM is changed, the appropriate CHR$ is also printed:
sta $d800,x // store in color ram
inx // increment counter
bne !loop- // continue?
lda chrColorCodes,x // get correct chr$ code
The keyboard input was a bit wonky (as was the joystick) so changes were needed to make it feel as it should. There is nothing worse than a game that does not react in the way you expect. I moved the keyboard and joystick scan to the main loop and removed all scanning from the game modes. This means that the keyboard and joystick are read ONCE per frame resulting in stable results.
This helps stability of the read input as reading the keyboard buffer can only be done once each frame. Any subsequent query will result in no input detected. Not good.
This is the new keyboard scanning code:
lda keyPressed // get held key code
cmp previousKey // is it a different key than before?
bne !skip+ // yes. dont use key delay
// key is the same. update delay counter
// restore key delay counter
// save key code for next update
lda #NOINPUT // yes
// if we press down, the delay is shorter
sta inputResult // store input result
This also solves an issue when pressing DOWN. The times between the down movement were changing all the time, now it is stable. Adding a little section to have a shorter delay when pressing DOWN makes a manual drop behave like it should.
To make the time delays between inputs more stable, I also added the same structure and a separate delay counter for the joystick so there is no interference.
The hardest part of game development is finishing the project and while this series has been a long time in the making, I was determined to finish it and THEN move on to other challenges.
For me, this has been a great exploration of the Commodore 64. I hope you enjoyed it as well, and that you now have a better understanding of game development on the C64, with assembly. A lot of things in the C64 we did not even talk about, like sprites, multi-colour, raster interrupts, scrolling, border manipulation etc. but these are all things that were not necessary for this game. It would be great to play with that in a future project though! :)
Thanks for reading and showing an interest in this great little machine that gave (and keeps giving) so much pleasure the world over.
Happy coding and see you next time!