Spritework on the ZX Spectrum: Preparing Our Graphics | Bumbershoot Software
It’s finally time to properly harness the power of the ZX Spectrum’s graphics hardware. We’ve looked at what BASIC offers us, we’ve seen how the system ROM simulates a text mode for us, and we’ve implemented graphics primitives on the underlying bitmap ourselves. What we have not done is created a software package that will let us define small rectangles of graphics and then put them on the screen with pixel-level precision.
Put simply: the ZX Spectrum does not have any sprite graphics capability, but this did not stop the game developers of its time, it does not stop the developers targeting it today, and by the end of this article, it is not going to stop us.
My personal Rosetta Stone for spritework for awhile has been a simple shooting gallery program. We’ll be using its particular needs to guide our engine, and making note of the places where that leaves gaps.
It’s also going to be a quite large project. This week turns out to be almost entirely design and prep work, though that’s still going to involve writing a good chunk of code.
Bob’s Your Uncle: All-Software Sprites
The Spectrum may have no dedicated sprite hardware, but as I discussed in my article about distinct sprite traditions, there’s nothing magical about sprites—they’re merely a hardware-accelerated graphics compositing mechanism. Without hardware support, but with a bitmap, we can make do by simply doing that compositing ourselves—hardware sprites are replaced with "bitmap objects," or simply "bobs." On the Spectrum, we’ll face two difficulties along the way: we have a sharply limited budget of CPU time each frame to do the work of the graphics updates, and we’re fully constrained by the restrictions of the overall bitmap display. The attribute clash situation in games with scrolling backgrounds or lots of free-moving animation ends up being grim enough that a common solution was just to make the entire playfield monochrome:
(Image credit: MobyGames)
Even here it’s not entirely monochrome: there are bands of color where the configuration stays consistent but they don’t have to take up the whole screen, and for basically fixed displays like the status window at the bottom, they can still go all-out. Applying those principles to the shooting gallery is how we got the mockup that’s guiding our design:
We aren’t doing anything special here yet: the shots, targets, and blaster trucks are all being drawn with user-defined graphics characters. As very basic prep work, I switch from using RST $10 to direct memory copies to put them into place. Copying a character’s worth of graphics from HL to DE ends up looking like this:
ld b,8<br>lp: ld a,(hl)<br>ld (de),a<br>inc hl<br>inc d<br>djnz lp
One nice part of keeping the playfield colors fixed is that we end up not having to update color memory at all, which hopefully will also save us some CPU time later on.
This solution still isn’t enough, though; it’s laying down full characters at a time and is functionally equivalent to a more-unlimited form of text-style user-defined graphics. We want to take a shape and draw it at any pixel position. That means dealing with unaligned graphics, and our approach varies depending on dimension:
Drawing vertically-unaligned graphics means dealing with the fact that INC D won’t suffice to move down a row. Happily, any logic sophisticated enough to deal with this also can deal with bobs of any height whatsoever.
Drawing horizontally-unaligned graphics obliges us to bit-shift the component graphics and blend the graphic into the existing display, much as we did for our single pixel in the implementation of our PLOT function.
Bitshifting is pretty slow, though, and we’ll be doing this a lot. We can make our lives easier during the heat of the action by precomputing all the offsets of our graphics and then just picking the correctly-aligned version at render time. Once that is done the composition will look like copying out an 8-bit-aligned but slightly wider graphic. As long as we’re doing that, we also might as well handle things like horizontal and vertical mirroring during this precomputation phase too.
We get one more simplification: for the shooting gallery program, we happen to know that sprites will always be drawn on even pixel coordinates. That means we’ll only need three unaligned versions of each of our major graphics instead of seven:
These precomputed sprites take up quite a lot of space. I have only three 16×16 graphics here and the library of shifted graphics takes up over half a kilobyte of RAM. Fortunately, even 16KB of RAM is roomy enough that this won’t slow us down, but once we start moving to more sophisticated games, the RAM crunch will become real and it will probably be necessary to swap back and forth between which sprites are truly ready to go immediately.
The rest of this article will focus on what it takes to precompute the graphics data we’ll need for rapid...