Custom Character Controller in Unity: Part 4 – First Draft

After nearly a month of silence, the wait is finally over! No longer will you have to endure the hardship of waking up every morning, immediately opening my illustrious blog only to suffer a crushing disappointment deep in your soul. …Anyways. In the intermediate time since the last post, I’ve built a first draft of the character controller, and I’ll be going over it’s implementation in this article. This post will concern itself solely with the main controller class—in the next article, I will go over an application of the controller in a demo character that I’ve built. The controller itself is a single C# script, which can be downloaded here. As with the previous Pushback example (from the second part of this series), I am making use of some of fholm’s RPG classes, as well as a modified version of a class which he uses to find closest points on the surfaces of colliders called SuperCollisions.cs. In addition, for debugging purposes I typically use my own DebugDraw.cs class to draw markers and vectors on the screen. Finally, I use the lots of the 3D math functions found in this class, by Bit Barrel Media. In the future, it’ll probably be simpler for me to just post a Unity project, but for now since we’re just mostly focusing on a single class today, this is easier. I’ll go over the basic structure of the controller, and then iterate through each of it’s features in more detail.

[ EDIT: past Erik was spot on. You can now get the controller through the Downloads page. Note that the code linked to above is an earlier version of the character controller, which I am leaving posted here for clarity and learning purposes. If you are planning on using the controller in your project, please head over to the Downloads page for the up-to-date and complete code along with a sample project ]

The controller goes through three primary phases: Movement, Pushback and Resolution. In the Movement phase, we calculate all of our character’s movement logic and modify his position accordingly. We then run our Pushback function, ensuring that he is not intersecting any of our geometry. Finally, we run any necessary Resolution steps. These could include limiting the angle of slope our character could move up, clamping him to the ground, etc.

 Figure showing the movement and pushback phases of the controller

Figure showing the movement and pushback phases of the controller

Before we get started I should note that, unlike the previous controller, this one is built using three OverlapSpheres, placing one above the other, to simulate the shape of the capsule. The controller is built to work with any number of spheres—tall slim characters may require more than three, while short squat ones may need less. Let’s take a look at the code now. The first phase within our controller is fairly simple for the time being, consisting of a single instruction:

transform.position += debugMove * Time.deltaTime;

This allows you to set in the inspector how much the controller will move each frame. When we build our actual character, this line will be replaced with all of our movement logic. For now, it serves as a handy debugging tool. Phase two is Pushback. Here, our goal is to check if the controller is intersecting any colliders, and if so to then push him to the nearest location on their surface. The basics of how we do this can be seen in the Implementation article I posted earlier. This time around, the algorithm is slightly more complicated. The first half of the method is more or less the same as before; we check the nearest point on the surface of any collider within the OverlapSphere. Next, we need to see which side of the normal the origin of the OverlapSphere is. We do this by raycasting from the center of the sphere in the direction of the nearest point on the surface. Since a raycast only detects a surface if the normal is facing the cast, whether this cast returns true of false will tell us if the origin is outside or inside the surface, respectively. Note that in the code I use a SphereCast with a very small radius instead of a raycast; this avoids errors when raycasting directly at an edge of a mesh.

The "feet" OverlapSphere of the controller detects a collision with the ramp, finds the nearest point and then raycasts towards it (shown as the red arrow)

The “feet” OverlapSphere of the controller detects a collision with the ramp, finds the nearest point and then raycasts towards it (shown as the red arrow)

Before applying the pushback vector, we do a final check to make sure we’re still colliding with the object. Because the OverlapSphere will return all the collisions first and then apply the pushbacks one by one. This makes it is possible that, in the case of hitting multiple colliders, a previously applied pushback can have the side effect of moving the controller enough so that it is no longer colliding with objects that were originally touched by the OverlapSphere, but no longer are. We resolve this by checking the distance between the origin of our sphere and the nearest point on the surface of the collider; if the distance is greater than the radius and we are located “outside” the normal, we know that we are not touching it and were displaced by an earlier collision.

The controller's lowest OverlapSphere collides with both the green ramp and the blue ground, with the nearest points on their surfaces shown in teal and blue, respectively. The ramp collision pushback is resolved first, causing a side effect where the OverlapSphere is no longer colliding with the blue ground

