One of the problems with Unity's CharacterController, and how we solved it


Hi everyone, Hunter here, Gameplay Programmer on Iconoclast. I wanted to talk a bit this week about a specific issue we ran into while making Iconoclast, and how we resolved it. Warning: technical jargon ahead!

Unity's built-in CharacterController component can be very useful for setting up player movement in a 3D game; it automatically generates a capsule-shaped collider, it has built-in properties for things like detecting whether it's touching the ground, or determining what part of the player is colliding with something, and things like the player's step height and the maximum steepness of slopes they can climb can be easily set in the inspector.

 But one major caveat to keep in mind when using the CharacterController is that it doesn't interact with Unity's built-in physics system. This means that by default, a player with a CharacterController won't be effected by gravity, react to forces, or generate collision events. The lack of collision events presented a problem while developing Iconoclast.

The CharacterController does, in fact, have a built-in function for detecting collisions. OnControllerColliderHit is called whenever a game object with a CharacterController collides with something. However, OnControllerColliderHit is only called while the player is moving, and it gets called over and over continuously if the player stays in contact with an object. For these reasons, OnControllerColliderHit wasn't going to cut it for our purposes. If we wanted to, for example, play a sound effect when the player bumps into something, we couldn't use this function because it would play the sound effect repeatedly as long as the player is touching the object.

I was able to create a workaround for this issue using the following steps:

First, I drew a capsule slightly larger than the CharacterController's built-in capsule collider each frame, using Physics.OverlapCapsule. I assigned the function's results to a collider array, which I then assigned to a global list variable called lastCollided:




yCoord1 and yCoord2 represent the height (relative to the player) of the two spheres at the ends of the capsule. Our CharacterController's collider has a height of 1.5 meters, and our player's transform position is at the bottom of the collider, so our spheres' center points were 0.5 meters and 1 meter above our player's position, respectively. I made these values variables because our player can crouch, so their height may not always be the same. I used the TransformPoint function to convert these coordinates from our player's local space into world space.

Our CharacterController has a radius of 0.5 meters, and I found that drawing a capsule slightly larger than the collider gave the best results, so I drew a capsule with a radius of 0.6 meters.

The next step is handled in the CheckCollisions function:



In addition to the lastCollided list, I have another global list of colliders; this one is simply called collided. lastCollided contains every collider the player was touching in the last frame, and collided contains every collider the player is currently touching. Each frame I go through each collider in collided and check to see if the same collider is in lastCollided. If it is, I know the player is still touching that object, so I pass the collider's game object into a function called OnStay. If it isn't, I know the player is no longer touching that object, so I remove it from collided and pass its game object into a function called OnExit. Lastly, I check lastCollided for any colliders that aren't in collided. If there are any, I know the player has just collided with that collider, so I add it to collided, and pass its game object into my OnInitialHit function.

Now I have three functions: OnInitialHit, OnStay, and OnExit, that fulfill the same function as Unity's built-in functions, OnCollisionEnter, OnCollisionStay, and OnCollisionExit, respectively, but my functions can work on any game object, whether it uses a CharacterController, a Rigidbody, or neither.

Get Iconoclast

Leave a comment

Log in with itch.io to leave a comment.