Jump to content
IGNORED

Karl's Blog - 3) Data and Player Graphics (Minikernel Developer's G


RSS Bot

Recommended Posts

At this point, we have engaged in enough yak shaving - that is, we have covered some assembly coding and made a minikernel do different things, but haven't made visible progress towards our ultimate goal: a minikernel that will display separate life icons for two players.

In this lesson, that will change: we will cover drawing player graphics, and use that in our minikernel. Before we get to that, there's a couple more simple assembly commands I want to cover that I didn't get to in the last lesson (it was already long enough). I will then explain indexed addressing and pointers, which will be needed to load our graphics.

Register Transfer

These are simple instructions that copy the contents of one register to another. Note that there are no instructions to directly copy from X to Y or vice versa, however:

TAX (Transfer A to X): Transfer the contents of the A register to the X register.
TXA (Transfer X to A): Transfer the contents of the X register to the A register.
TAY (Transfer A to Y): Transfer the contents of the A register to the Y register.
TYA (Transfer Y to A): Transfer the contents of the Y register to the A register.


JMP, JSR, and RTS

JMP (JuMP): Go to the specified address/label
JSR (Jump to SubRoutine): Go to the specified address/label, and store the return address in the stack.
RTS (ReTurn from Subroutine): Go back to the address stored in the stack.
JMP is fully equivalent to a goto, JSR is fully equivalent to a gosub, and RTS is fully equivalent to a return statement in batari Basic.

Note - There is no way to specify a destination bank with a JMP or JSR command. Bankswitching is a more advanced topic that we will not cover in this guide.


Batari Basic Code:

 

Equivalent assembly code:
 
Defining and Accessing Data

Defining data can be done by marking the beginning with a label, and defining the bytes. It can be in decimal, hex, or binary just like in bB, and either multiple bytes defined per line separated by commas, or one byte per line.

 

As mentioned in the previous lesson, the X and Y registers are referred to the index registers, as they can be used as an offset to an address when loading and saving values.

 
Comparison to batari Basic

Here's an example of data being defined and accessed in bB vs how we would do the same in assembly code:

batari Basic Code:
 
Equivalent assembly code:
 

Pointers and Player Data

Finally we can talk about drawing player objects to display lives. One way to do it would be to define data for each of the player objects, and then access that in our minikernel to draw the player lives. However, we already defined the player graphics in our batari Basic code, so it may make more sense to just use that instead of repeating the same data.

So, where is this data stored? The answer is not quite as simple as you might think. In a batari Basic program, you can change the player graphics as often as you want. Since this is stored in ROM, and ROM can't change, it stands to reason that each version of the player graphics is stored separately as data. How does the program know which copy of the player graphics to use?

This is where pointers come in handy. Pointers are two adjacent bytes of RAM that store a memory address. When you read from a pointer, you read from the address that is stored in the pointer instead of what is stored in the pointer variables themselves.

In the case of a game with player graphics that change (e.g. for animation frames), each of the animation frames would be stored as data, and two bytes are set aside to store the address of the current frame of the animation. Every time the player graphics changes, the pointer is updated to contain the address of the new frame.

bB uses pointers for player graphics "under the hood" as well. For our purposes right now, I am just going to cover how to access this data in order to use it in our minikernel.

player0pointer and player1pointer are the names of the pointer variables bB uses to store the current player graphics. The player0height and player1height variables contain the heights of the current player graphics. Here is an example of how we would access this data:


Note - When accessing static data, the X and Y registers can be used interchangeably as indexes, but when accessing data via pointers, they have different functions. I will not cover the form of pointer data access that uses the X register, as it is much less commonly used.


Displaying Player Graphics

We have covered how to access player graphics, but how do we display them? There are two TIA registers for this purpose: GRP0 and GRP1 that hold the player 0 and player 1 graphics, respectively. Note that each of these registers only hold 1 byte of data, enough to display 1 scanline of graphics. A simple example of code to display a player from static data might look something like this:


 

Back to Our Minikernel

Now that we know how to display player graphics from the existing pointers in bB, let's try putting these in our minikernel. Everything we have done up to this point has just been for demonstration purposes, and won't be part of our final minikernel. Let's discard that, and leave just this:

 
We have the label that the bB kernel goes to (via a JSR command) and the command that returns back to the bB kernel at the end (RTS). We will add our new code in between these.

First, we need a loop counter. As previously mentioned, bB stores the height of each of the player graphics in variables: player0height and player1height. For purposes of this minikernel, we will assume that both player graphics are the same height.

We will first load player0height into Y:
 
Now, let's add a loop label again; maybe we will call this one GraphicsLoop, and start with a "sta WSYNC" again at the start of the loop to start at the beginning of a scanline:

 
We can then add the commands to load data from the bB-defined player graphics pointers, and store that data in the player graphics registers:

 
Finally, we decrement Y, and branch back to GraphicsLoop as long as Y is 0 or above:

 
Putting it all together, we now have this:

 
We can put that in out minikernel file, save it, recompile cannons.bas and launch it in Stella:



What do we see now? The good news is that the player graphics do display in the minikernel area. However, there are a few oddities:
  • The minikernel graphics are "squished" compared to in the game itself.
  • The background color carries over into the minikernel area.
  • There are graphic artifacts at the top of the screen above each player.
  • The minikernel graphics stay in the same horizontal position as the players, even as they move.
For the first issue, you may recall from the first lesson that each line of graphics in a batari Basic screen is drawn using two scanlines, whereas some minikernels use one scanline per line of graphics. We have just created such a minikernel!
In our case, we have a simple solution for the squished-looking graphics. In out cannons game, we set the players to double-width via the NUSIZx registers, and this carries over to the minikernel. This is easily fixed by resetting each of these to 0 before our loop. While we're at it, we can also take care of the second issue by setting the background color to 0 as well:


Let's save, recompile, and take a look:



Much better! Now how about the graphic artifacts at the top of the screen?

When the player graphics registers (GRP0 and GRP1) have any value other than 0, they will keep displaying their contents on every visible scanline until the contents are cleared. To fix this, we will zero out these after our loop. We will put a "sta WSYNC" first so that the last scanline has a chance to display before the graphics registers get cleared out:
 
Let's see how it looks with our changes after we recompile again:



All fixed! Now, how about the horizontal position issue? That is a more complex topic that we will cover in the next lesson.

Here is our minikernel source as it should look at the end of this lesson:


Note - While it is not needed for our sample game, in many cases you will also want to clear out the REFPx registers as you did the NUSIZx registers, otherwise our minikernel graphics will get flipped anytime the player graphics are flipped.



Summary and Next Lesson

Data can be defined in a way that is similar to what you are probably used to in batari Basic. We can access data by loading it using the label at the start of the data with an offset that is stored in one of the index registers (X or Y). Pointers are two adjacent bytes in memory that store an address, such as player graphics data. Pointers are useful because they can be updated as needed, such as pointing to different frames of animation for player graphics data.


The GRP0 and GRP1 registers store one byte of graphics data for player0 and player1, respectively, which is enough for a single scanline. Player graphics are displayed by loading and displaying graphics data one scanline at a time.

In our next lesson, we will cover scanline timing, and horizontal positioning of graphics.
Attached thumbnail(s)
  • blogentry-48311-0-04774700-1536672288_th
  • blogentry-48311-0-56240900-1536692479_th
  • blogentry-48311-0-69587500-1536692680_th


http://atariage.com/forums/blog/741/entry-15032-3-data-and-player-graphics-minikernel-developers-guide/
Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
  • Recently Browsing   0 members

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