7
\$\begingroup\$

So this sort of behaviour probably exists in other games, but the old Kirby games - Kirby's Adventure and Kirby's Dreamland 2 - is where I noticed it.

So, when dealing with regular block platforms, Kirby's hitbox acts like a simple rectangle, as you'd expect, as evidenced by the fact that you can stand like so:

Kirby standing over an edge, supported by only a pixel or two of his foot.

On the other hand, when dealing with slopes of any kind, standing on them looks like this:

Kirby standing on s slope, with one foot embedded in the slope.

As you can see, if we were still using the rectangular hitbox, it would be halfway in the ground. This sort of behaviour is easily explained, however, buy a hitbox shaped like a 'diamond' or a 'plus'. (This is probably so that it doesn't look like Kirby is hovering in midair when walking on slopes)

Platforms such as these show perfect consistency with the 'diamond' hitbox model:

Kirby hovering off the edge of a sloped platform.Kirby standing on the peak between two slopes.

However, of course, the first screenshot shows that it's not possible for a diamond-shaped hitbox to be used all the time, or you would be able to 'slide' off any corner as if there was a slope there.

Kirby standing on the lip between a flat and sloped surface.

In fact, formations such as this show that it can't even be based on particular tiles, at least not 'full' tiles. (16x16, Kirby's size) (Otherwise you couldn't walk all the way up that slope)

Another model I've come up with is that the hitbox is always rectangular, but all slopes in the game are actually 'sunk' deeper than the tiles would have you believe. However, the stake-shaped platforms in the third & fourth screenshots invalidate this model, since then you wouldn't be able to stand at the top middle like you see there.

By the way, slopes on the ceiling work symmetrically, as can be seen in water stages.

While it seems intuitive to the player, how could we program a similar collision system ?

N.B. I am not asking for speculation on the internal workings of Kirby (unless someone here actually developed the collision system for those games - unlikely). Just a general approach.

\$\endgroup\$
  • 6
    \$\begingroup\$ We generally don't answer questions of historical curiosity on this Q&A site. This resource is dedicated to helping developers today solve problems in games they're currently developing - so if your question is "How can I handle 2D platformer collisions" you'll find many existing answers addressing this topic. How Kirby did it may or may not be a suitable solution for the game you're making, so it's better to ask about the problem than a specific past solution. If you're interested in the historical mechanics of Kirby specifically, a ROM hacking forum might be a better place to ask. \$\endgroup\$ – DMGregory May 17 '17 at 12:57
  • 2
    \$\begingroup\$ I've adjusted the question to avoid the assumed "historical speculation" aspect noted by @DMGregory. \$\endgroup\$ – Engineer May 17 '17 at 13:32
1
\$\begingroup\$

One other way this could be handled is by checking for collisions at the center of the character's base first, and falling back to the adjacent overlapped tile if no collision is found there.

Regardless of which tile we end up finding collision in, we always calculate the floor height using the same point in the middle of the character's base, which here I'm calling characterFootX & Y respectively

Assuming integer pixel coordinates on a 16x16 tile grid, it might look like this:

// Round the character's foot position into its corresponding tile indices.
int tileX = characterFootX >> 4;
int tileY = characterFootY >> 4;

// Fall back on an adjacent tile if this tile has no collision.
if(tiles[tileX, tileY] == EMPTY_TILE) {

    // Round the character's tile up or down to the closest neighbouring tile horizontally.
    // (+1 if we're in the right half of the tile, -1 in the left half)
    tileX += (characterFootX & 0x8) >> 2 - 1;

    // Check tile above, to handle cases where we're 
    // just off the bottom edge of a slope above us.
    if(tiles[tileX, tileY + 1] != EMPTY_TILE)
        tileY++;
}

int floorY = -1;
Tile tile = tiles[tileX, tileY];

if(tile == EMPTY_TILE) {
    // Falling
} else {
    // Get pixel offset from the center of the tile.
    int deltaX = characterFootX - 16 * tileX + 8;

    // Get base height of the tile.
    floorY = tileY * 16 + tile.baseHeight;

    // Adjust height based on slope.
    floorY += tile.slope * deltaX;
}

I think this policy of always using the center of the character's base for collisions, and only looking at the single valid collision tile closest to that center, can account for all of the cases shown in the screenshots.

\$\endgroup\$
1
\$\begingroup\$

For old 2D games full vector collisions were too CPU heavy and shortcuts were taken. If you want to recreate the same feel and it's associated collision glitches you will have to use the same tricks.

There's two separate types of collisions: the simple and fast full block collision (in blue) and the special cases (slopes in yellow).

The yellow circle is a 1-pixel collision.

The full collision (in blue) only checks for full blocks completely ignoring special tiles (yellow).

This allows walking into the slope's cell boundaries.

Once the foot pixel check (yellow) detects being inside a slope it pushes the character up as needed and disables the full collision check to allow walking all the way up the slope. If the full collision (blue) wasn't disabled when inside a slope cell you'd get stuck halfway up the slope due to hitting the side of the top full cell.

enter image description here

Optionally there is a similar 1-pixel collision for the left and right sides at the centre to handle the case of a wall up a slope (in green).

This could be avoided if there is never a wall up a slope and some games did this. When you only had between 1 and 8MHz you saved where you could.

And a 4th check on top of the head for ceiling slopes.

enter image description here

To save on processing the special case collisions were not a real diamond but single pixel-sized checks. This worked fine with the limited slopes shapes and making sure to edit the map accordingly to avoid the possibility of wedging the character.

Unless you're targeting an ~8Mhz micro-controller or want to reproduce the particular issues this trickery could cause an actual diamond-shaped collision can now be used on modern computers.

\$\endgroup\$
0
\$\begingroup\$

You could achieve this by using different collision conditions for:

  1. adjusting the character y-position while in the "grounded" state to get slope following behavior
  2. transitioning from the "grounded" into the "falling" state

Whenever the player-character is in the "grounded" state, you do a raycast from a point between the feet of the character straight down and straight up. See where you hit a platform surface and change the y-coordinate of the player sprite so it sits on that platform. Limit the length of the raycast to less than 1 tile in both directions and don't do anything when there is no hit. This is to not interfere with the cliff-standing behavior seen in the 1st and 3rd screenshot when there is ground below the cliff.

After that, check if the character is still grounded. Use a line-cast from the lower-left to the lower-right corner of the player-sprite to check if it is still in contact with a platform. When it isn't, change the player's state from "grounded" to "falling".


Note that this is what you would be doing in a "modern" game where you have the convenience to make deliberate use of floating-point arithmetic and geometrically complex colliders. The game from the example was from a much simpler time. The MOS 6502 CPU of the NES didn't even have a floating-point unit, so they had to do everything with integer math. This meant that their code was likely a lot cruder. I would assume they had special coding for handling walking on the diagonal tiles. Something like:

walkRight:
    x++;
    if (tile[x>>4, y>>4 + 1] == UP_SLOPE)
        y--;
    if (tile[x>>4, y>>4 + 1] == DOWN_SLOPE)
        y++;
\$\endgroup\$
0
\$\begingroup\$

There are multiple ways to do collision systems in 2D games. I find it unlikely that this particular game didn't use a tile-based system of some kind, sprites larger than their hitboxes is a common feature for tile based platformers, and pixel-perfect tile systems are still extremely useful for modern games as well. The only tricky part about it is to handle slopes and other edge-case obstacles like one-way platforms. This article provides a decent explanation for several collision systems, though it is by no means the only method.

I'll describe the basic algorithm the article provides, but I encourage you to read the whole thing for more comprehensive explanations/insights into these systems. There are for sure more efficient, intuitive ways of doing 2D collision systems nowadays, but this is what I can gather from your specific question.


The whole level is built as a tilemap with all the relevant information stored on the tiles (type of terrain, player effects, etc...) and characters have their usual collision/hit boxes. Much of 2D collision systems, especially for older games, involved pixel-perfect math and the like.

Each frame we determine the movement in the X and Y directions. If moving left we take the leftmost edge of the hitbox (similarly for moving right, up, and down) and determine what tiles that edge is intersecting with. Go through these tiles to detect any obstacles that would prevent movement and move the character the minimum distance it can move. Repeat until you've calculated all movement for the frame.

That was for maps without slopes, and adding them can get tricky. Unless you want to do some more wacky calculations, it's best to keep the slopes used a consistent size. So, for example, a ramp that is 4 tiles long and goes up 1 tile should never be placed with gaps or end prematurely.

We first need to check the X direction of movement, and before checking for collisions, we first check to see if the tile we are on is a slope and register the tile as a collision only if we're going up the slope (in this case moving left). If we have a slope collision, we'll have to move the player up in the Y direction.

Mega Man tile map example

Another case we need to consider is the top and bottom of the entire slope (the tile with the blue dot marked on it). To prevent the character from being stuck, we should ignore the full tile obstacle immediately before and after the slope is completed. The article does this by checking if the slope is filled on the left or right edges, meaning that we're at the end of the slope's tiles.

For vertical movement, usually we have gravity being applied to the character, so again we check for collisions in slope tiles and move the player down accordingly.

A lot of this is pixel-perfect math and collisions which depends heavily on the coordinate system you're using for your tilemap.

\$\endgroup\$
0
\$\begingroup\$

For the images that you posted it looks like Kirby has a point collision at the middle of his feet and the scenery has its collision boxes extended beyond the cliff edges so that the point collision doesn't allow Kirby to fall until he is fully beyond the edge of the cliff.

\$\endgroup\$
0
\$\begingroup\$

Well, you answered it yourself. When Kirby is standing on a "normal" tile, the bounding box is rectangular and when he is touching a slope, it turns into a diamond.

\$\endgroup\$
  • \$\begingroup\$ The actual implementation is likely what DMG suggested. Model-wise, the bounding box "changes shape". \$\endgroup\$ – wolfdawn Apr 17 '18 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.