rss feed of the blog sol stuff discord follow sol email sol Sol::Speccy

Speccy, Vol 7: SolarGun

January 23rd, 2016

As I mentioned in the previous blog post, I made a simple shmup for the zx spectrum. You can play it here on an online emulator (link depending on the online emulator and dropbox still existing, so if you're reading this on 2025, good luck on that). The .tap file that you can use with an emulator or actual hardware can be found here.

After I had decided to start working on a simple shmup, I went to IRC and said that I need name for a cheesy shmup, stat. Pekuja said SolarGun, which sounded good enough, so I ran with it.

To make the game I used pretty much all of the stuff I've built for the speccy so far - sprite routines, Image Spectrumizer for the logo, Mackarel to package the result. Code was compiled using SDCC (small device c compiler), along with z88dk, although I think the only part of z88dk I actually used is the .tap to .wav conversion to test with actual hardware.

For sound effects, I found a neat sfx editor called BeepFX by Shiru. Unfortunately even the shortest sound effects (by modern standards) were so long that they take more than a frame of rendering time. I reduced the couple of effects I used to a bare minimum, which is kinda sad, because the BeepFX is able to generate pretty nice sounds. Maybe for a turn based game? Hm.

Here's a video of some gameplay. I didn't try for a highscore =)

The game itself is pretty simple. Asteroids fly at you at increasing pace, shooting an asteroid gives you 10 points, dodging an asteroid gives you one point. You can sustain three asteroid hits with no problem, fourth one is game over time.

Back when I was doing first tests, I found that I could, basically, memcpy 64 scanlines worth of stuff to the screen in a frame (without doing anything else). That's 2kB of data. I've learned more efficient ways of moving stuff around since, but it's still a good yardstick (especially since the emulator I was using runs z80 code a bit too fast compared to actual speccy).

So since we can only copy 1/3 of the screen in a frame, double buffering is pretty much out. To make it look like we're scrolling a big image and have lots of sprites on the screen at the same time, I cheated a bit.

The game's logo (which took all of five minutes on Photoshop and Image Spectrumizer) takes 1/3 of the screen. The way I cheated with rendering, I could have used the whole screen, but this way the play area is 128 pixels high, which helped with the player's ship math (I could do 7.1 fixed point, basically).

The scrolling background is copied to the screen interlaced, 13 scanlines in a frame (so we draw the whole background every 10 frames or so). The enemy sprites are drawn once every three frames - or in other words, on each frame, we update 1/3 of the enemy sprites. The sprites are never cleared, instead the background draw takes care of that. This also creates a kind of motion blur effect on the sprites.

This also meant that I didn't need to worry about display beam racing. The sprites still blink just a little bit when they are written by CPU and read by ULA at the same time, but there's so much noise going on that you barely notice it.

Not only are the enemies drawn every third frame, their physics is also evaluated only on every third frame. This has the additional benefit of giving more accuracy to the movement - the enemies can move 1/3 of a pixel per frame without having to use fixed point math.

The player's ship - the focus point of player's attention - is drawn on every frame.

So in practise you're playing three separate games running at third of a full framerate at the same time, which is pretty convincingly 50Hz.

I timed things so that when you have the full set of sprites on screen we're still running at 50Hz, but whenever sound effect plays, it pushes the timing over the frame. The sound effects are pretty rare, though.

I use color attributes for a slight "vignette" effect, with darker colors on the edges. This is partially because I didn't want to handle sprite clipping with edges, and partially because otherwise things would have been (even more) monochromatic.

The attributes are also used for "explosions" (drawing a red blob around where an asteroid is cleared) and the player's shots. The player's gun is a slowly recharging mega-beam which slices through everything. There's probably a bug with the hit detection because the beam doesn't always wipe out asteroids it should.

Whenever either of those attribute effects are used, the next 16 frames are used to clear the attributes back to the original state, 1/16 at a time. This also creates the nice "blider" effect on the beam.

Gun charge meter and user's lives are also done with simple attribute changes.

The score.. Oh right. Printing out the score is a surprising problem - you see, there's no division (or modulo) opcode on the z80, so if we wanted to simply sprintf the score, that would probably eat significant amount of our frame time.

Enter binary coded decimal, a numeric encoding format so common that there's often special opcodes in various CPUs for it. Simply put, for each hex nibble we store a value from 0 to 9, and the rest (a-f) are illegal. If a nibble's value is over 9, we add 6 to the value, to roll it back to the legal range. In case of overflow, the next digit is incremented by one, and then we have to check if that is in the legal range, etc.

While relatively complicated (depending on how many digits you want to handle), it's still way cheaper than dividing by 10 a few times. The z80 also has an opcode for doing all this - DAA (decimal adjust after addition). Instead of using that, I wrote the same kind of logic in C. Probably a huge waste, but fast enough for this project.

Looking at the Mackarel memory maps, the game takes quite a lot of the memory, but there would still be plenty of room for more stuff if wanted:

Memory : 0       2       4       6       8       10      12      14      16
On load: rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr

On boot: rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr

Key    : r)om s)creen b)asic L)ow block C)ode block
         H)igh block .)unused -)reserved

The low block (L) contains the game logo. It is simply copied to video memory when the game starts. If there were more than one type of enemy at once, I could store the sprite data for various levels there. The memory below the 32k limit is considered "slow", because it is contended, and thus it's preferable to keep all code and data we want to play with above that limit. As a recap from earlier blog posts, accessing contended memory while the screen is drawing incurs at an additional speed penalty.

There's also some room above the code block (C), meaning that there's still room to add code before I'd have to start worrying about size optimization.

As before, all of the sources are available on github.

Site design & Copyright © 2022 Jari Komppa
Possibly modified around: June 23 2016