2D Platformer Controller Fundamentals

In this post I will cover the most important part of my platformer controller, the physics. This controller uses 2D collider casting for its collisions, and semi accurately responds to collisions. In its most basic form, as described here, the controller will slide nicely off slopes and allow you to create responsive characters.

So here’s the deal. Unity’s physics can be a little difficult to deal with for platformer controllers. Unity’s physics can be too drifty, jerky, or otherwise uncomfortable to work with. Instead, we will use only the bits of Unity’s 2D physics we like, namely, colliders and collider casting.

Your character will want some kind of collider on it, I use a BoxCollider2D. Also, collisions in Unity get kinda weird without a Rigidbody2D, so chuck one of those on and turn on all the position and rotation constraints. This lets us bend the Rigidbody2D to our will while still getting proper collisions.

The main physics issue is what happens when we approach terrain at high speed. We don’t want to hit terrain and suddenly come to a stop, we want to slide off terrain as below.

We will achieve this by:

  1. Calculate how far we would move this frame unimpeded. We will call this our delta.
  2. Collider cast our collider in the direction of delta to find where we collide
  3. Move to the point of collision and subtract the distance we traveled from our delta.
  4. Collisions reduce velocity, so modify our delta and velocity accordingly.
  5. If there is any delta remaining, go to step 2.

Step 4 requires a little vector math:

For those of us that haven’t taken a course in vector math, that dot is the dot product operator. When used like this, it basically gives us a float that says how much our velocity points in the direction of the normal. By subtracting (velocity ⋅ hit.normal)* hit.normal from our velocity, we are eliminating all velocity in the direction of the hit normal, leaving only the green arrow. We do this for both out velocity and delta.

 

Implementing steps 2-4 above gives the following function:

You might notice line 33 was not in our list of steps. If we don’t add a fraction of the collision normal (about 0.01) to our position, we will get stuck in the ground.

Line 20 does the actual collision detection for us. Collider2D.Cast casts the collider in question like a giant raycast, and stores any hits it gets in hits. The second parameter that I have filled in with terrainFilter is a ContactFilter2D, a handy little class that lets us filter by collision normal, depth and collision layer. This is a member variable that I have set up as public so I can edit it in the inspector. In this case you only need to set it up to collide with your terrain layers.

Now all we need to do steps 1 and 5, set our delta and loop if it is not zero:

The moveIterations parameter is the maximum number of times you want to move the character per frame. The first diagram above shows 3 move iterations which is what I have my moveIterations set to. I run this in my FixedUpdate loop. You’ll need to set up a velocity variable and some way to modify it, and you should be good to go. The easiest way of testing would be to add a little downward velocity each frame to give your character gravity. If you encounter any bugs, or this code just straight up doesn’t work, feel free to flame me on twitter @_Zwander.

Leave a comment

Your email address will not be published. Required fields are marked *