Sprites are the dynamic objects that make our game interesting. They are the player you control and interact with, as well as the enemies you defeat. Let’s add a sprite to our game.
If you would like to follow along in my git repository, download or clone the following repository:
Then to see the code for this tutorial specifically, checkout the branch tutorial-4:
git checkout -b tutorial-4 remotes/origin/tutorial-4
Drawing the Sprite
Sprites, much like the background tiles, are also made up of 8x8 pixel tiles. Because this isn’t very much space, characters like Mario or Mega Man are actually made up of multiple sprites that move together in unison. We will use YY-CHR again to create a spaceship graphic, much the same way that we created our background graphics:
While the spaceship that we added into our sprite sheet looks fine if we’re viewing the sprite sheet itself, where we placed the graphics makes it difficult to locate in code. In order to make it easier to reference each tile in the spaceship from code, we can “minify” this graphic. This is recommended due to the limited graphical space we have available to us, and ideally we shouldn’t be wasting any. With YY-CHR we can copy single or multiple tiles and move them to other locations, like so:
Go ahead and save the sprite sheet. Because we already included this file earlier when we added background graphics, we don’t have to do anything further with this file.
Adding Sprite Data
Now, much like we did with the background graphics, we need to create the data that represents our new spaceship graphic. Create a new file called
graphics/sprites.asm and add the following data:
spritePlayer: .db $80, $0A, $00, $80 .db $80, $0B, $00, $88 .db $80, $0C, $00, $90 .db $88, $0D, $00, $80 .db $88, $0E, $00, $88 .db $88, $0F, $00, $90
Keep in mind that the spaceship we created consists of a total of 6 tiles. In the data above, each row represents a single tile. For each tile, there are four bytes of data, in the following order:
- Vertical screen position (in pixels)
- Graphical tile (hex value of the tile in the sprite sheet)
- Attributes (represents multiple things, including the color palette)
- Horizontal screen position (in pixels)
Let’s make some better sense of the vertical and horizontal screen positions. These are just the initial positions, and can be modified later. This will become important when we want to actually move this thing around on the screen. Because we know that each tile is an 8 pixel square, we can initially set them up 8 pixels apart. Placing the ship near $80 both vertically and horizontally will place it near the center of the screen. Sprites within a few pixels of $FF will end up off the screen.
The second byte in the row is the graphical tile, which corresponds exactly to the tile in our sprite sheet. This is done the same way we set up our background data.
Finally, the attribute byte is a little more complicated. We have used the value $00 here, but this byte can be more easily represented in binary because each bit has a purpose. Let’s use 76543210 and see what each bit does:
- Bits 0 and 1 are for the color palette
- Bits 2, 3 and 4 are not used
- Bit 5 is the priority (0 shows the sprite in front of the background, and 1 displays the sprite behind it)
- Bit 6 flips the sprite horizontally (0 is normal, 1 is flipped)
- Bit 7 flips the sprite vertically (0 is normal, 1 is flipped)
For example, if our sprite attribute byte was %10100011 our sprite would be flipped vertically, behind the background, and using the fourth color palette.
Having the ability to flip the sprite is important because it reduces the amount of graphical data we require. Our spaceship only needs to be drawn facing a single direction in the sprite sheet. If we decide in our game that instead of just facing up we want our spaceship to face downward, we can flip all the sprites vertically by changing that bit in the attribute bytes.
Loading the Sprites
Much like we did for all the other data, let’s include our sprites data file in bank 1 where we put the rest of the data:
sprites: .include "graphics/sprites.asm"
Let’s create a method to load the sprites:
LoadSprites: LDX #$00 .Loop: LDA sprites, x STA $0300, x INX CPX #$18 BNE .Loop RTS
The difference with sprite data is that we are not putting the sprites into the PPU here. We are storing each sprite into a RAM address, starting at $0300. We loop #$18 times (24 in decimal) because we have that many bytes in our sprite data. Once we have added this method, we need to call it from the RESET method:
In order to display the sprites, we have to send them to the PPU, but the reason we didn’t do that while loading them is because we have to do that during the NMI. Recall that the NMI stands for non-maskable interrupt and it gets called once per frame. Also, this is exciting because this is the first time we are adding something to the NMI:
NMI: LDA #$00 STA $2003 LDA #$03 STA $4014 RTI
Our NMI code now looks like this. So what the heck does that stuff do? This is transferring our entire sprite memory on the CPU to the PPU using direct memory access. We are loading both the low byte and the high byte to get the address $0300 (which is where our sprites live). Once we store this in memory address $4014 we are accessing the PPU and starting the transfer. We will then transfer everything on the CPU from address $0300 to $03FF (256 bytes).
Also, let’s go ahead and change the color palette for our sprite:
.db $0F,$21,$15,$30, $0F,$0F,$0F,$0F, $0F,$0F,$0F,$0F, $0F,$0F,$0F,$0F .db $0F,$10,$17,$07, $0F,$0F,$0F,$0F, $0F,$0F,$0F,$0F, $0F,$0F,$0F,$0F
We only modified the first four bytes of the second row. This should make the spaceship stand out a little more. At this point, we should be able to assemble and start our game. It should look something like this:
With the direct memory access, we can only send 256 bytes of sprite data to the PPU. Because each sprite requires 4 bytes of data, this limits the Nintendo to only being able to display 64 sprites on the screen at a time. Our spaceship takes up 6 sprites of that total. Try to imagine an NES game like Super Mario Bros. Mario takes up 4 sprites, but if he eats a mushroom to become larger he takes up 8 sprites. Add to that some enemies like Koopas and Goombas, a few coins and a mushroom and you’re probably getting close to 64 sprites already.
If that didn’t make it hard enough, only 8 sprites can be displayed per scanline. A scanline is a horizontal line all the way across the screen. If we attempt to draw more than 8 sprites in a horizontal line, the 9th sprite and beyond will simply not get drawn. Mega Man is usually 3 sprites wide, taking up about half this amount already with just the main character. Sprite flicker is a mechanic that was added on purpose for some games like Mega Man to allow you to see more sprites on the screen at the tradeoff of graphical flashing.
In the next tutorial we will learn how to use a controller to move that frozen spaceship around on the screen.