06. Sprites
Last updated
Last updated
What’s a sprite? A sprite is a tile that can be moved freely all over the screen. Sprites are usually 8×8, but they can also be 8×16 (a little more complicated). I will be using 8×8 examples. Sprites are defined by the 256 bytes in the OAM part of the PPU. There are 64 sprites. That’s 4 bytes per sprite.
But 8Ă—8 is so small. How do we make Mario so big? We combine multiple sprites to move together on the screen. This is called a metasprite. Look below. Small Mario is made up of 4 sprites, and large Mario is made up of 8 sprites.
Sprites on the NES have an annoying limitation. 8 sprites per horizontal line. That’s all you get. Any more than that, and then next sprite will disappear. The order of the sprites in the OAM determines which 8 will show and which disappear. First in the OAM (the 0th sprite) has top priority. It will show up in front of the others and will count first toward the 8 sprite limit.
You might have seen sprites flickering in NES games. To avoid disappearing sprites, it is common to rotate the order of the sprites in the OAM, so that the sprite that disappears alternates, creating flickering. That’s better than an invisible sprite, I suppose.
Another oddity, is that sprites are always shifted down 1 pixel. If you put a sprite at Y = 0, the top of the sprite won’t appear until the next line down. Look at Mario’s feet, and you see that he is 1 pixel into the floor. This might look ok for platform games, but a top down game might look better with sprites aligned to the background. We can do this easily by shifting the BG down 1 pixel.
Sprites can go anywhere on the screen. However, they are not very good at moving smoothly off the left side of the screen. There is an option (PPU Mask 2001 bits xxxx x11x), that if zero, you turn off the left 8 pixels of the screen, and THEN you can smoothly move off the left side of the screen.
Any sprite Y position >= $ef is off the screen. When you call the function oam_clear() or oam_hide_rest(), it puts the sprites Y at $ff, which is below the screen. Sprites don’t wrap around.
So… there are 4 bytes for sprites. Y, tile #, attributes, and X.
Attributes (copied from the wiki)
So how do we make sprites appear? Like writing to the background, you can only write to the sprites during v-blank, which is handled by neslib in the nmi code. The standard way to do this, is to set aside a 256 byte buffer, aligned exactly to xx00. The picture above is at $700, but neslib usually uses $200 (defined in crt0.s as OAM_BUF). The nmi code will do a quick OAM DMA and copy all the sprites from the buffer to the OAM.
Since we are using a buffer, you should be able to write to the buffer at any time. I prefer to clear the buffer every frame, and rebuild it from scratch every frame.
oam_clear(); //Clear the sprite buffer.
sprid = 0; //Set the index to the buffer to zero.
sprid = oam_spr(x,y,tile,attribute,old sprid); //Push 1 sprite to the buffer.
sprid = oam_meta_spr(x,y,sprid,*data); //Push 1 metasprite to the buffer.
NOTE: I changed all this 9/17/2019, and removed sprid from my code to speed the functions up a bit. Now it looks like this.
oam_clear(); //Clear the sprite buffer.
oam_spr(x,y,tile,attribute); //Push 1 sprite to the buffer.
oam_meta_spr(x,y,*data); //Push 1 metasprite to the buffer.
Fewer variables pushed to the c stack = faster.
Also added were these functions, just in case you need to access the sprid.
oam_set(); // manually set the index to the sprite buffer
oam_get(); // returns SPRID, the current index to the sprite buffer
And to make it all work, I had to add an internal variable in crt0.s called SPRID.
When I made the graphics file, I put the sprite graphics in the second half. We have to remember to tell neslib that we want to use the second half for sprites…
bank_spr(1);
And make sure to define a palette for both BG and sprites.
Ok, so, how do I make a metasprite? NES Screen Tool has a tool for making metasprites. I find it a bit difficult to use. Sometimes I just copy and paste a definition from a same sized metasprite (and change the tile #s). But, if you use NES Screen Tool, you can “put single metasprite to clipboard as C” and then paste it into the code. Then you can pass that to the oam_meta_spr() function.
oam_meta_spr(x,y, * data).
The sprite definitions used by neslib (and NES Screen Tool) are out of order. It goes x,y,tile,attribute, as opposed to the NES’s actual byte order (y,tile,attribute,x). Keep that in mind, if you want to just retype it by hand, like I sometimes do.
If you want to have a metasprite that changes direction (and flips horizontally), then you should make 2 separate metasprites, one for each direction.
One limitation. None of these functions keep track of how many sprites are in the buffer. You could easily put in too many, and overwrite the ones in the beginning of the buffer.
This example uses 1 basic sprite and 2 metasprites, and moves then down 1 pixel per frame.
https://github.com/nesdoug/07_Sprites/blob/master/Sprites.c
https://github.com/nesdoug/07_Sprites
Oh, one more thing. If you are transitioning from one part of a game to another, and you turn off the screen, make sure you clear the sprites before you turn the screen back on so you don’t have 1 frame of junk sprites left on the screen.