One-Way Platforms

Since we've just finished working on the ground collision check, we might as well add one-way platforms while we're at it. They are going to concern only the ground collision check anyway. One-way platforms differ from solid blocks in that they will stop an object only if it's falling down. Additionally, we'll also allow a character to drop down from such a platform.

First of all, when we want to drop off a one-way platform, we basically want to ignore the collision with the ground. An easy way out here is to set up an offset, after passing which the character or object will no longer collide with a platform.

For example, if the character is already two pixels below the top of the platform, it shouldn't detect a collision anymore. In that case, when we want to drop off the platform, all we have to do is move the character two pixels down. Let's create this offset constant.

public const float cOneWayPlatformThreshold = 2.0f;

Now let's add a variable which will let us know if an object is currently on a one-way platform.

public bool mOnOneWayPlatform = false;

Let's modify the definition of the HasGround function to also take a reference to a boolean which will be set if the object has landed on a one-way platform.

As explained before, we also need to make sure that this collision is ignored if we have fallen beyond the cOneWayPlatformThreshold below the platform.

Of course, we cannot simply compare the difference between the top of the tile and the sensor, because it's easy to imagine that even if we're falling, we might go well below two pixels from the platform's top. For the one-way platforms to stop an object, we want the sensor distance between the top of the tile and the sensor to be less than or equal to the cOneWayPlatformThreshold plus the offset from this frame's position to the previous one.

Finally, there's one more thing to consider. When we find a one-way platform, we cannot really exit the loop, because there are situations when the character is partially on a platform and partially on a solid block.

We shouldn't really consider such a position as "on a one-way platform", because we can't really drop down from there—the solid block is stopping us. That's why we first need to continue looking for a solid block, and if one is found before we return the result, we also need to set onOneWayPlatform to false.

Now, if we went through all the tiles we needed to check horizontally and we found a one-way platform but no solid blocks, then we can be sure that we are on a one-way platform from which we can drop down.

Handle Collisions for the Left Wall

Similarly to how we handled the collision check for the ceiling and ground, we also need to check if the object is colliding with the wall on the left or the wall on the right. Let's start from the left wall. The idea here is pretty much the same, but there are a few differences:

The sensor line is on the left side of the AABB.

The inner for loop needs to iterate through the tiles vertically, because the sensor is now a vertical line.

The outer loop needs to iterate through tiles horizontally to see if we haven't skipped a wall when moving with a big horizontal speed.

Handle Collisions for the Right Wall

Finally, let's create the CollidesWithRightWall function, which as you can imagine, will do a very similar thing as CollidesWithLeftWall, but instead of using a sensor on the left, we'll be using a sensor on the right side of the character.

The other difference here is that instead of checking the tiles from right to left, we'll be checking them from left to right, since that's the assumed moving direction.

Move the Object Out of the Collision

All of our collision detection functions are done, so let's use them to complete the collision response against the tilemap. Before we do that, though, we need to figure out the order in which we'll be checking the collisions. Let's consider the following situations.

In both of these situations, we can see the character ended up overlapping with a tile, but we need to figure out how should we resolve the overlap.

The situation on the left is pretty simple—we can see that we're falling straight down, and because of that we definitely should land on top of the block.

The situation on the right is a bit more tricky, since in truth we could land on the very corner of the tile, and pushing the character to the top is as reasonable as pushing it to the right. Let's choose to prioritize the horizontal movement. It doesn't really matter much which alignment we wish to do first; both choices look correct in action.

Let's go to our UpdatePhysics function and add the variables which will hold the results of our collision queries.

Now let's start by looking if we should move the object to the right. The conditions here are that:

the horizontal speed is less or equal to zero

we collide with the left wall

in the previous frame we didn't overlap with the tile on the horizontal axis—a situation akin to the one on the right in the above picture

The last one is a necessary condition, because if it wasn't fulfilled then we would be dealing with a situation similar to the one on the left in the above picture, in which we surely shouldn't move the character to the right.

If any of the conditions besides the last one is false, we need to set mPushesLeftWall to false. That's because the last condition being false does not necessarily tell us that the character is not pushing the wall, but conversely, it tells us that it was colliding with it already in the previous frame. Because of this, it's best to change mPushesLeftWall to false only if any of the first two conditions is false as well.

As you can see, it's the same formula we used for checking the collision with the left wall, but mirrored.

We already have the code for checking the collision with the ground, so after that one we need to check the collision with the ceiling. Nothing new here as well, plus we don't need to do any additional checks except that the vertical speed needs to be greater or equal to zero and we actually collide with a tile that's on top of us.

Round Up the Corners

Before we test if the collision responses work, there's one more important thing to do, which is to round the values of the corners we calculate for the collision checks. We need to do that, so that our checks are not destroyed by floating point errors, which might come about from weird map position, character scale or just a weird AABB size.

First, for our ease, let's create a function that transforms a vector of floats into a vector of rounded floats.

Check the Results

That's going to be it. Let's test how our collisions are working now.

Summary

That's it for this part! We've got a fully working set of tilemap collisions, which should be very reliable. We know in what position state the object currently is: whether it's on the ground, touching a tile on the left or on the right, or bumping a ceiling. We've also implemented the one-way platforms, which are a very important tool in every platformer game.

In the next part, we'll add ledge-grabbing mechanics, which will increase the possible movement of the character even further, so stay tuned!