So, what do I have in store for you this time? Not an awful lot. I've mostly been reading my code to familiarise myself with it again. It's been a while since I looked at it. Now, dear reader, I don't know if you are an assembly coder or not (it seems to be a dying art these days, it's all C, C++ or Java - does nobody like to get down and dirty with the hardware anymore? 'Yall should try Forth. It's kind of like dirty sex, without the damp bits).
Well, I don't know about you guys, but I comment my assembly code a LOT. Every single line has a comment*. The idea is, in two years time, the comments will tell me what my code does (and others). Well, despite my copious comments, reading my code back has still led to a few "what idiot wrote this?" moments. It's so depressing when I realise it was me! Closer inspection however usually reveals that I was right the first time. Doing it a certain way saves a register, or maybe a shorter op-code (faster), so sometimes the code looks a little contrived (for example taking a hit early on to set things up using lots of registers, meaning I can use indirect addressing later and take advantage of both the extra speed and shorter op-codes).
The game engine itself (partly written) is all hooked on the video interrupt. There are 60 interrupts a second on the NTSC consoles, and 50 on the PAL (European) consoles. So all the interrupt code is written to be as short as possible. Get in, do the job, and get out.
The level renderer (the code that sets up the graphics for each screen and draws the level) is not interrupt based. I believe the ZX Spectrum version re-draws the entire screen every frame - there's no need to do that on the TI - we have sprites which are independent of the background, so the animation (sprites) won't disturb anything underneath.
Currently (IIRC) the game engine is logically divided into 12 frames. I call different parts of the 'total job' on different frames. For example, on every frame:
- Decrement portal flash delay counter (the portal is the exit to the next level)
- Service speech synthesizer (which may immediately return if there is no work to do)
- Decrement the air delay counter
- Decrement animation counter for the 'life sprites' (indicates how many lives remain)
- Cycle the key colours
- Animate the conveyors (if required)
- Do guardian animation
- Read the keyboard/joystick
- Animate Willy (Miner Willy, our hero)
The guardian animation is a separate engine in its own right. It's just called by the ISR (interrupt service routine) every six frames, and does a different amount of work each time, depending on which guardian(s) need to be moved. Basically it works by decrementing counters. There are counters for:
- Movement frequency (how often to physically move on the screen)
- X upper extent (how far to the right the guardian can move)
- X lower extent (how far to the left to guardian can move)
- Y upper extent (how far up the screen)
- Y lower extent (how far down the screen)
That's the plan anyway! I'm just about to start putting the code together. The nice thing about coding a generic engine is (of course) it only has to be done the once. Then each level just has some data to initialise each guardians start positions, colour, animation frame index, x and deltas and extents. Simples!
We'll see how it shakes out. I'll probably code the engine so that it's 'intelligent' in terms of writing to VDP ram. Makes the engine a bit more complex, but it not write to VDP unless there is something to write - saving a time penalty (VDP on the TI is a IO device, you write to it through a 3 byte window - it's not part of the CPUs memory space).
Stay tuned. When I get level 1 (Central Cavern), and level 2 (the cold room) animating, I reckon I will have cracked the animation engine.
There are a couple of exception cases (the Kong levels for examine, and Eugene's lair) which do special things when all the keys on a level have been collected. I'll look at that when I get to it!
* except comments. Comments don't have comments. That would be really silly.