This is the first in a series of posts where I'll be addressing the question:
How would you do that?!
In today's post, we'll be looking at the classic arcade game Donkey Kong.
Like many video games of its day, Donkey Kong consists of sprites (Mario, Donkey Kong, the barrels, the princess, etc.) and tiles (the environment). Not coincidentally, this is just like Mini Micro! But hang on — most of the girders in Donkey Kong are sloped! How would you do that?
The secret of the tiles
If we zoom in on the Donkey Kong screenshot and add grid lines every 8 pixels, we begin to see the secret.
Here you can see that the environment really is a tile display — there's a clear periodicity to it, every 8 pixels. But where the girders are sloped, we must have other tiles in our tile set, that are shifted versions of the basic girder tile. We would need, in fact, eight girder-tops, and eight girder-bottoms. We'll also need versions with ladders above and ladders below. With those, we could build everything we see here.
A bit of googling for "Donkey Kong arcade tiles" led me to a website called The Spriter's Resource, where someone had already extracted a Donkey Kong tile set. But it had a lot of wasted space, so I got out an image editor and boiled it down to this:
If you're following along at home, save the above image as dk_tiles.png
. Then fire up Mini Micro and let's get coding!
Using the Tile Set
Whenever working with a tile display, the first step is to make sure you know what your tile indexes are! To save time, I've prepared a blown-up version of our tile set, with indexes overlaid:
As you can see, the top-left tile is index 0; the one next to it is index 1, and so on. A basic girder is at index 16 and 32; the next seven after each of these step down by one pixel each. (The set starting at index 16 have ladders above them, while the set starting at 32 do not). Girder-bottoms are found at indexes 24 through 31.
I made this tile set 64x128 pixels, because 64x64 wasn't quite big enough, and computers like powers of 2. So there is plenty of unused space at the bottom for you to add your own tiles, if you decide to pursue this further.
Now that we have an orientation to the tile set, let's start coding! Launch Mini Micro, edit
, and put in the following code.
clear
display(5).mode = displayMode.tile
td = display(5)
td.tileSet = file.loadImage("dk_tiles.png")
td.tileSetTileSize = 8
td.cellSize = 32
td.extent = [28, 32]
td.clear
makeFlat = function(x0, x1, y)
for x in range(x0, x1)
td.setCell x, y, 16
end for
end function
makeRampUp = function(x0, x1, y0, startLevel)
level = startLevel
for x in range(x0, x1)
td.setCell x, y0, 32 - level
if level > 0 then td.setCell x, y0+1, 40 - level
if abs(x-x0) % 2 == 0 then continue
level += 1
if level > 7 then
y0 += 1
level = 0
end if
end for
end function
makeFlat 0, 13, 0
makeRampUp 14, 27, 0, 1
makeRampUp 25, 0, 3, 4
makeRampUp 2, 27, 7, 6
makeRampUp 25, 0, 11, 7
td.setCell 10, 1, 8 // (ladder)
The first eight lines or so just set up the TileDisplay. Note that tileSetTileSize
is set to 8, because that's how big the tiles are in our tile set. But given Mini Micro's much higher-resolution display, we set the cellSize
on screen to 32. This effectively scales up the tile display by a factor of 4 (32 divided by 8).
Then we define a couple of helper functions for building the level. makeFlat
is simple; it just makes a straight row of tile 16 (a basic girder). The makeRampUp
function is a little more complex; it keeps a "level" from 0-8 that each girder is shifted relative to the basic girder, and advances this every other column, so we get nice gradual ramps as in the original game.
Finally, the last half-dozen lines use these functions (plus an extra call to setCell
to build the level. If you've done all this correctly, running your code should produce this:
Note that rather than building the level in code, you could use a level editor like /sys/demo/levelEditor to build the level by hand. That's probably more like what the Donkey Kong developer did; each level is probably just stored in ROM and loaded onto the screen. But either approach is fine.
Adding a Player Sprite
A game level isn't much fun if you can't play in it! So let's add our favorite platformer character, Kip. We'll make use of the /sys/lib/spriteControllers library to do most of the heavy lifting for us. edit
the code again, and add this line to the top of the file:
import "spriteControllers"
Then scroll to the bottom, and add this code:
// Set up our player character, "Kip"
newAnim = @spriteControllers.newAnimation
kip = new spriteControllers.Platformer
display(4).sprites.push kip
kip.x = 400; kip.y = 80
kip.idleAnim = newAnim(file.loadImage("/sys/pics/KP/KP-stand.png"))
kip.runAnim = newAnim([
file.loadImage("/sys/pics/KP/KP-run1.png"),
file.loadImage("/sys/pics/KP/KP-run2.png")], 10)
kip.jumpUpAnim = newAnim(file.loadImage("/sys/pics/KP/KP-jump.png"))
kip.fallDownAnim = kip.jumpUpAnim
kip.climbAnim = newAnim([
file.loadImage("/sys/pics/KP/KP-climb1.png"),
file.loadImage("/sys/pics/KP/KP-climb2.png")], 10)
kip.curAnim = kip.idleAnim
// Main loop:
while true
spriteControllers.updateSprites 1/60
yield
end while
Run again, and you should have Kip hanging out on the bottom of the screen. Using the arrow keys and spacebar, you can make Kip run around and jump — but he passes right through the girders, ignoring them entirely.
Interacting With the Girders
For our last, and perhaps most crucial, trick, we need to get Kip to walk on the girders — including the sloped ones!
If your last test is still running, press Control-C to break out of it. Then edit
again, and insert the following code before the main loop:
// determine where the ground is below the given position
kip.groundBelow = function(x,y)
col = floor(x / 32)
row = floor(y / 32)
while true
if row < 0 then return 0
tile = td.cell(col, row)
if 16 <= tile <= 23 or 32 <= tile <= 39 then
return (row+1) * 32 - (tile % 8) * 4
end if
row = row - 1
end while
end function
This defines how Kip finds the ground below him. The given x,y position is in pixel coordinates. We start by dividing those by 32 — the size of our TileDisplay cells on screen — to find the row and column. Then comes a little loop, reducing the row by 1 each time until we find a non-null tile in our display.
Then comes the real magic: if the tile we find is in the range 16-23 or 32-39 — which, you might recall, are grid tops in our tile set — then we can calculate the correct Y position of the ground with this math:
(row+1) * 32 - (tile % 8) * 4
Here, (row+1) * 32
gives us the top of the tile row; but then we subtract off an additional amount, (tile % 8) * 4
, to account for how far the girder is shifted down in this particular tile. %
is the "mod" operator, and gives us a value from 0-7, based on how far our tile index is beyond 16 or 32 (or any multiple of 8 for that matter). And then we multiply this by 4, because of the 4-to-1 scaling of our TileDisplay.
So save and run that, and you should be able to walk and jump on the girders perfectly!
Conclusion
So now you know the secret of Donkey Kong: those apparently sloped girders are just a bunch of shifted girders, carefully arranged side by side, with a bit of math to figure out how Mario should walk on them.
This was the first game to do something like this, and must have caused quite a stir; nobody had seen a tile engine used like this, creating a strong illusion of a non-rectangular environment. And now that you know how it's done, you can use the same trick in your own Mini Micro games!
(For the follow-up, see HWYDT: Ladders!)