Sunday, July 7, 2019

Big and Tall

One of the things that can use up a lot of your CPU time on the NES is metasprite rendering.  Each hardware sprite is 8x8 pixels (or optionally 8x16). Complicated characters are then made up of a bunch of different sprites together, which is often referred to as a metasprite

Rendering a metasprite isn't complicated -- you push X, Y, tile, and color values into the right spot for the first sprite, then read the next sprite, adjust X and Y appropriately, and repeat.  The problem is twofold. First, it's just a lot of instructions on the 6502 to read, add, and write the sprites, when there's a lot going on onscreen.  Second, you have to deal with clipping.

Sprites can be positioned from pixel 0 to 256 horizontally, which is represented in a single byte. If you wrap around from 256 to 0, the sprite will appear on the left side. So for a large metasprite that's near the edge of the screen, you have two options.  First is to hide the object entirely if any of the sprites go off the edge. This is faster, but looks bad -- the character pops onto or off of the screen. A lot of old nes games do that, and you get used to it as a player, but it looks cheesy.  The second is do check each single sprite as you're computing its position. If a sprite's X value wraps around from 256 to 0 (or vice versa), you omit drawing that sprite. That gives a much better appearance of smoothly scrolling off the edge of the screen.  Unfortunately, it's that much more math you're doing for each sprite in the game.  Since enemies are often made of at least 4 sprites, and often 8 or more, that can become cumbersome very quickly.

In Halcyon, I wasn't happy with the popping-off-the-edge solution, so I opted for taking the time to check each sprite. To try to keep it easier on the CPU time, I have a number of ugly optimizations that make the wrapping/clipping checks a bit faster.  Unfortunately, one side effect of those optimizations is that my metasprites can only be 32 pixels wide or tall.

That was fine until this week, when I wanted to make a tall barrier that opens when you defeat a boss.  It needed to be more than 32 pixels tall.

That ugly thing in the middle is an "enemy" wall that can raise/lower depending on what other enemies are alive. The graphics are just placeholders.


That was tonight's challenge -- how to work around my optimizations and allow larger enemies?

The answer ended up being fairly simple: I still limit each metasprite to 32x32 pixels, but I allow a metasprite to specify a new base position and start a new metasprite from there.  So really, a metasprite can be more of a meta-metasprite, being made of a few different metasprites at different offsets.

Ugly?  You bet.  But it works.

No comments:

Scrolling Camera Tweaks

One of the subtle things that can really impact how a scrolling platformer feels is how the camera works. Does the character stay centered, ...