Character Controller: Stairs
Have you ever played a game and came across something like this?
This is a problem that every 2D and 3D programmer must face at some point. Stairs are a tricky obstacle to get characters to maneuver over because they’re bumpy, steep, and have vertical parts. Running up stairs may require the character to jump up each step, and running down may make the character airborne and glide to the bottom, as though they had just run off a tall ledge. What we want is for the character to stick to the stairs, not get airborne, move at a constant speed (that we can control), and not bounce up and down.
This problem is typically solved using one of two methods (and they’re both kind of rubbish).
Solution 1: Capsule Collider for Character
What if you just put a capsule around the character? If you make the bottom round enough, the character should be able to slide up and down the surface!
Unfortunately, this has some issues.
Firstly, if the character’s feet may not be aligned with the steps correctly, such as when the character is on the edge of the step.
Due to the corner of the stairs colliding with the curve of the capsule, this will slow the character down when going up, resulting in jittery movement. You’ll see the whole body bob up and down as it goes over each step.
And what about sticking to the stairs when coming down at high speeds? This does nothing to cater for that.
Also, how do you determine how steep the stairs are? Afterall, you probably want the player to slow down when going up steep stairs. Or maybe you want to maintain the same speed as running on a horizontal surface. Either way, you’re not going to have complete control over the movement speed.
Solution 2: Sloped Collision for Stairs
The second solution is to use a separate sloped collision mesh for your stairs. This way you only need to worry about your character moving up and down ramps. This is great because if your character can already run along the flat ground, going up and down a slope should come free. Used in conjunction with the capsule collider, friction will be minimised and the movement will be super smooth, right?
Yeah, kind of, but there are downsides to this.
Firstly, the character will usually move faster going down the stairs, and slower going up. Have you ever tried running down stairs? If you go too fast, it’s not pretty. There are ways around this (like raycasting toward the ground and adjusting the speed based on surface normal).
Secondly, you’ll find that the character’s feet either float in the air or penetrate the stair’s mesh.
Also, this doesn’t address the issue of sticking the character to the stairs. If the slope is too steep and the character is moving too fast, the character will still fly from the top when coming down. Once again, this can be solved by raycasting to get the distance to the ground and forcibly sticking the character in the correct vertical position, which can be icky to calculate if your character’s collision is a box or something more complicated.
Okay, so neither of these solutions are great, so what should we do?
Solution 3: Raycast Snapping
So it seems like we want complete control over how the character moves along this complex surface, including the speed, velocity, and sticking to the surface. But notice how this doesn’t really have anything to do with the shape of the character? What if we remove the collider from the character entirely for now, and just use good old raycasts to solve it. This is a good way to think about it:
What we’re doing here is raycasting from the hips (or the highest point that the character can step up) down through the ground (to the lowest point that the character can step down to). There is also a point around the feet. This is where we want the ground to be.
Alright, so how does this work?
First we raycast down. If it doesn’t hit anything, then you can trust that we’re airborne.
If we have hit something, we want to move the character’s feet toward this point. This means cancelling the character’s vertical velocity and either shifting the character up or down. This doesn’t need to be an immediate jolt to the correct position, it can be a smooth lerp (be careful not to make it too slow or your character will slowly sink into the stairs or float above).
What about controlling the speed based on the stair’s/slope’s steepness? Well we already know how far below or above the character is from the target, so we can use that to dictate the speed.
You should expect the code to look something like this:
|
|
You may find there are issues due to a raycast being infinitely thin. This is obviously not an accurate representation of a human leg. To combat this, we can use a spherecast (a raycast with a radius), or perform multiple raycasts in a circle around the character’s feet. This has the added benefit of calculating the angle of the stairs which can help with controlling the speed of the character when going up or down the stairs.
So the upsides for this solution are:
- Don’t need to change any environment meshes
- Better control over the speed when moving up and down stairs
- Control how high and low the character can step
- Control how fast the character can move vertically
- Trivial to add foot IK
- Still very performant
But what about colliders for the character? Without colliders, the character will be able to walk through walls! What makes this great is that you can put collision on the character as usual, just make sure there’s no collider over the raycast area (ie the legs).