The controller’s lowest OverlapSphere collides with both the green ramp and the blue ground, with the nearest points on their surfaces shown in teal and blue, respectively. The ramp collision pushback is resolved first, causing a side effect where the OverlapSphere is no longer colliding with the blue ground

The third phase is less clear cut than the previous two. It can be defined as doing any “clean-up” or “reactionary” logic. There are two main methods that are executed here: slope limiting and ground clamping. Slope limiting should be familiar to anyone who has used Unity’s built-in controller: if a character attempts to move up a slope that is steeper than a specific angle, he is repelled by the slope as if it were a solid wall, instead of pushed up it. Ground clamping is not included in the Unity controller, and is fairly important. When moving horizontally over an uneven surface, the controller will not (by itself) follow the geometry of the ground. In the real world, we time our leg movements to allow for each slight increase or decrease in elevation, and gravity takes care of the rest. However, in a game world we need to handle this a bit more explicitly. Unlike the real world, gravity is not a constantly applied force in most controllers. When we are not standing on a surface, we apply acceleration downwards. When we are on a surface, we set our vertical velocity to zero, to represent the normal force exerted by the surface. Because our vertical velocity is zeroed out when standing on a surface, it will take time to accelerate our downwards speed when we walk off said surface. This is fine when we are actually walking off a ledge, but when we’re walking down a slope or over uneven ground, it creates an unnatural bouncing effect. In addition to being a problem visually, this oscillation between grounded and not-grounded is a problem for our actual game logic, since a character’s behavior is typically very different when he is on a surface compared to when he is a falling.

The left image shows how the character's movement follows the uneven surface by ground clamping. On the right, we see how he "bounces" across the surface when clamping is not applied. Each red "X" represent when the downward force of gravity is zeroed out

The left image shows how the character’s movement follows the uneven surface by ground clamping. On the right, we see how he “bounces” across the surface when clamping is not applied. Each red “X” represent when the downward force of gravity is zeroed out

This problem is solved in our reaction phase with the aforementioned “ground clamping,” which, as the name implies, will adjust our character’s position to be in line with the ground by SphereCasting directly downwards from our “feet.” Obviously, there are plenty of times when you do not want to clamp your character to the ground, such as when he is beginning a jump, or far enough above the surface that he should not be counted as standing on it. You’ll notice that I talk about whether the controller is “grounded” or not an awful lot. You’ll also see the method ProbeGround() be called multiple times throughout the main loop of the controller. Knowing when your character is standing on a surface and when he is not is very important to building a proper controller. I don’t intend to provide the tools to check if a character is “grounded” or not within the main controller class, since this depends greatly on your game’s structure. However, I do provide a method that will detect what is below the player, as well as store it (and some additional useful information) in a variable that is easily accessible. How you use this is up to you, but in the next article in the series I’ll be providing an example character that uses this controller and the data from the ProbeGround() method. The SlopeLimit method should be easy enough to understand as it’s functionality is familiar and I’ve commented it fairly well. (I actually haven’t. But I plan to before I upload the file.) Speaking of familiar functionality…those who know the Unity character controller well have probably identified that my custom controller seems to be lacking a feature: StepOffset. I do intend to tackle this method, but it seems much more complex than I initially expected, or I’m missing a simple solution for it. It’s definitely a “need-to-have,” since it’s pretty essential for most applications. That pretty much covers the Super Character Controller. Next time, I’ll go over an example character that I’ve built using the controller class detailed today, as well as provide the source code for it. If any of my code doesn’t seem to be working of compiling properly, please contact me so I can fix the error!

