Jump to content
Sign in to follow this  
RSS Bot

Karl's Blog - Finishing Up (Minikernel Developer's Guide)

Recommended Posts

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:



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:


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:
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:
Here is the new version of the minikernel with our changes:
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.



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:
This will be changed to:
Similarly for player 1, we have:
This will be changed to:
After compiling the changes, the zero lives condition is properly displayed at the end of a game:



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:
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:
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:
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:
We can add to this to use the same zero value in register A to clear out the REFPx registers:
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!



Attached thumbnail(s)
  • blogentry-48311-0-17051100-1541095146_th
  • blogentry-48311-0-52902900-1541096842_th
  • blogentry-48311-0-88770300-1541099597_th
Attached File(s)


http://atariage.com/forums/blog/741/entry-15202-finishing-up-minikernel-developers-guide/

Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...