How to Approach Designing a Complex Character Controller

Hey everyone,
I’m sure this has been asked before (however I couldn’t find it when I tried searching) but I’m attempting to create a fairly complex character controller. Ideally the player should be able to move around with a third person camera (this part is easy enough) but in addition to that it should be possible to knock the player down (maybe do a temporary switch to ragdoll physics) as well as crouch and climb surfaces.

I’ve previously attempted this using the 3DBuzz third person character controller tutorial (I swapped out the CharacterController with a Rigidbody) but everything feels kind of muddy and it’s generally not fun to move around. I’m also not even sure if I am taking the correct approach by using a rigidbody however that seemed to be required as I can’t rotate a CharacterController when the player is knocked down (this seems to be needed so the body doesn’t clip through doors but could fly over a short barrier). Overall I’m pretty happy with the camera, my issue is with moving the player in a way that feels good to the player and making sure it behaves properly when it comes to collisions.

Work in small steps, use one script for moving, crouching, knock down, and climbing. Rigidbody is a must have, you will need it for collisions and gravity.

I guess your character controller uses “AddForce()”, but I don’t like it at all. It just feels muddy. I use “rigidbody.velocity =” for a more responsive movement. How is the game controlled? For keyboard you could use something like that:

if(Input.GetKey(KeyCode.W)){
rigidbody.velocity = new Vector3 (0, rigidbody.velocity.y, speed);
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.forward), lerpTime);
return;
}
if(Input.GetKey(KeyCode.S)){
rigidbody.velocity = new Vector3 (0, rigidbody.velocity.y, -speed);
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(-Vector3.forward), lerpTime);
return;
}
if(no button){
rigidbody.velocity = new Vector3 (0, rigidbody.velocity.y, 0);
}
etc.

For better transitions you could also Lerp the speed. This technique offers a very responsive movement.

An accurate physics-based model will feel muddy. Players are used to pressing a direction input and seeing their character move immediately without waiting for forces to propagate through the physics simulation. That’s why CharacterController exists. Responsive player controls aren’t physically accurate. You can do something similar with a rigidbody character by setting it kinematic. If you bypass physics for your player, though, you need to simulate parts of it yourself such as gravity.

Stepping back to the bigger picture, consider using layered FSMs. It’ll make your controller much simpler and more extensible. They work like the layered FSMs in an Animator Controller except at a higher conceptual level, encapsulating states such as crouching, falling, reloading, etc. The active character states will control the animator controller.

For example, you might have a base layer with these states:

  • Idle

  • Moving

  • Sprinting

  • Climbing

  • Jumping

  • Falling

  • etc.

When the character is in a state, you only need to check the inputs and transitions that are valid in that state. For example, in the base layer’s Falling state my character can’t sprint, crouch, jump, climb, etc. The only transitions are landing or dying. So the implementation of this state is really short and clear: keep playing the falling animation, and check for landing or dying. If the character becomes grounded, transition to the Landing state. When in the Landing state, check for the landing animation to finish. When it’s done, transition to the Idle state.

Higher layers (such as upper body) will probably need to check transitions based on input and on lower states. For example, say the character’s upper body layer is in the Reloading state. When the base layer changes to Falling, the upper body layer might transition to a FlappingArms state. The FlappingArms state might exclude any transitions to combat-related upper body states such as firing or reloading. When the base layer changes to Landing, transition the upper body to its upper body Idle state.

Also, use a virtual controller. Don’t read input directly (e.g., no Input.GetKey) except for in scripts whose only purpose is to read input. Your script layers should look something like this:

  • [PlayerInput] (reads Input.GetKey() etc., sets virtual controller inputs)
  • [VirtualController]
  • [HighLevelCharacterController] (reads virtual controller, maintains FSM)
  • [HighLevelAnimationController]
  • [Animator]

This isolates the character controller from the input method. If you end up using a gamepad, or an Ouya controller, or AI control, or some other input method, you only need to swap out [PlayerInput] and everything below it will work fine without needing any modification.

10 Likes

Great discussion. Probably better in a physics or scripting section.
Gigi

Interesting. So judging by the replies my original alpha design was on the correct path however it seems I should avoid using the AddForce function. My main source of worry was not maintaining accurate physics (except when using ragdoll to knock the player over from an explosive force) but to make sure they wouldn’t block the movement other characters when they have gone into a crawling state.

On that note, by manually adjusting the velocity, will this allow for players to scale steep surfaces or will the physics engine stop them like it does when I attempt to add force while moving up a slightly too steep ramp? If it does, is there a good way around this or should I just add a little extra to velocity.y?

That’s probably a better question for the Scripting forum, since it gets more into the weeds of implementation. You’ll get better feedback there.

But…if it’s in Physics now…where was it before?!?

On topic, it’s important to know what your goal is. It’s worth noting that the overwhelming majority of character controllers do not use rigidbody physics as their primary form of movement. This is because in the real world, people are not capsules, so approximating our movement by adding forces to push around a giant capsule is not a very good model for movement. As well, in games the goal for movement isn’t usually realism, but rather giving the player a sense of control or a specific feel.

In the link in my signature I have a custom character controller that implements most of the stuff TonyLi talks about above…but bear in mind it’s a fully custom controller, in that all the physics collision is done manually so it may be somewhat advanced. It does have an FSM included so it does demonstrate that concept.

1 Like