15 thoughts on “Custom Character Controller in Unity: Part 4 – First Draft

  1. Very interesting reading here. I’m glad I came across your blog while trying to understand 3D platformers’ mechanics (I’m a beginner Unity scripter).
    I tried to import your script in Unity but I encountered some errors that I may not be able to fix with my current knowledge. But it’s ok, I enjoy reading your articles, you saved me a lot of time! Thanks!

  2. Hey Dan,
    Would you be able to post what the error was that you encountered? I’m guessing the scripts themselves compile fine (I’d hope so!) but rather that it’s a runtime error of some sort. I had initially intended to follow up Part 4 fairly quickly with a Part 5 where I’d include a demo Unity project, but it’s kind of fallen on the wayside due to school/enthusiasm for Mario 64 remakes. Either way I’ll edit this post with a sample project over the weekend to make it more useful to readers.
    Thanks,
    Erik

  3. I am also following Mario 64 remakes very closely (especially the one made with the Blender Game Engine). I have played the game a lot during these last days in order to have a better understanding of its mechanics.

    I’m getting two errors with the Math3D script:
    Assets/Math3D.cs(893,42): error CS0103: The name `RotDiffToSpeedVec’ does not exist in the current context
    Assets/Math3D.cs(902,42): error CS0103: The name `RotDiffToSpeedVec’ does not exist in the current context

    I guess I can just wait for the demo to try your project. But don’t feel obliged to release it quickly, focus on your priorities first.

    Thanks Erik, keep up the great work!

  4. Mystery solved. It seems that someone has edited the wiki page of Math3d to add in a method, AngularAcceleration, that references an assumed-to-be private method, RotDiffToSpeedVec, which is never actually defined in the file.

    I edited the link in the Part 4 post to instead point to my google drive, where I’ve uploaded the Math3D class that I use on a regular basis. Bear in mind for whatever reason I have 3D in all caps, as opposed to 3d, since I’m guessing the wiki was Math3D when I originally pulled it. This should be consistent with the other files, but just a heads up in case it isn’t.

    I’ll still add a new post with a demo package over the weekend anyways to avoid this in the future.

    Erik

  5. Thanks for putting this out! I’ve had to write my own character controller because I needed a rotatable character controller (wall walking), this saved me a lot of time 🙂

  6. Hi Erik, thanks for this very interesting reading. I, too, wanted a rotable character controller to make the character run on planets, and other fun stuff. Unfortunately unity’s character controller (his capsule collider) can’t be rotated, so, unfortunately, I have to write my own. I tryed the demo and it works really well, but with a few glitches. First, I find a little annoying the fact that I can’t make a capsule collider, but I have to simulate the capsule with three sphere colliders, but players won’t notice it especially if your character model is not a capsule, so that’s ok (and it’s not really a bug). Another bug, the character doesn’t handle steep slopes correctly, once you start moving, the character keeps moving forever even if you stop pressing the key, as for less steep slopes, the character seems to work fine. Hopefully you can fix this!
    The last thing I wanted to say is that this is very intersting and that it helped me a lot.

    • I agree about the capsule shape. I hope in the future the Unity team will implement something like Physics.OverlapCapsule. Using the spheres is a bit inefficient computationally but tends to be unnoticeable most of the time.

      Could you post a video or picture showing the issue with the slopes, and how steep? I’m using the controller for my Mario 64 project, and you can see here that Mario is able to run on very steep slopes fine. It’s likely something that needs to be fixed with the PlayerMachine class, which is meant to be modified to suit your game’s needs.

      • Hi, thanks a lot for your quick reply. I want to say good work for the mario project. It looks very good. In your video Mario seems to work perfectly even with that steep slope. I just play the demo scene and I have problems with the steepest slope in the scene (the platform in the bottom-left).

        • Also, I forgot to tell you that in Super Mario 64, when Mario is on a slope and is running, the character rotates to make his “y” axis align with the surface normal (I don’t know how to explain it), it would be nice if you explain how to do that.

          • You are quite correct, in the original Mario 64 he does turn his body up/downwards depending on the angle of the slope and the direction he’s running on it (if he’s running perpendicular to it’s descent, he is not angled, if he is running parallel he is). I just haven’t gotten around to putting it in yet. I did however do something similar for when he is sliding on a slope that you can see here. I’m not actually rotating the Super Character Controller (although you can do this) but rather just the mesh (which is a child transform of the main gameObject). This is because Mario is only rotating “visually” with the physics staying the same.

            For your original question, are you using the code provided on this page or the sample project on the Downloads page?

Leave a comment