A simple arcade game for the ZX Spectrum Next - Work in progress!
The code can be compiled with make and the z88dk toolchain. It is developed using Visual Studio Code.
More details will be added here soon.
Use the mouse to point and shoot. The bolts you fire land and explode shortly later, and will dematerialise any nearby Jeff.
Do not let any Jeff reach the edge of the screen, or you will lose health and eventually the game will be over. Once a certain number of Jeff is eliminated, you move onto the next area.
Shooting uses charge which slowly replenishes over time. You can trade fire rate for charge speed (and the reverse) by using the mouse wheel.
Shoot bonuses for various benefits:
- Red plus: Replenish health
- Green plus: Replenish charge
- Black plus: Bonus points
- Yellow diamond: Smart bomb
- Blue clock: Freeze the Jeff for 5 seconds
- Black and blue rectangle: Temporary shield from damage
- Purple umbrella-like-thingy: Pause drops for a moment
- Round yellow down arrow: Slow down the Jeff temporarily
- Orange concentric circles: Extended bomb range (for a limited number of shots)
- Circle with bolt in the middle: Supergun (fast rate, no charge cost, for a limited number of shots)
If a bolt explodes especially close enough to a Jeff you get a small bonus as well.
Press P to pause during the game.
The game packs all its resources into a single NEX file. Almost all data, except the executable code and digial audio, is compressed using the zx0 compressor and decompressed at the time of use.
The resources are converted to native Next formats from the files in resources_original via the convertResource.sh script. This script performs these steps:
- Level backgrounds are converted from PNG to native Next using the
gfx2nextutility. - Generated images and palettes are compressed using the
zx0utility. - Level heightmaps are converted from PNG to via the
convertHeightmaps.swiftscript to *.hm files (which are just a raster of 8-bit brightness values). - The converted assets are then scanned by the
makeAssets.swiftscript which results in a generatedassets.asmfile with an accompanyingassets.hfile. The ASM file builds the binary blob with all the binary data, sorted to fill each memory page as much as possible, and the H file creates defines which are used in the game to refer to each resource's (paged-in) address, data length, and page. - Finally the loading screen for the NEX is converted in a slightly different native format as required by the NEX spec.
- The ULA is on top and handles the large "OSD" style messages that are displayed.
- Sprite layer handles the mouse pointer and JEFF hoardes.
- The tilemap handles the bonuses that appear in random locations.
- Layer2 is configured for "high res" 320x256 mode and used for the menus, in-game stats, and level backgrounds.
Like most z80 targetting software, the code makes heavy use of paging. This diagram may help make code and memory setup more understandable. The main strategy is:
The ROM is configured as ROM3, which is the traditional 48k Sinclair ZX Spectrum ROM and is required by the ESX Dos API. However outside of making ESX Dos API calls, the ROM is usually paged-out. Instead the layout of the bottom 16k is this:
MMU0is read-only and set to page 28, containing the ISR routine, and most constant values used in the program.MMU1is also read-only and mostly used for loading and buffering, like the compressed contents of levels or as the first half of the digital audio buffer.MMU0andMMU1are configured as write-only for writing to the Layer 2 display in 16k chunks.
MMU2would traditionally be the ULA on a Spectrum but that is shifted to page 10 and assigned toMMU3below. Instead this slot is either (a) used as an extension for 16k-size buffers starting atMMU1, like when loading sprites and playing audio loops, or (b) as a write destination for data when we're decompressing zx0 data read fromMMU1such as level screens.MMU3would traditionally point to the tilemap on the ZX Spectrum Next, but most of the time we point it to page 10, which is to where the ULA has been relocated (so thatMMU1andMMU2can form a contiguous memory space for DMAing when buffering). The game can flip between the two when required to modify each one.MMU3Acts as secondary buffer as well, currently used when loading palettes, as that can happen simultaneously while playing digital sound from the "main" buffer area atMMU1andMMU2.
MMU4toMMU7hold the executable code and read/write variables, buffers, etc.- The stack grows back from the end at $FFFF.
All code (c) 2025 Paul Tsochantaris, published under the MIT license.