Back to the Z80
Having spent a week away in Normandy, marooned from the world of the Intarwebs and PC, I have come up with a potential new Z80 project. I had my Game Gear with me (naturally) and one of my games was the most excellent Game Gear port of Marble Madness:
The game is simple if you do not know it; you must guide a marble through an obstacle course to the finish in under a certain time. Various tricky level design elements and enemies make this a hard task. The world is presented in an scrolling isometric 3D view.
The Game Gear is not an especially powerful platform; the Z80 runs at ~3MHz. The TI-83 Plus runs at 6MHz (an underclocked 8MHz Z80). Of course, the Game Gear has the bonus of hardware-accelerated smooth-scrolling (which is how we get super-fast games like Sonic on it) but Marble Madness has fairly simple basic scrolling; vertical smooth scrolling on a TI is cheap, and horizontal smooth scrolling only needs to go one pixel at a time for Marble Madness - we can't do much about that apart from bitshifts, but a pixel/frame isn't asking for too much.
In a bizarrely well-organised move I ended up taking along a pen and paper, and ended up making a bunch of notes on how to construct a Marble Madness engine.
For this game, it all boils down to the way the graphics are presented to make or break the gameplay. They need to accurately convey the illusion of a 3D world, whilst at the same time being handled internally as very primitive - we can't do CPU/memory expensive things like treat the world as a true 3D model and have a 3D isometric engine.
Also, the limitations of the display and LCD driver of the TI-83 Plus need to be taken into consideration. The TI-83 Plus is equipped with a 96x64 pixel monochrome LCD. It is accessed through the use of a driver chip on ports $10 and $11. As it is monochromatic, a single byte represents 8 entire pixels. The physical display is 12 bytes wide and (there are 64 rows); in reality it is 16 bytes wide, but normally the 4 bytes are off the edge of the display. It can be set into a special 6-bit mode where each byte only covers 6 pixels; the 6 pixel columns are squashed up to give us a 16-column display, but this is only really useful if you are, say, using it for a 6x8 font (the TI-OS uses this mode for the text-mode screens).
For this reason, horizontal scrolling is cheap; you can just change the byte offset as you copy to the buffer to scroll up and down. However, horizontal scrolling requires (expensive) bit-shifting. Not only that, but the display operations have to be timed using inefficient loops (push af \ inc hl \ dec hl \ pop af is common - there is no accurate timing hardware) else it decides to do useful things such as jump into 6-bit mode. The whole TI graphics system is very sluggish.
The low resolution needs consideration; the Game Gear Game relies on clean, large, colourful graphics. I will have to drop the resolution down so that you can see a decent region around your marble; and can't afford to use effects such as dithering which make the display unclear (the display is quite blurry).
Here is a sample course (thanks to Maxim for producing the map):
To fit it on the TI, I have decided to halve the resolution. If I kept the squares as tightly packed as in the Game Gear version, this would result in 4x4-ish tiles - mucky, cluttered and ugly. To remedy, I shall have to expand the tiles to double their visible width/height; so a single tile in my version would cover 4 tiles on the Game Gear version. The levels would have to be adjusted (in the image above, the tile blocks at the top are arranged in 3x3 grids; I shall probably go for 2x2 grids of my larger tiles, or just go straight to 1x1...
The way I intend to handle the levels would be to split them up into their individual parts;
- Background - this would be a simple 2D tilemap, made up of 8x8 tiles with a fast smooth-scrolling engine. This is rendered, then the marble/enemies are copied on top of it.
- Heightmap - this is a sort of 2D map of the 3D world, where each point has two values - a type (flat, steep slope, empty space, goal...) and a relative height. This would be used by the physics engine to handle the movement of the marble around the level.
- Depth map - this is a sort of copy of the 2D tilemap, but only contains special masked copies of the tiles on the tilemap, together with a depth (z-index). This way, when the ball is drawn the tiles that it is convering can be checked. If the corresponding tile in the depth map is closer to the user, it is redrawn, concealing part of the ball. For example, see the railings in the picture of the level above, or any of the large holes. As most of this layer will be empty, it could be RLE compressed fairly succesfully.
Other things, such as animated sprites or dynamic parts of the level, would be handled separately. For example, level 2 has this tilty thing:
It tilts up and down. For this, a special large sprite could be rendered, and the 3D heightmap in memory adjusted as required.
I'd be interested to hear any thoughts/ideas/pointers on my methods...