Jump to content

Karl's Blog

  • entries
  • comments
  • views

3) Data and Player Graphics (Minikernel Developer's Guide)

Karl G


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 (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:
   goto CoolNewPlace   .   .   .CoolNewPlace   (more code)   gosub MySubroutine   .   .   .MySubroutine   (more code)   return

Equivalent assembly code:

   jmp CoolNewPlace   .   .   .CoolNewPlace   (more code)   jsr Subroutine   .   .   .MySubroutine   (more code)   rts

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.

shoot_sound   .byte 8,8,2   .byte 1   .byte 8,8,8   .byte 1   .byte 8,8,12   .byte 1   .byte 8,8,19   .byte 1   .byte 8,8,23   .byte 1   .byte 2,8,27   .byte 4   .byte 255rainbow_fruit   .byte %00011100   .byte %00111110   .byte %00111110   .byte %01111111   .byte %01111111   .byte %00111110   .byte %00010100rainbow_fruit_colors   .byte $6A   .byte $7A   .byte $8A   .byte $CA   .byte $1A   .byte $3A   .byte $4A

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.

   ldy #0   lda my_data,y ; load first byte of my_data   ldy #5   lda my_data,y ; load 6th byte of my_data

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:

   data sound_data   12,5,24   .   .   .end   temp1=0   AUDC0=sound_data[temp1]   temp1=temp1+1   AUDV0=sound_data[temp1]   temp1=temp1+1   AUDF0=sound_data[temp1]

Equivalent assembly code:

sound_data   .byte 12,5,24   .   .   .   ldy #0   lda sound_data,y   iny   sta AUDC0   lda sound_data,y   sta AUDV0   iny   lda sound_data,y   sta AUDF0

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:

   ldy #0   lda (player0pointer),y ; Load first byte of player0 graphic into A.   ldy player1height   dey   lda (player1pointer),y ; Load last byte of player1 graphic into A.

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:

   ldy #player_height - 1PlayerLoop   sta WSYNC   lda (player0pointer),y   sta GRP0   dey   bpl PlayerLoop

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:

minikernel    rts

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:

   ldy player0height

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:

GraphicsLoop    sta WSYNC

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:

    lda (player0pointer),y    sta GRP0    lda (player1pointer),y    sta GRP1

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

    dey    bpl GraphicsLoop

Putting it all together, we now have this:

minikernel    ldy player0heightGraphicsLoop    sta WSYNC    lda (player0pointer),y    sta GRP0    lda (player1pointer),y    sta GRP1    dey    bpl GraphicsLoop    rts

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:

  1. The minikernel graphics are "squished" compared to in the game itself.
  2. The background color carries over into the minikernel area.
  3. There are graphic artifacts at the top of the screen above each player.
  4. 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:

    lda #0    sta NUSIZ0    sta NUSIZ1    sta COLUBK

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:

    sta WSYNC    lda #0    sta GRP0    sta GRP1

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:

minikernel    ldy player0height    lda #0    sta NUSIZ0    sta NUSIZ1    sta COLUBKGraphicsLoop    sta WSYNC    lda (player0pointer),y    sta GRP0    lda (player1pointer),y    sta GRP1    dey     bpl GraphicsLoop     sta WSYNC    lda #0    sta GRP0    sta GRP1     rts

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.


Recommended Comments

I've gone through the easy 6502 tutorial twice and the collect tutorial atleast twice in the past but something about this little tutorial makes everything fall into place.


When you're done with all parts it would be nice with some notes at the end of each part on what differs in the DPC+ kernel (playerpointers for example) and a counterpart example.


Thanks again!

Link to comment
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.

  • Recently Browsing   0 members

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