In this article, we’ll be implementing a Simple and Reusable 2D Character Controller for our platformer that can move around and jump.
What is a State Machine
The first thing you should do when writing a script to handle player movement, is to first define the allowed movements. By this, I mean: can a crouched player jump? Are double jumps allowed?
A common way to model this kind of restriction is through the creation of a State Machine: at any point in time, the player is in a given state, which defines what a given input will trigger. For example, pressing the spacebar while standing would make the player jump while this it would have no effect when the player is crouched down.
For our tutorial, we will be using this system, as I think it meshes well with another feature: Character Animation.
Now, let’s get started on the implementation!
Implementing states and transitions for our State Machine
These are the 3 states will be implementing, and the logic will go as follows:
If the player is not pressing any of the left,right or up key, and the player is on the ground, then the player is IDLE.
If the player has not pressed the up key but the left or right key is being pressed, the player is RUNNING.
If the player has pressed the up key, it gets into the JUMPING state and cannot jump again until it touches the ground. When the playertouches the ground, its state switches back to IDLE or RUNNING depending on currently pressed keys.
We’ll create our PlayerController script, and give it some initial properties:
publicclassPlayerController : MonoBehaviour{publicCharacterState mPlayerState = CharacterState.IDLE; [Header("Movement Settings")]publicfloat mSpeed =5.0f;publicfloat mJumpStrength =10.0f;// Setting the animated sprites for the different states [Header("State Sprites")]publicRuntimeAnimatorController mIdleController;publicRuntimeAnimatorController mRunningController;publicRuntimeAnimatorController mJumpingController;// We'll be caching the animator component to easily change current player animationprivateAnimator _mAnimatorComponent;// Tracking the direction our player is goingprivatebool _bIsGoingRight =true;privatebool _bPlayerStateChanged =false;voidStart() { _mAnimatorComponent = gameObject.GetComponent<Animator>(); _mAnimatorComponent.runtimeAnimatorController = mIdleController; }}
Now, we’ll seperate our State Machine in 2 parts: the movement part, and the state changes.
Implementing state changes in the PlayerController
At the beginning of our Update Method, we check for changes in the player state according to what we described in the previous section. I usually try to give descriptive names to all my variables, so the next section should be self-explaining.
If you pay attention, you’ll see that while I said the actual movement stuff would be handled later, it turns out I am accessing the RigidBody2D component of my character and setting a velocity when the player enters the jumping state. This is because while the movement handling afterwards handles the movement of the player WHILE it is in its current state, the jump must occur WHEN the jump input is registered so this is the easiest way to handle it.
Additionally, there is no way for the player to give inputs that would take the character out of the Jumping state in order to prevent infinite consecutive jumps.
What we do here is we start a Coroutine that checks whether the player is back on the ground after its jump. Once it has, the state is changed back to Idle or Running, and the Player can now jump again.
Now that we have set the appropriate state for our character, we can detect the inputs and move our character very easily. The code should speak for itself here:
The actual animation is handled by the animation controllers, so we are in effect just swapping the controllers we specified as properties of our character controller script.
We’ll also be checking the direction where our player is going, and flipping the sprite accordingly!
We now have a functionnal player character, that implements restriction on jumping and switches from an Idle to Running animation when appropriate, and vice-versa. That’s great stuff!