Custom Character Controller in Unity

During the creation of Twin Stick Turbo, I came a across a show stopping bug with character movement.

The character's movement stutters on the seams of the cube stacks.

The character's movement stutters on the seams of the cube stacks.

The character's box catches on the seams between the cubes and tumble amusingly on top of the cube stacks. Note that when moving on top of the cube I am not pressing jump. You can see the character looks like it's jumping, but thats due to the glitch. It's quite jarring when your playing.

Here's a better demonstration of the problem:

I'm not sure what exactly causes this, it's something to do with PhysX's collision resolution algorithm to a floor that looks like this:

6452 cubes make up one of the large obstacles in the green level.

6452 cubes make up one of the large obstacles in the green level.

I tried a bunch of simple hack-fixes: overlapping the colliders slightly, changing some values in Unity's Physics Manager, and even went as far as scaling everything in the game up 10x, thinking it was just a result of the tiny-ness of the cubes. None of these worked. Increasing the accuracy of the physics solver helps, but this is a very physics heavy game, and I can't afford the performance hit.

I had two options: remove all the box colliders and use a single mesh collider, or remove rigid body based movement system and write a character controller.

I didn't want to rewrite all my movement stuff, since I had spent a lot of time finding the perfect amount of acceleration & friction, so I opted to try to merge the static cubes into a single mesh.

This didn't solve the problem either. Even the vertices in a mesh can cause this to happen. Blast!

I figured at this point that lots of other people must have had this problem. They have, and the solution seemed to be to write a custom character controller with controlled collision resolution.

Ok, I've seen this coming for a while now. Writing a kinematic character controller will not only grant me more precise power over my characters movement and collision resolution, but will also enable fine grained network synchronization if I decide to go that route in the future.

The basic algorithm of a kinematic character controller is this:

Vector3 velocity;
Vector3 position;
float friction;
float acceleration;
void Update()
    velocity += GetInput()*acceleration * Time.deltaTime;
	position += velocity * Time.deltaTime;
    velocity -= friction * Time.deltaTime * velocity;

Implement that and you can start moving about the environment and play with the acceleration and friction. Max speed and acceleration curves are simple extensions. I dunno, something about a little cube running on top of another cube with the right friction and acceleration looks and feels so good to me.

The problem here, is that you'll run through walls without any knowledge of their presence.

Enter collision detection, or rather, collision resolution.

A basic collision resolving algorithm looks like this:

void LateUpdate() {
	// check collisions
	int numOverlaps = Physics.OverlapBoxNonAlloc(m_transform.position, m_halfExtents, m_colliders, m_rigidBody.rotation, layerMask, QueryTriggerInteraction.UseGlobal);
	for (int i = 0; i < numOverlaps; i++) {
		Vector3 direction;
		float distance;
		if(Physics.ComputePenetration(m_boxCollider, m_transform.position, m_transform.rotation, m_colliders[i], m_colliders[i].transform.position, m_colliders[i].transform.rotation, out direction, out distance))
			Vector3 penetrationVector = direction*distance;
			Vector3 velocityProjected = Vector3.Project(velocity, -direction);
			m_transform.position = m_transform.position + penetrationVector;
			velocity -= velocityProjected;
			Debug.Log("OnCollisionEnter with " + m_colliders[i] + " penetration vector: " + penetrationVector + " projected vector: " + velocityProjected);
			Debug.Log("OnCollision Enter with " + m_colliders[i] + " no penetration");

Note that Physics.OverlapBox crashes on Android, so be sure to check out my work around post if you're planning on deploying to android.

Unity just added this Physics.ComputePenetration function in 5.6b1, so we're on the bleeding edge here. The only really interesting element here is that you must set the velocity to zero along the collision vector. We do that by using Vector3.Project to find what component of velocity needs to be eliminated. This makes it so when you hit the ground, for example, the -y element of velocity is eliminated. Also, I'm not sure this is the "correct" way of resolving collisions, maybe this should all be in FixedUpdate, and maybe you should check for (and resolve) the collisions before even moving the character.  Either way, this works for me and I'll update this blog post if and when I change my approach.

Now, there's the basics. But my player movement system is composed of three major systems: movement, ground detection & jumping.

The character's jump relies heavily on the rigid body system.

The character's jump relies heavily on the rigid body system.

I think it would be unwise for me to try to write a custom jump, since I'd have to write the collision resolution algorithm resolve angular velocity. So instead, I'll need to enable the rigid body on jump, apply the upward force and torque, and disable it when the character hits the ground again. It's starting to feel a little hacky, and I have an idea on how to fix the rigid body seams problem, so I'm gonna do what I do best in this situation and bust my buns to implement both this kinematic character controller, and fix the seams problems with the rigid body character controller, so I can get a good comparison between the two solutions. My next post will be about how I solved the rigid body seams problem without removing the rigid body.

An interesting tidbit of tying the jump, the ground detection and the movement together is this:

When you jump, the rigid body is activated, the kinematic character controller is disabled and the ground detection is told to wait at least 0.1f seconds before turning the kinematic character controller back on. This should be enough time for the rigid body system to get the character off the ground. When the ground detection detects that we're on the ground again, we (smoothly) stand the character up and turn on the kinematic movement again.

Now, I still haven't solved the seams problem. The collision resolution system described above has the exact same problem with seams that the rigid body system does. Actually, it's worse, as I'm sure PhysX's collision resolution is more advanced that what I've written above.

The final, final, final™ solution is going to be to merge the cube stacks into a series of quads, which play nicely with my kinematic character controller (but not with the rigid body). This will improve the rendering system and may allow me to turn off those 6452 colliders pictured above until they are needed.

Next up I'm going to write a blog post on how I solved the rigid body seams problem (while still using a rigid body) in my second solution to this problem. The solution exploits downward ray casts and physics layers to not collide with those blasted seams at all, but rather to collide with an AABB box collider when on the seams.

Update: Here is the followup post regarding solving the rigid body seams problem.