18. Sprite Zero
Last updated
Last updated
This one is a bit hard to explain. But SMB used it, so most people consider it a standard game element. When you have the top of the screen not scrolling, but the bottom of the screen scrolling, it’s probably a sprite zero screen split.
The sprite zero, is just the first Sprite in the OAM… addresses 0-3. The PPU marks a flag (register $2002) the first time it sees the a non-transparent pixel of the zero sprite over a non-transparent pixel of the BG. We can look for this flag, and use it to time a mid-screen event, like changing the X scroll.
Waiting for this flag is wasted CPU time. It would be better to use a mapper generated IRQ, like MMC3 / MMC5 scanline counter or Konami VRC 2,4, or 6 scanline counter. Better, because the cartridge would count for you, and you don’t need to poll 2002 repeatedly for changes, and you can do multiple splits per frame.
But, I decided not to cover IRQs in this tutorial. You would need to learn ASM to use those, since IRQ code needs to be written in ASM.
Anyway. once it hits, you know the PPU is on a specific line on the screen, and you can change the scroll position.
Just writing to 2005 twice midscreen can only change the X scroll. This is just how the NES PPU works. You just can’t change the Y scroll by writing to the scroll register mid frame. But, you can change the X scroll. So, I changed the split function to only take 1 argument, X, to better reflect this limitation.
I want the top left screen to be the HUD, so we keep the regular scroll stuck at 0,0. When I set the regular set_scroll_x(0) and set_scroll_y(0), I just keep them at zero.
Then, first thing we do on each frame is poll for Sprite zero right away. We don’t want to miss it! It would crash the game. Send it the actual X. It will adjust the scroll mid screen.
split(scroll_x);
We had to adjust the screen drawing code not to overwrite the top of the screen. Now we have a stable HUD that we can draw our stats to.
https://github.com/nesdoug/21_Sprite_Zero/blob/master/sprite_zero.c
https://github.com/nesdoug/21_Sprite_Zero
.
And, just to contradict myself, we actually can change the Y scroll midscreen with a complicated 2006 2005 2005 2006 trick. I’m doing this last (the bottom of the screen), and it’s more dangerous, because running past the end of the frame before running this function, and the whole game could crash, if it never finds the Sprite zero hit.
But, I wanted to show that it was possible, even if you should never use it. Perhaps for someone braver than me. Perhaps for the top of the screen in a vertically scrolling game.
Anyway, I made this function…
xy_split(x,y);
I had to make some changes to the platformer, since the entire screen is now aligned higher, so I had to adjust the y values of bg collisions. this is probably not the ideal setup, but I just wanted to demonstrate it. I’m putting the top of the screen at the bottom of the screen.
Here’s the example. I guess I should have used the vertical scrolling code to show this better. This could have been done with the regular split code.
https://github.com/nesdoug/22_XY_Split/blob/master/xy_split.c
https://github.com/nesdoug/22_XY_Split
.
And I was so worried about slowdown causing scrolling errors in the game, that I didn’t end up using the sprite zero hit in the final game, coming up later. These examples work fine, because there is not much game logic, but as soon as we add a few enemies and move them around and have to check collisions, the logic goes longer than 1 frame, and slowdown causes scolling errors on every 2nd frame.
It should be noted that Mojon Twins (na_th_an) uses this same sprite zero split in many of his games, and told me just refactor if scrolling problems happen. Split long tasks into 2 different frames. Check only half the collisions a frame, for example.