Last week, we looked at how you would do sloped platforms like in Donkey Kong. People loved it, but they wanted to know:
How Would You Do That: Ladders?
So it turns out that the Platformer class in /sys/lib/spriteControllers already handles most of the messy details of climbing. If you define a climb animation, then by default your character can climb anywhere. We defined just such an animation in our little demo:
kip.climbAnim = newAnim([
file.loadImage("/sys/pics/KP/KP-climb1.png"),
file.loadImage("/sys/pics/KP/KP-climb2.png")], 10)
Try it! If you don't have last week's code handy, you can twist open this section and copy it into Mini Micro:
Last week's code (click the little triangle to see)
import "spriteControllers"
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
// Set up the level
makeFlat 0, 13, 0
makeRampUp 14, 27, 0, 1
makeRampUp 25, 0, 3, 4
makeRampUp 2, 27, 7, 5
makeRampUp 25, 0, 11, 6
td.setCell 10, 1, 8 // (ladder)
// Set 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
// determine whether (and where) kip has solid ground below him
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
while true
spriteControllers.updateSprites 1/60
yield
end while
run
that code, and then just press the up-arrow key anywhere. You'll find Kip happily climbs up (and down) nothingness.
Adding ladders
Last week we hacked in a single ladder tile, just for decoration. This week, we want more! Again we'll copy the original Donkey Kong layout.
edit
the program, and scroll down to right below the makeRampUp
function (right above // Set up the level
). Insert this new function to create a ladder:
makeLadder = function(x, y0, y1=null)
if y1 == null then y1 = y0
for y in range(y0, y1)
c = td.cell(x, y)
if 24 <= c <= 39 then
td.setCell x, y, c - 16
else
td.setCell x, y, 8
end if
end for
end function
This function takes the X (horizontal) position of the ladder, and one or two Y (vertical) positions for the ladder extent. It iterates over that Y range, and sets a ladder. But what exactly is going on with that if
statement?
To understand that, we need to refer back to our tile set:
You can see that tiles 24 through 39 are the partial-girder tiles, places where we have just the top or bottom of a girder (in order to make sloped platforms). How do we modify those to have a ladder? As the tile set is arranged, the proper girder+ladder section is always 16 tile indexes before the girder-only tile. So, if we find the existing cell is in this range, we subtract 16. If the existing cell is anything else, then we just set it to index 8, which is a full ladder piece.
Now replace the next part of code, which sets up the level, with this:
// Set up the level
makeFlat 0, 13, 0
makeRampUp 14, 27, 0, 1
makeRampUp 25, 0, 3, 4
makeRampUp 2, 27, 7, 5
makeRampUp 25, 0, 11, 6
makeLadder 10, 1; makeLadder 10, 4
makeLadder 23, 1, 3
makeLadder 12, 5, 8
makeLadder 4, 5, 7
makeLadder 8, 9; makeLadder 8, 12
makeLadder 14, 9, 12
makeLadder 23, 9, 11
When you run this, you should see a more complete level with ladders, as shown below.
Climbing on ladders only
To limit where our character can climb, we need to override the canClimb
method on our character class. To prove that this works, edit
your code, scroll down below the kip.groundBelow
function (right above the while true
loop), and insert this code:
kip.canClimb = function(direction)
return false
end function
Now run
the program again, and you should find that Kip can't climb at all (even though he still has a climb animation defined).
So the job of this method is to return true
when kip can climb, given his current location and the intended climb direction (+1 to climb up, or -1 to climb down).
Now if we didn't have partial-girder, partial-ladder tiles, and if ladders extended through the girders, then implementing this function would be fairly trivial: we'd only need to check if the tile at our feet is the ladder tile. But neither of these things is the case; ladders in Donkey Kong stop below the girder they attach to, and because of slopes, we have to deal with funny Y coordinates depending on which particular tile we have. All that makes our canClimb
method a bit longer. But here it is:
kip.canClimb = function(direction)
col = floor(self.x / 32)
row = floor((self.y - self.footOffset) / 32)
c = td.cell(col, row)
if (c == null or c < 8 or c > 23) and (
direction < 0 or self.state == spriteControllers.CLIMBING) then
// no ladder here, if either we're going down, or we're
// already climbing, then also check the tile right below it
row -= 1
c = td.cell(col, row)
end if
if c == null or c < 8 or c > 23 then return false
// OK, there's a ladder in this cell; but account for slopes
// to figure out exactly where it ends.
climbStep = self.climbSpeed / 60
if 16 <= c <= 23 and direction < 0 then // ladder on top of a girder
minY = self.groundBelow(self.x, self.y)
if self.y - self.footOffset < minY + climbStep then return false
else if 9 <= c <= 15 and direction > 0 then // ladder below a girder
maxY = self.groundBelow(self.x, (row + 1) * 32)
if self.y - self.footOffset + climbStep*2 > maxY then return false
end if
// center ourselves horizontally on the ladder
self.x = (col + 0.5) * 32
return true
end function
Let's walk through how this works. First, we get the col
umn and row
of our feet, and check the cell at that position. If it's not a ladder cell (between 8 and 23), and if we're either going down or we're already climbing, then we check the tile below. That deals with the case at the top of a ladder, where we're actually on a girder tile but we still want to consider it climbable.
Then, we have two if
blocks that make adjustments for slopes (partial girders). The first one, checking for a tile index from 16 to 23, deals with a ladder section on top of a girder (which occurs at the bottom of a ladder). We make use of the groundBelow
function from last week to figure out where the actual girder part is, in pixel coordinates. If our climb down would exceed that, we report false ("can't climb").
The else if
block, checking for a tile index from 9 to 15, handles the opposite: this is the girder on top of the ladder, and again we use groundBelow
(starting up one row) to figure the maximum Y we should allow the player to climb.
The final part of this method just centers the player on the ladder. You could comment this out if you want to see the effect: without it, you can climb up the side of a ladder, which looks a little funny.
This method was developed iteratively, which is to say, I coded a little, tested a little, and repeated until I was satisfied. That's likely to be the case in your own game, too — getting the feel just right often takes a little experimentation.
That's all, folks!
And that's it! Once you have that canClimb
method properly defined for your game, the library code handles the rest. You should now be able to run, jump, and climb around your level!
As you can see, making a run/jump/climb platformer in Mini Micro is much easier than you'd probably expect (even with sloped platforms!). Why not try making your own game?
Also, I'd like to continue to explore how classic video game effects were done, all on hardware considerably less powerful than Mini Micro. So keep your eye out for things that make you go "hmm," and let me know in the comments below if anything makes you say: How would you do that?!?