Retro Platform Jam #6
As anyone I know who can draw was tied up with other things at the time, I didn't hold much hope for this one. But I thought I'd use it to make engine improvements and also to try to get multiple games finished. It's a 3 week jam, so plenty of time.
The theme of the jam was 'Underground' and trying to come up with something original using that was actually the hardest part of the whole thing!
Project.
I'm keeping all of the games within a single project, which also has about 5 other in-progress games in there. I set which game is active with a single line: eg: `IS_GAME_UNDERGROUND equ 1` I have all the graphics for all the games in a single editor project as well.
Editor.
Lots of iterative improvements made, notably to do with placing objects with paths for a horizontal shooter. Plenty of bugs found and fixed.
Perhaps the most important change I made to the editor though was enabling it so that I can make real time changes in the editor, which send messages and data to the running game (in an emulator). The game can fix up its pointers to the new data (eg. level changes, path changes, object parameters) and restart immediately. The turnaround for level changes is now less than a second, which gives much more time to really play around and make gameplay improvements. I'll write this up in a separate blog entry.
Game 1: Horizontal Shooter.
I'd already written a few shooter type games, so I wanted to use this for two things: 1. developing the tools for making a horizontal shooter, and 2. Trying to get some snazzy tech in there so that people would not notice how bad the graphics would be.
I tried to do a tortuous pun, even half explaining it on the title screen. The game is a fight against the coffee invasion. (you're a teapot) Most enemies are coffee beans. You need to destroy them, gather up the grounds, which power up your weapon. They are under-ground, and you need to grind them more to win!
It's clear that no-one understood this as the game was rated pretty close to last in the jam for 'theme' usage. Oh dear :)
Color change per scan line.
It's possible on the Genesis get an interrupt every scan line, and set various registers there to produce interesting effects. In this case I just wanted to change two colors per scan line. This obviously takes up a fair amount of CPU.
As this is all in asm, I have the advantage of being in full control of what CPU registers are used, so I thought I'd try making one free for use only in the HBL... A6 became my HBL register. I had to re-write a few other functions in the engine and make sure they worked OK. I also put this on a global define, so I can specify whether I need A6 to be saved at an engine level.
Here's the code, coming in at 76 cycles:
move.l (a6)+,VDP_DATA
move.l #VDP_CRAM_WRITE+COLORBAR_ADDR,VDP_CONTROL
rte
There are alternative ways to write it, eg.
move.l a6,-(sp)
move.l g_cbar_addr,a6
move.l (a6)+,VDP_DATA
move.l #VDP_CRAM_WRITE+COLORBAR_ADDR,VDP_CONTROL
move.l a6,g_cbar_addr
move.l (sp)+,a6
rte
Or:
move.l g_cbar_addr,VDP_DATA
add.l #4,g_cbar_addr
move.l #VDP_CRAM_WRITE+COLORBAR_ADDR,VDP_CONTROL
rte
..which take a lot longer to run.
Using the HBL takes in total about 20-25 scans worth of CPU, so in the end, this optimization is probably worth doing. More registers could have been used to improve performance too, but that would be a lot more effort fixing the rest of the code.
Boss Effects.
What is a Genesis shooter without some linked sprite boss fights. I wanted to try something that perhaps hadn't been done before, though pretty similar to many implementations in the past. The difference is that this one has a name: Verlet integration. It's something that 90s programmers certainly used, but proabably without knowing what it was called.
It's very simple code. It's a bunch of positions moving around with velocity, but after being moved, the code tries to limit the distanec between them. No square root or floating point, and slow divides and multiplies, makes this pretty slow on the trusty 68k, but for a boss battle there's not a huge amount else going on, so i didn't really need to optimize it much for 16 objects.
I generated a normalize vector lookup table with a maximum distance of 64 between points. It's just a quarter of a circle, only positive values, so input values need to be converted to this format. Because of this I could use the faster mulu instead of muls instruction, and flip back to the correct negation using a couple of eor instructions. I always enjoy it when I get to use an exclusive-or!
In his case I wanted to achieve a hose pipe effect, like you might see in black and white comedies, where the hose appears to have a life of its own.
One end is attached to an object (a coffee machine!?), whilst the other end follows a looping bezier curve around. This makes the inner points of the hose bounce around.. almost convincingly!
Scaled Software Sprites
I added this right at the end, as the jam was extended by a day, and I had nothing else to do!
Nothing special, but I hoped it would again be more impressive than my bad drawings. I simply pre-generate code to copy from one buffer to another, skipping at various intervals. Didn't even particularly optimize it, it was enough to get one 32x32 on screen at a time.
Music
It had to be a version of something from a Bach coffee cantata. Midi import to my sequencer to the rescue!
Game 2: Firebird.
Flow Map
This had a few iterations on gameplay ideas, but it started out as me wanting to do the tech aspect of using a flow map and 'particles' on the Genesis. I already had flowmap editing in my toolset (for a racing game) so I was able to export a map and some test flows.
Iterated on the 'particles' a number of times until it was able to move 256 of them around, over 4 or 8 frames, and render them all at once. The particles are character sized, so rendering them is very fast.
I use a ram buffer for the visible screen area (even though the map is scrolling). every frame I copy in the background map, then render all particles, then dump the entire (2k) map to the VDP in the vertical blank. This makes the scrolling less efficient than updating edges, but makes everything much simpler.
Game
After iterating on a few ideas I settled on a 'Joust' control system for the player - a bat - using a button to flap wings. And flapping the wings could affect the flow map. This all worked nicely, so it took a few more tests to settle on the goal of the game.. getting 'flame' in to 'vents' by directing their flow. So you're flying around in underground caves, flapping wings in pillars of fire, directing it to a destination. Obviously that's a firebird, not a bat now.
I was pleased with the tech, it was quite tricky enabling all of this movement at 60hz. But it doesn't really look difficult, so I assumed I wasn't going to get such a great score for 'platform usage' in the jam, which turned out to be the case, only ranking 6th in the end, even though it's some of the 'best' stuff I've written for the Genesis. Oh well! :)
Adding the rest of the game was tricky, trying to come up with new enemies to dodge. I don't think there's anything else particularly original in there, sadly, but it all came together nicely, and luckily I got first place for 'fun' and 'game design', just pushing it to an overall victory.
Sprite Optimization
As it turns out this wasn't needed for this game, but I thought I'd need to overhaul the sprite 'render' system. This did come in handy for the other games though, and going forward to new projects.
The inner part of my Sprite Renderer was fairly fast from the start, even going back to Rocket Panda. Data is exported in a format which reduces the CPU costs when writing out the sprite parameters.
However over time, more and more gunk had been added for special cases. Some sprites need clipping, some don't, there are Compound Sprites to deal with, and the whole callback system for sprites to do non-standar rendering (for instance the Verlet chains in Game #1). So I ended up with things like 'postRenderCallback', 'preRenderCallback' and ''compoundSpriteAddr' - all of which are being tested in the main render loop. I knew it was bad, but on testing it was worse than expected.
I bit the bullet and made an entirely new system based on a single 'onRender' callback (the sensible way) - what had stopped me doing this before was that I had about 7 games on the go using the same renderer, and it would have been a fair amount of work to update everything. However it had to be done.
It turned out very successfully, perhaps saving a third of CPU render time in the worst case. And better still it became more modular and simple to work with., allowing more fine grained control over render types, and also saving some memory on the GameObjects, as they don't need the extra pointers.
This was stage 1, and stage 2 became overhauling the compound sprite system. This had large overhead per sprite because of the way the data was stored.. referencing the original sprites by ID, which needed extra lookups per sprite. Another big change later saw the export format entirely changed, keeping it's own versions of the sprite data rather than referencing originals. This halved the render time for compound sprites, at the expense of using more ROM data, and adding a limitation in that each compound sprite can only use frames from a single sprite. In practice that's what I always do anyway, so not a problem.
Music
Something from Stravinsky's The Firebird, of course. This took as much time as anything else in the game. Transcribing from a full orchestral score down to my 6 FM channels. Very slow going at start because of transposing instruments. Had to try to do this in my head for each note entered. But then, thankfully, I found that Musescore can do this for you. This saved my life, I would not have got so much done otherwise.
Game 3: Edgar Allan's Hole.
Nothing interesting technically happened here. I just wanted to get another quick pun based game in to the jam!
At least it was based underground, so people understood the theme relationship.
After a few trials and errors, the game became something similar to old games like Outpost, with enemies attacking from all sides, and you needing to choose the correct weapon for each one.
Music
I had a little time left to write something new for this. Trying to get a creepy Hammer Horror type thing going. As the gameplay was based on 1 minute rounds, I was able to get the music to match the game, for once, building up towards a crescendo at the end of the round.
Comments
Post a Comment