Last weekend I participated in Micro Jam 013, which had a theme of "Lava" and a prerequisite of "Time is Limited". As I'm a sponsor of that jam, I wasn't eligible for any prizes, but I wanted to enter anyway, both for fun and to demonstrate what can be done with MiniScript and Mini Micro.
In the week before the event, I knew the theme (Lava) but not the prerequisite. Still, I started thinking about what sort of game I wanted to make. One of the built-in demos in Mini Micro is a platformer demo, so I thought about extending that into a full game, which could fit the theme by having you jump over frequent pits of lava.
A quick search led me to this Lava Cave tileset available at GameArt2d.com. The only thing I didn't like about that was the lava; it was completely static. So I decided to make my own animated lava tiles, copying the style and color from the professional tileset.
Here's the code I used to make that.
grad = file.loadImage("pics/lava_gradient.png")
bubbleColor = "#fe7c1c"
topColor = "#fef51d"
bubbleX = [25, 50, 100, 120]
bubbleY = [70, 30, 110, 40]
bubbleR = [6, 3, 8, 4]
surfHeight = function(x, t)
// return height of surface (generally around 42) from
// top of image, at pixel x on frame t.
return 42 +
3 * sin(x/128 * 2*pi) * sin(t/15 * 2*pi) +
2 * sin(3*x/128 * 2*pi) * sin((t+2)/10 * 2*pi) +
1 * sin(5*x/128 * 2*pi) * sin((t+1)/6 * 2*pi)
end function
drawFrame = function(x, y, t)
gfx.fillRect x,y,128,128, color.clear
for i in range(0,127)
top = surfHeight(i, t)
gfx.drawImage grad, x+i, y, 1, 128,
0, grad.height - 128 + top-42, 1, 128
if i % 10 == 0 then yield
end for
for i in bubbleX.indexes
bx = bubbleX[i]
by = bubbleY[i] - 128/30 * (t % 30) // note: top-down
r = bubbleR[i]
top = surfHeight(bx, t)
if by-r < top - 3 then
by += 128
end if
if by < top - 2 then
gfx.color = topColor
else if by < top + 4 then
gfx.color = color.lerp(bubbleColor, topColor, 0.5)
else
gfx.color = bubbleColor
end if
gfx.fillEllipse x + bx-r, y + 128-by -r, r*2, r*2
end for
end function
clear
x = 0; y = 0; t = 0
frames = []
for t in range(0,30)
drawFrame x, y, t
frames.push gfx.getImage(x, y, 128, 128)
x += 130
if x+128 > 960 then
x = 0
y += 130
end if
end for
t = 0
while true
gfx.fillRect 960-256, 640-128, 256, 128, color.clear
gfx.drawImage frames[t], 960-128, 640-128
gfx.drawImage frames[t], 960-128*2, 640-128
t = (t + 1) % frames.len
wait 0.1
end while
Truth be told, I did that a few days before the jam actually began, considering it "noodling around with artwork" and not part of the jam proper (hey, I'm already disqualified from winning anything, so cut me some slack!). But that was it, until the jam officially started and the prerequisite was announced.
Day 1
The prereq turned out to be "time is limited," which was easy to incorporate into my platformer idea; like the original Super Mario Bros. games, I would simply have a timer that counts down, and if you don't reach the end of the level before the time runs out, it's game over.
So I dug in, first writing a little code to manage the parallax-scrolling background. Then I used an external tool to arrange the tile images from the asset pack (and later, some other assets, like these) into a tile set in an order that made sense to me:
Using the tile editor demo (in Mini Micro at /sys/demo/levelEditor.ms), I banged out a quick layout using these tiles, and pulled code liberally from both the platformer demo and this post about Donkey Kong to get our hero Kip running and jumping around.
The lava was an interesting challenge for two reasons. First, because it's animated, while the other tiles in the level are static; and second, because the lava needs to actually extend behind other tiles, otherwise there would be a visible gap between a column of lava and the craggy column of rock next to it. I solved both problems at once by putting the lava into its own TileDisplay, layered behind the main level TileDisplay. When loading the level, I scan for lava tiles, and wherever I find one, I actually set the lava in the corresponding lava display, plus the neighboring cells on either side. To do the animation, I just have 31 different versions of the lava tiles, each with the bubbles in a different position. The lava code cycles through these, assigning a different tileset to the TileDisplay every tenth of a second. Presto, animated lava!An aside about how the lava works.
The next step was to get some enemies in there. I define the starting position of each enemy by plopping an enemy-shaped tile down in the tile editor, but of course enemies aren't actually tiles; they need to be sprites, so they can be animated and move smoothly. So, much like the lava, the level loader scans the layout for these enemy tiles, clears the tile, and creates an enemy sprite at that location.
The enemies make use of the same support code from /sys/lib/spriteControllers as the player avatar. So they were very quickly able to move on the level, dropping off of platforms and so on. I added checks for player-enemy collision, and (again, like Mario) decided that if you jump on an enemy (i.e. collide near the top while moving down), you squash 'em; otherwise, they kill you (or for now, the game exits).
And that was the end of the first day.
Day 2
The first day was only a few hours (the jam started Friday at 6:00 PM my time). So, the second day (Saturday) was the big work day. I had a long list of goals, but the two biggest and thorniest ones were (1) a boss monster, and (2) syncing the clock to the music.
For the boss, I used one of the "rock monsters" in this asset pack. He has bits of glowing lava all over him, and so was perfect for the theme.
Like the other enemies, I made the boss class derive from the Platformer class in /sys/lib/spriteControllers. But his behavior is much more complex; where enemies simply walk in one direction until they hit something, and then walk the other way, the boss needs to run, jump, taunt, etc. So, this took a bit more code and logic. In the capture below, you can see some debug output being printed to the screen as the boss moves around in a test.
I thought my boss really needed a ranged attack; otherwise it would be too easy to stay away from him, or just jump on his head when he gets close. The lava caves tileset came with an animated fireball sprite,Β which I was also planning to use in the level, popping up here and there from the lava. At first I thought I would have the boss spew fireballs from his mouth, but the asset pack didn't come with any open-mouth images. I went so far as to try and paint an open-mouthed boss myself, but it just didn't look as polished as the rest of the images.
So I decided to make the boss throw fireballs instead. There was no "boss throwing" animation either, but I was able to make one by taking select frames from the attack animation, and pasting a fireball into his hand.
So now I had at a working boss, I turned my attention back to the timer. I have a paid subscription at StoryBlocks.com, a great site for sound effects and music. I wanted something retro and 8-bit-ish, and finally settled on a song called Chiptuned. After some editing, I cut it down to 1 minute 45 seconds for the main level, and about 50 seconds for the boss level, which seemed about right. Even better, the boss section is more intense, as a boss fight should be!
The only problem was, Mini Micro's Sound class does not have a "position" or "time" property. You can tell when it's playing, and you can tell its duration, but you can't tell how much it has played or how much is left.
I worked around this by noting the current time and duration when I start playing the sound. From these, I can calculate the time left later by checking time
again. (I actually included some more complex logic to allow for the possibility of fast-forwarding to a certain point, e.g. for when you respawn, but didn't end up using this.)
By dinner time Saturday, the game was roughly playable, though most of the level was still empty. At the end of the long empty stretch was a door leading to the boss level. You could bonk the boss on the head to play his "hurt" animation, but he never actually died, so the game was not winnable. But you could die (end the game) in lots of ways, including stepping in lava.
After dinner, I was starting to run out of steam. I added 3 phases to the boss β so you have to hit him 3 times, and after each hit, he gets progressively harder/more aggressive until after the last one, he finally dies. But that was about it for Saturday.
I did one last task before calling it a night: I created the project on itch.io, and uploaded what I had so far. I consider this a really important step, because it's one of those things where unexpected hurdles might pop up, and you don't want to be dealing with that five minutes before the deadline.
Last Day
I woke up on Sunday refreshed and ready to go, with about 12 hours left. I thought about what major features I still needed, and the only tricky one I really wanted was smashables.
I just love smashing things in video games, don't you?
I also added collectible coins and gems, because what's the fun in smashing things if they don't disgorge loot?
Then it was time to really think about the design for the main level. I knew it was going to be only one level, so it had to do it all. I decided to divide it into 5 phases, and in the level editor, I actually plopped down a signpost (later removed) every 26 columns so I knew how much space I had. The 5 areas were:
- tutorial (learn how to walk and jump)
- easy stuff (ladders, barrels, simple enemy)
- upper/lower (two ways to proceed, with more loot on the tricky upper path)
- hard stuff (tricky jumps, harder enemies)
- kitchen sink (jumps + enemies)
Laying all that out and testing it took a while, but having a plan really helped.
I should add that just like the enemies, the initial position of the player is also determined by a special tile in the tile map. So, to test any section of my level without having to play through all the stuff before it, I could just plop down a Kip tile where I wanted to begin. Then after testing, clear that tile back out. This was a real time-saver.
Then it was sound effects, scoring and lives, a death animation for our hero, a main menu, and little tests and tweaks. Both my sons (19 and 23 years old, and both avid gamers) tried it out and were able to beat it after a few tries, which let me know I had the difficulty level about right.
In the afternoon, I made some cover art, updated the itch.io page, and submitted to the jam.
Lessons Learned
Honestly, this was one of the smoothest development projects I've ever done. Murphy must have been on vacation last weekend. But here are a few take-aways:
Mini Micro is plenty powerful enough for a 2D platformer, even with fancy visual effects like animated tiles, and even in the web player.
Thinking about a project for a good week before starting it really helps βΒ I had so much of the code mapped out in my head, that there was very little backtracking once I actually started.
Ten bucks for an asset pack to make your game look pretty is totally worth it, at least if you have meager art skills like I do.
That "death animation for our hero" than I casually mentioned in passing? The one you probably didn't even notice? Yeah, that was by far the #1 most commonly mentioned feature among people who commented on my game. It's just a simple spin and throw, but apparently players love it. Not sure what the general lesson here is, but I found it interesting anyway.
- I should think more carefully about the name. I vascillated between "Kip in the Caves of Lava" and "Kip and the Caves of Lava," and I'm still not completely consistent about it (though I prefer the latter).
If you'd like to try the game, you can visit its official page here:
Kip and the Caves of Lava on itch.io
The project page includes a download of the user.minidisk, which you can mount in Mini Micro and play locally. I've also put all the source code and assets up on GitHub.
This was a long post, but I hope a fun one to read. Does it remind you of any of your development experiences? Or inspire you to try something new? Please share in the comments below!
Top comments (0)