Finishing Up (Minikernel Developer's Guide)
Now that we have a minikernel that can display a life icon for each player and be positioned correctly, we need to display the correct number of lives for each player. We will do this by setting the NUSIZx register for each player to a value that displays the correct number of copies of the player object that corresponds to their remaining number of lives. Before we do that, we need to talk about minikernel variables.
Most minikernels will need some variables that will hold their value from frame to frame without getting overwritten by the display kernel or the user. In our case, we will want to keep track of the number of lives for each player. The documentation says that aux3 through aux6 are reserved strictly for minikernel use, but this isn't really true, as you can see looking at the standard kernel memory map:
While our sample game doesn't use pfheights or pfcolors, and won't use pfscore bars at the end, we will still avoid those variables to leave them free for projects that do make use of these options. Since we only need two variables, we will use aux2 and aux6.
To make it easier, we will give these two variables another name in our minikernel, which will make our code more readable. Place the following at the top of the minikernel before the "minikernel" label:
p0lives = aux2 p1lives = aux6
Note: Our minikernel in its current form does not need any temporary variables, but if we needed them, we could use temp1 through temp7, and kernelpointers through kernelpointers+5. These can also be given alternate names for readability as we are doing for aux2 and aux6.
We will next need to set the appropriate NUSIZx register to the correct value for each player object to the appropriate value to display 1, 2 or 3 lives. A NUSIZx value of 0 will display one copy of the player object, a NUSIZx value of 1 will display two copies, close spacing, and a NUSIZx value of 3 will display 3 copies, close spacing. There is no way to display 0 copies with a NUSIZx register, so that case will be handled separately later.
We will create a lookup table that gives the appropriate NUSIZx based on the number of lives, as follows:
lives_nusiz .byte 0 .byte 0 .byte 1 .byte 3The lives for each player will be used as an offset for the data, so the first entry will be used for 0 lives, the second value will be used for 1 life, etc. To do this, we load the appropriate lives variable into an index register, and use it as an offset when reading from our lives_nusiz table. The Y register is already in use as a loop counter, so we will load it in X instead:
ldx p0lives lda lives_nusiz,x sta NUSIZ0 ldx p1lives lda lives_nusiz,x sta NUSIZ1Here is the new version of the minikernel with our changes:
p0lives = aux2 p1lives = aux6 minikernel ldy player0height lda #0 sta NUSIZ0 sta NUSIZ1 sta COLUBK sta WSYNC ; Start a new scanline at cycle 0 sleep 21 ; Wait 21 cycles sta RESP0 ; Set player 0 position to current location sleep 34 sta RESP1 lda #$40 sta HMP0 lda #$60 sta HMP1 sta WSYNC sta HMOVE ldx p0lives lda lives_nusiz,x sta NUSIZ0 ldx p1lives lda lives_nusiz,x sta NUSIZ1 GraphicsLoop sta WSYNC lda (player0pointer),y sta GRP0 lda (player1pointer),y sta GRP1 dey bpl GraphicsLoop sta WSYNC lda #0 sta GRP0 sta GRP1 rts lives_nusiz .byte 0 .byte 0 .byte 1 .byte 3
The cannons game will need to be modified as well to make use of these changes. Previously, the game used pfscore bars to track lives, so the variables pfscore1 and pfscore2 were used to track lives. Furthermore, the lives were decremented by dividing by for (bit-shifting two places). Instead now we will be using the p0lives and p1lives variables, initializing each of them to 3, and decrementing lives by subtracting 1 each time. Here is the modified version of cannons.bas with these changes.
Compile the new version of the game, and launch it:
Success! All 3 life icons appear as expected. Some experimentation shows that the life icons decrement as expected as well, with the exception of zero icons being displayed if zero lives are left.
Displaying Zero Lives
There is no NUSIZx value that displays no copies of the player graphics, so we need another method to display no icons if the number of lives is zero. Since we have already explicitly set the background color to black, we can set the appropriate COLUPx register to black to hide the life icon in this case. First we load the appropriate lives variable into a register, and then skip the code that changes the color to black if the value is not zero. If not skipped, we set the color to black. Repeat for the other player.
Since we already load the p0lives and p1lives into register X, we can put the code to detect that case right after that code. E.g. for player 0 right now we have:
ldx p0lives lda lives_nusiz,x sta NUSIZ0This will be changed to:
ldx p0lives bne ____skip_hide_p0_icon ; goto the ____skip_hide_icon label if p0lives = 0 stx COLUP0 ; Set the color of p0 to black to match the background color ____skip_hide_p0_icon lda lives_nusiz,x sta NUSIZ0Similarly for player 1, we have:
ldx p1lives lda lives_nusiz,x sta NUSIZ1This will be changed to:
ldx p1lives bne ____skip_hide_p1_icon ; goto the ____skip_hide_p1_icon label if p1lives = 0 stx COLUP1 ; Set the color of p0 to black to match the background color ____skip_hide_p1_icon lda lives_nusiz,x sta NUSIZ1After compiling the changes, the zero lives condition is properly displayed at the end of a game:
If you have played around with the cannons game, you may have noticed that it exposes an issue with this minikernel: if player 0 is hit, the minikernel area for both players shrinks temporarily. This is because our minikernel uses the value of the player0height variable as a loop counter, and the cannons game decreases the value of this variable when the player is hit to show the cannon vanish.
How can we fix this? One way would be to compare player0height to player1height, and use whichever is greater for our loop counter instead. Right now we have:
ldy player0heightWe want to compare the value in player0height to player1height, and use whichever is greater. First, we will load player0height into register A first (the accumulator is used for comparisons like this), then we will compare to player1height, skipping some code to use its value instead if it is greater:
lda player0height cmp player1height ; compare player0height to player1height bcs ____skip_use_player1height ; goto ____skip_use_player1height if not greater lda player1height ____skip_use_player1height tay ; transfer result to the Y registerAssembler Directives and Maximum Height
We don't need it for the cannons game, but some games may need the lives icons to only show part of the player graphics if they are above a certain height. We can add some code to allow the user to add a const in their bB code that sets a maximum height for the lives icons, e.g. "const lives_max_height=8". We can check for a constant being defined by using the assembler directive "ifconst". This isn't an actual CPU instruction, but something that tells the assembler to only assemble the code following it if a symbol is defined (a constant in bB in this case).
Adding the following code below the code we added in the last step will use this constant only if it is defined. Since the height is still in the accumulator at this point, we can skip a step and compare the defined maximum height to the value already in the accumulator:
ifconst lives_max_height cmp #lives_max_height bcc ____skip_max_height ; Skip if the max height is taller than the player graphics ldy #lives_max_height ; Use lives_max_height instead ____skip_max_height endifUnflipping Player Graphics
The cannons game doesn't use the REFPx registers to flip the direction of the players sprites, but some games using this minikernel may do so. We will want to set the REFP0 and REFP1 registers to zero to avoid having the lives icons getting flipped as well. We could do this anywhere before the GraphicsLoop label, but we will add it to where we are zeroing out other registers to save a step. Right now we have:
lda #0 sta NUSIZ0 sta NUSIZ1 sta COLUBKWe can add to this to use the same zero value in register A to clear out the REFPx registers:
sta NUSIZ0 sta NUSIZ1 sta COLUBK sta REFP0 sta REFP1Summary and Final Code
We used a lookup data table to convert player lives to the appropriate NUSIZx value to display the correct number if life icons. We set the color of the appropriate graphics to zero to hide it in the case of zero lives. We added some code to use the taller of the player graphics to set our loop, and added some code to optionally allow the user to set a maximum height. Finally, we cleared out the REFPx registers to our life icons would not be flipped if used in a project that flips the player graphics.
I am attaching the final versions of the code for the cannons game as well as the minikernel. I hope this has been useful!
- RevEng likes this