Jump to content

Karl's Blog

  • entries
    18
  • comments
    43
  • views
    5,283

Finishing Up (Minikernel Developer's Guide)


Karl G

1,159 views

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.

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:

blogentry-48311-0-17051100-1541095146_thumb.png

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 = aux2p1lives = 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.


Setting NUSIZx

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 3


The 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 NUSIZ1


Here is the new version of the minikernel with our changes:

p0lives = aux2p1lives = aux6minikernel    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    rtslives_nusiz    .byte 0    .byte 0    .byte 1    .byte 3



Modifying cannons.bas

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.

cannons.bas

Compile the new version of the game, and launch it:

blogentry-48311-0-52902900-1541096842_thumb.png

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 NUSIZ0


This 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 NUSIZ0


Similarly for player 1, we have:

    ldx p1lives    lda lives_nusiz,x    sta NUSIZ1


This 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 NUSIZ1


After compiling the changes, the zero lives condition is properly displayed at the end of a game:

blogentry-48311-0-88770300-1541099597_thumb.png

Consistent Height

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 player0height


We 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 register


Assembler 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    endif


Unflipping 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 COLUBK 


We 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 REFP1


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

cannons.bas

2playerlives.asm

  • Like 1

6 Comments


Recommended Comments

Thanks. I'm now playing around with the titlescreen to fix that. I think I have gotten as far as I can with the game. Learned a lot of stuff along the way as well.

Link to comment

Karl G,

 

I have the error 

 

text12a.asm (20): error: Label mismatch...
 --> minikernel f54d                  

Unrecoverable error(s) in pass, aborting assembly!
Complete.
Errors were encountered during assembly.
2600 Basic compilation completed.

 

I noticed with the example program above once I added the below caused the issue. Well that is my assumption of the error. 

   inline text12b.asm

   inline text12a.asm

Link to comment

You can't have two minikernels in the same project,  and even if the two were hacked together, it would end up using too many display lines. The text minikernel uses a lot already on its own, which is why I have an option to make the score use less lines to compensate. If you are going to use the text minikernel, I would stick with pfscore bars for lives, since this does not add further to the visible lines. 

Link to comment

Thanks for explanation. I was thinking that maybe an issue (limitations), but having the multisprite kernel with the options you provided does make for some possible games. 

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...