You may have encountered this concept in the NESmaker UI, various online tutorials or retro explaining videos: Sprite Zero. What is it, what would you need it for and how would you detect such a hit? Although this is “just an intro,” we’re diving pretty deep into this concept, how to implement it and it’s caveats. We even do a sidestep into interrupt requests for a different mapper. Do you have about 7 spare minutes? Strap in then, because we’re about to hit… sprite zero!
What is sprite zero?
Sprite zero is called that, because it’s literally the first sprite loaded in the sprite RAM section of the PPU, a.k.a. the sprite loaded in slot zero. The sixth bit of the PPU status bit (address $2002) is the flag that shows whether or not sprite zero has been, or is being, rendered yet. During frame rendering, when the game draws sprite zero, and it’s on top of a non-transparent background color, the PPU changes this bit from 0 to 1 (apart from some caveats which the NESdev Wiki explains perfectly).
Why or when would you need it?
One of the most practical uses for this is drawing a static HUD on the top of a scrolling screen. Because you can now reliably know where you are in the frame rendering process, you can intercept the scroll and/or nametable offset and draw part of a different screen at that specific moment. This video by Retro Game Mechanics Explained shows perfectly what I mean exactly.
Why is it disabled/unimplemented since NESmaker 4.5?
You’ll find sprite zero settings in the NESmaker UI, but the core scripts no longer handle these values. Mainly because users had trouble understanding the concept behind sprite zero hit detection and couldn’t get it to work the way they want to – if they even know how they wanted it to work. This is why the default background HUD has been replaced with the sprite HUD concept for scrolling modules in NESmaker.
Another reason is the probable cause of lag frames. The concept of sprite zero hit detection is kinda like a kid in the backseat of a car on a road trip, yelling “are we there yet?” every second. The game has to keep checking and is basically idle until we are “there yet,” and can’t really do any other game stuff like collision detection or object updates. And since the core NESmaker modules – especially the scrolling ones – have prioritized the ease of use through its UI and the generalization of (re)usable assembly code over speed optimization, these are prone to slowdown as is, which only gets worse if we have to wait for 32-40 scanlines to start handling these scripts at all. Although we could try and give it some leverage by “doing game stuff” before the sprite zero hit, but you’ll have to make absolutely sure you don’t miss it, or you’ll get lag frames with a bonus side of flicker and jitter, so that may be a risky endeavor.
Example implementation of sprite zero hit detection
Still, if you’re feeling lucky, you could try and play around with the following snippet! This script re-enables the sprite zero detection values from the NESmaker UI and repositions the screen before and after drawing the HUD. Ths snippet is meant to illustrate how sprite zero hit detection may be implemented in NESmaker; what you want to do when the hit happens is up to you. Just make sure you draw the first sprite over a non-transparent background, or that hit will never happen and your game will freeze up. Also, I’d suggest putting any type of sprite zero hit detection script at the end of your non-maskable interrupt, to prevent flickery jitter screens when the game drops frames before reaching the sprite zero check.
LDA #CHECK_SPRITE_ZERO ; First we need to check if sprite zero hit detection is BNE +doneSpriteZero ; enabled. If not, we skip the check altogether. LDA gameHandler ; If we're currently rendering a whole new screen, we AND #%10000000 ; skip the check as well. BNE +doneSpriteZero ; LDA SpriteRam+0 ; Finally, we check if the sprite in slot 0 actually is CMP #SPRITE_ZERO_Y ; in the Y-position we expect it to be. If not, we skip BNE +doneSpriteZero ; the check as well. ; If we're here, it's sprite zero time! LDA #%10010000 ; First, we overwrite the PPU control address with the STA $2000 ; nametable reset to the left one. LDA #$00 ; Also, we need to reset the scroll offset. We do this STA $2005 ; by writing zero to the PPU scroll memory address STA $2005 ; twice. ; Currently, the HUD is being rendered to the screen. -loopUntilReset: LDA $2002 ; We first load the status of the PPU in the accumulator. BVS -loopUntilReset ; Now we check if the overflow bit is set: if so, the last ; sprite zero hit has not been reset yet, so we'll need to ; need to wait until it is. We keep loading $2002 until the ; sprite zero bit is no longer set. -loopUntilHit: ; If we're here, we know the sprite zero bit has been reset. LDA $2002 ; Now we reload the PPU status into the accumulator. BVC -loopUntilHit ; Only this time, we keep checking until the sprite zero bit ; is no longer unset. ; Once we're here, sprite zero has been hit. We'll want to wait a set number of ; cycles to make sure we're in HBlank. LDX #SPRITE_ZERO_HBLANK ; This loop is essentially a small time waster, just to -loopUntilHBlank: ; wait until the beam is into its horizontal blanking DEX ; period. You can set this number in the NESmaker UI and BNE -loopUntilHBlank ; get it right through trial and error. ; We're ready to update the screen now, whether or not we have waited for sprite ; zero to hit. +doneSpriteZero: LDA camScreen ; Now, we add the correct nametable, based on the camScreen AND #%00000001 ; variable, to the default value of the PPU control address. ORA #%10010000 ; STA $2000 ; LDA camX ; Finally, we write the correct x-offset to the PPU scroll STA $2005 ; address, and reset the y-offset (unless you're also scrolling LDA #0 ; vertically, in which case you'd want to LDA camY instead of STA $2005 ; #0 here).
This should give you a solid baseline how to (re)implement sprite zero hit detection in your NESmaker project! Battling slowdown with sprite zero hit detection enabled is going to be a different story though; losing 32-40 scanlines worth of processing time is pretty significant. But…
But how does Mario 3 put its HUD at the bottom?
…Super Mario Bros. 3 has the HUD at the bottom of the screen. Does that mean that the game has to wait until the entire game scene has been drawn to the screen, wasting about 200 scanlines worth of processing time? And does the game really handle everything within that short window of time, where the game is drawing its HUD? That would be insane, if not impossible, to pull off, right?
Indeed, that would be insanity. SMB3 does not use sprite zero detection to decide when to draw the HUD, but it uses an interrupt request (IRQ) based on a scanline counter. When the game renders a certain scanline of the screen, the game sends an interrupt request, which then handles drawing the HUD.
Unfortunately, scanline counting is not something the NES can do natively. Rather, it’s a function supplied by the cartridge, which is mapper-dependent. Super Mario Brothers 3 uses the MMC3 mapper which has this function; NESmaker, by default, produces Mapper 30 (UNROM512) roms which have no scanline counter. And, while you can change the mapper NESmaker uses by altering the header.asm file (many developers have done this to make their games Mapper 2 (UxROM) compatible), every mapper has its own memory address references, bank switching routines, implementation quirks and more, so you can’t just plop in a MMC3 header and expect things to work. And if you want to go physical with your game, that’ll open a whole different can of worms: each mapper has its own set of compatible chips, which may not be cheap or even available at all since we’re talking 40+ year-old hardware here. Although that’s a rabbit hole I have kept away from, and since I’ve digressed from the original topic way too much, I’ll just keep it at that for now.
Wrapping up
Long story short: sprite zero hit detection is easy to implement, but harder to pull off correctly in terms of timing, slowdown and getting the scroll to work as intended. There are other ways to pull off mid-frame scrolling, but those are too far away from NESmaker so I didn’t go too much into those here. If anything, it’s a fun thing to play around with in practice projects, just to see what it does and to get more acquainted with the NES and its hardware. Have fun with that zero sprite and let me know if you have pulled off anything with it.
I may come back to this topic again when I have more hands-on experience with sprite zero hit detection in NESmaker. Until then, see you next time!