Custom Character Controller in Unity: Part 3 – Analysis of the Physics API

Up until this point I’ve made multiple references to some of the Unity Physics API, but we haven’t really explored it in detail. As an astute reader may have guessed from the title of this entry, that’s what we’ll be doing now. We’ll go through the functions available, analyze some of their associated issues, and ways to overcome them.

As per usual I’ve done my best to avoid doing original research and will be making heavy use of this post from fhhoollm.



The Physics API

With many of the functions simply being variations of each other, it shouldn’t take long to go over the details of the Physics Script Reference. I’m not going to bother talking about the methods that have an All variation available, since they are identical other than that the raycast stops immediately at the first contact.

Raycast: Fires a ray in a specified direction for a specified distance (or infinitely far). If an object is contacted, the RaycastHit structure provides useful information about it: where it was contacted, what the normal of the surface was at the contact point, and so on. Because it fires just an infinitely thin ray, this method isn’t particularly use for collision resolution.

CapsuleCastAll: At first glance this seems ideal for usage with a character controller (due to it’s capsule shape), and for the most part it is. It is important to note that as this is a cast it will only detect a collision where the normal of the surface is facing the cast-no backfaces are detected. In addition, the cast does not detect any objects that are within the boundaries of the “capsule” origin of the cast, i.e., it doesn’t detect any objects touching it’s initial position. This is a drawback we will need to overcome if we want it to be a useful tool for our character controller.

CheckCapsule: Right away we have a candidate to solving the problem stated above. CheckCapsule seems to exactly compliment CapsuleCastAll-it will detect all the objects at the initial position of the cast that the CapsuleCast cannot. Unfortunately, it only returns a bool, as opposed to an array of colliders, giving us no information on what objects we actually collided with.

CheckSphere: Same as above, except with a sphere shape.

Linecast: Identical in terms of function to Raycast. Simply a different way of defining the origin, direction, and magnitude of the ray.

OverlapSphere: Now we’re getting somewhere. As far as I can tell, OverlapSphere works exactly as advertised. Bear in mind this note does appear on the docs:

NOTE: Currently this only checks against the bounding volumes of the colliders not against the actual colliders.

…and I really don’t know what this means. I’ve tested it against Box Colliders, Sphere Colliders and Mesh Colliders and it seems to be checking against the actual collider, not just the bounding volume. Note that I am taking bounding volume to mean axis aligned bounding box, and it may mean something different in this case. If not, I’m going to assume it’s a documentation error.

RaycastAll: Same as the Raycast method, except that it does not stop at the first object it contacts.

SphereCastAll: Functions the same as CapsuleCastAll, with the same primary drawback of not detecting objects contained in the sphere defined at the origin of the cast. SphereCast also (like CapsuleCast) does NOT always return the proper normal of the face it collides with. Because it is a sphere that is being cast (rather than an infinitely thin ray in Raycasting) it can collide with the edges of a mesh. When this happens, the hit.normal that is returned is the interpolated value of the normals of the two faces that are joined by the edge. Since CapsuleCasting is just casting with a swept-sphere, it also has the same issue.

In addition to the above tools to detect collisions, Unity also provides a Rigidbody.SweepTestAll method. After testing it, it seems to have identical behavior to the cast methods; faces contained within the collider are not detected by the sweep. I tend to prefer using CapsuleCastAll and SphereCastAll over SweepTest all, as they offer more options (like being able to define your own origin), however SweepTest is useful for box shaped characters, as there is no BoxCast method.

Mesh Colliders

Before we go any further, I want to talk a little bit about mesh colliders. Up until now we’ve focused primarily on the primitive colliders (Box, Sphere, Capsule, etc). However in practice the overwhelming amount of your level’s collision geometry is going to be composed of mesh colliders.

Unlike the primitive colliders, which have their collision representation built from a variety of preset parameters (radius for spheres, height for boxes, and so on), a mesh collider’s collision data is unsurprisingly formed from a 3D mesh. Mesh colliders come in two flavors: Convex and Concave. This article does a terrific job explaining the difference between them.

Since convex hulls must be fully enclosed and Unity limits their size to 255 polygons, they are unideal to be used to represent intricate level geometry. Concave hulls can be of any size, but they come with the drawback of no longer being an enclosed object; instead of being a solid volume, they are essentially just a surface of triangles. This means we can no longer detect if an object is “inside” a concave mesh, since there is no “inside” to check against. This brings us to the problem of phasing. Phasing occurs when a character is moving fast enough (or a wall collider is small enough) that in two frames he travels from one side of the wall to the other, effectively passing through it. Concave mesh colliders amplify this problem by no longer having the ability to detect player collisions occurring “inside” them, making it easy for the player to phase into the mesh.

Controller movement over one frame. His speed is great enough that neither his initial position or final make contact with the thin mesh collider wall in our way

Controller movement over one frame. His speed is great enough that neither his initial or final position make contact with the thin mesh collider wall in his way

Effectively, if we are directly beside a triangle on the surface of a mesh collider with it’s normal facing in the exact inverse direction of our movement vector, the furthest we can move is exactly equal to twice our radius. Considering collision resolution tends to place the character directly flush with the wall, this is a situation that is encountered fairly often. If your character controller is representing a character of about 2 meters (represented as generic units in Unity) high, your radius is typically in the ballpark of 0.5 meters (units). Which means your character can move at most 1 unit per frame. If your game runs at 30 frames per second, you can move at most 30 meters per second, or 108 kilometers per hour. This is pretty damn fast, but if you’re building the latest and greatest Sonic the Hedgehog title it may not be fast enough.

With the controller directly flush with the surface, it cannot move more than twice it's radius or it will phase through the wall

With the controller directly flush with the surface, it cannot move more than twice it’s radius or it will phase through the wall

One solution to this problem is to run your controller’s physics more than once per frame. Alternatively, we can use CapsuleCastAll to check if there are any colliders between our initial and final position every frame. We’ll explore both these options in future articles where we continue to implement the character controller.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s