Note: This is a pretty long drawn rant
I've been battling with the Physics API exposed by Unity on and off for months now, mostly trying to create high quality character controllers for third and first person games. There are basically two ways to create a character controller, you either use a full rigidbody and try to control the volatility of it or you use a kinematic rigidbody and move it manually using translates and add features (like collision) that you don't have.
Now, these both options both have very specific problems. First I want to talk about using a full rigidbody and trying to constrain it to behave properly. This was the first route that I took for both my character controllers. The main problem with using a rigidbody for your character controller is that the movement precision is constrained to your physics update rate, this is not the same as your frame rate. This will lead to movement that looks "choppy" (for lack of a better word), you can clearly see the difference if you create a cube and first move it inside Update and then move it in FixedUpdate and compare the two.
There are other problems with using a physics driven rigidbody also, such that it's very hard to get 100% precise movement (something which is required when building a controller for a multiplayer FPS). It's also incredibly hard to re-position, correct or re-play movement or movement commands when using a rigidbody, something that is also required for many types of multiplayer games.
So, with all these problem associated with a physics driven rigidbody, I ended up going with the more manual route: Using a kinematic rigidbody and manually move and position it and detect collisions manually. Moving and positioning the body is obviously very easy, as it's just modifying the transform.position property. However, properly detecting collisions and making sure that the character doesn't go through any object, as it turns out, is really really hard to get right inside Unity.
The common way to manually implement collision detection for a player character is pretty simple:
- Use a capsule as the players body
- Define what's referred to as a "skin width"
When the players capsule intersects another collider, you perform a push-back by applying a vector which is inverse to the collision, both in terms of magnitude and direction. If the intersecting collider has gone more then the width of the skin into the players capsule movement is halted completely.
Now, this would be really easy to implement if the proper methods were exposed by Unity, but they are not. You have access to these methods:
- Physics.CapsuleCast - Can't use (only returns one hit)
- Physics.CheckCapsule - Can't use (only returns a bool)
- Physics.SphereCast - Can't use (only returns one hit)
- Physics.CheckSphere - Can't use (only returns a bool)
- Physics.Raycast - Can't use (doesn't represent any width)
- Physics.RaycastAll - Can't use (doesn't represent any width)
All methods other then CapsuleCastAll, SphereCastAll and OverlapSphere are essentially useless for this purpose, as they all have some limitations on the return value. The obvious choice should be CapsuleCastAll, and the manual for it even states: "... is useful when a Raycast does not give enough precision, because you want to find out if an object of a specific size, such as a character, will be able to move somewhere without colliding with anything on the way ..."
Sadly, CapsuleCastAll isn't up for the task. The main issue being that it seems as if under *some* circumstances when a collider is already intersecting the capsule, it won't register a hit when you call CapsuleCastAll. Now I know some people are going to say that this is how (ray-) casts are supposed to work, that when the ray originates from inside a collider that collider will not be hit. But that's not what's happening with CapsuleCastAll. If you look at this screenshot:
Each colored box in the console log represent one press of W to move the soldier forward so he intersects with the cube, and you will see that the three red ones only OverlapSphere detect the collision with the box, even though the character clearly is colliding with it. Now, I've tried to find the reason for the behavior that CapsuleCastAll exhibits here, but I've not managed to dig anything up, the assumption I have is that it somehow has something to do with how the physics engine updates (since the cast is done inside Update and not in FixedUpdate), but not sure.
Anyway, CapsuleCastAll gives unreliable results and the same goes for SphereCastAll. The only function which lets you check an area (and get the intersecting colliders back as a return value) with 100% certainty is OverlapSphere. So the way I've managed to get around this is to use several spheres that define the body of your character and then use OverlapSphere to check them all, and then on-top of that implement my own skin width / collision detection, this is how it looks in the editor:
Now this works, but it complicates the code a lot and is a decent amount slower (as you need to check 4 volumes instead of one, even though Sphere overlaps are very fast) then just using one collider. So, we've come to my final reason for writing this post, a couple of questions, both directed at Unity in general and at the community.
- Why is the API of the underlying PhysX engine not exposed properly? The reason I'm asking is because being able to have access to OverlapCapsule would be great. And considering we already have CheckCapsule, it can't be that big of a leap.
- Has anyone managed to implement a fully working character controller, that is what people would call "AAA"-grade? And if so, how was this done?