Instead the code became an exercise in game sprite graphics. I wanted to have three huge-ish characters on screen with a smooth framerate, with a game that would again float somewhere near the Saboteur!, Bruce Lee and Dan Dare territory.
I decided on XORed sprites, so I don't have to do masks. XORing graphics means that the underlying pixels are inverted with the sprite pattern. It's a nice method in that with XOR the sprite drawing and erasing routines are exactly the same, because re-drawing on the same position leaves the screen as it was before drawing.
Granted, it can look a mess whenever the sprites overlap. Many complained about colour clash in ZX Spectrum games, but I guess XOR was partly to blame, too. Atic Atac is maybe the best game that uses the XOR approach, and it's very fast.
Early days. Using sprites adapted from Fort Django |
Another weird technique is to draw the sprites when entering the frame and erasing them on the way out. With a Commodore 64 you could simply check for the suitable scanline, but with a ZX Spectrum this is not possible. I'll simply have to ensure that each frame still does the same things even if there are no enemies on screen. This also means the game is fixed for the 3.5MHz timings. Such an approach is not very elegant nor portable, but it's justified to get a silky-smooth game in a closed environment that the 8-bit computer is.
The sprites, actually
Although XORing is a fast technique, I still had to revise my approach a bit. My initial target of 40x64 sprites with smooth framerate were clearly out, so I went for 40x48 instead, which is the size of the Saboteur human characters.
I can't even have true 40x48 sprites, but by doubling the vertical pixel size it is possible. Not only are the pixels doubled, but the underlying vertical screen resolution needs to be divided by two to make the XOR draw/redraw logic work. So I'm working with 256x96 screen with 1x2 proportioned pixels.
Drawing one "line" of the sprite data as 1x2 pixels. |
The sprite graphics are drawn from left to right, zig-zagging the two lines. The stack is pointed to a table that has the vertical screen addresses sorted out for every other pixel row coordinate, and these addresses are pop'ed for each sprite line. The horizontal component of the address needs to be added, too.
1x2 pixelled sprite data pictured in GIMP, with one data line highlighted. |
Other good things came out of the 1x2 pixels: Instead of needing 256 bytes for each sprite frame, I could fit a graphic frame inside 128 byte boundaries. The 40x64 sprites would not have fit in a 256-byte boundary anyway.
The pixel ratio is not that limiting, as the sprites can be drawn deal with it rather than stubbornly make something that does not fit. My current sprites are hardly the pinnacle of ZX graphics, but it looks promising. And of course the screen portions where the sprites are not drawn, can be in 1x1 pixel format.
Toying around with some gfx pretty much ripped from Dan Dare. |
I could use a portion of the screen for a dashboard, as in the image above, and these 16 lines might not matter. I am making a game after all, and not a generic sprite routine.
But then the silly me realized that if the sprites are drawn in a certain order, the scanline intrusion can be made nearly meaningless: If the first sprite to be drawn is at the top of the screen, the second at the middle of the screen and the third in the bottom of the screen, all happens smoothly by "racing the beam". I only have to take care that not all sprites move near the top or bottom at the same time.
The diagram below shows what happens during one frame. These are not based on actual values, the picture is exaggerated for clarity. In this example only the second sprite is truly both a: drawn before the scanline enters the pixel drawing area, and b: erased after the scanline leaves the pixel drawing area.
This brings certain limitations to what can be performed with the sprites in the game. For example, only the player character has full freedom to move all around the screen, whereas the other sprites would stay within invisible "cages". Yet these cages are so lax that by switching sprite positions these cages do not matter much.
Technically, it would even be possible to draw more sprites than the three, if the sprite drawing order is well managed. This is somewhat equivalent of multiplexing sprites on computers that have real sprites and good scanline routines. But I felt this might become a bit too complex programming exercise or limit the "cages" a bit too much.
So, there are now three "big sprites" and the player sprite is always the second sprite to be drawn. The game, whatever it might be like, has to be designed around the small limitation that the enemies can't go everywhere.
Animation frames were another source of trouble. I wanted to save frames by "baking in" the leg motion inside the shifted frames. But this produced too fast animation. So instead of four shifted frames there would be eight sprite frames for simply making the guy walk on screen. The eight frames make a total of 1K graphics laid out in 128-byte address boundaries.
What else? I didn't make a clipping routine, even if it's not too slow to check the coordinates and divert to another sprite drawing routine. I wanted to negotiate some programming time out of this project.
Dude, where's my game?
So, after making the to-hell-with-portability sprite engine, I could concentrate on the game. All I've made since the sprite routines is a tile-drawing/collision routine, a bit more joystick stuff and experimenting with ways to draw unobtrusive objects. It looks nice, but currently a bit limited for a proper game. I'll get back to this in some form, but it doesn't seem to happen anytime soon.