Implementing Character Movement in Unity

Unity development notes for a 2D Metroidvania-style game

Unity tutorial (zero) usage of Unity and VS Unity tutorial (one) learning state machines Unity tutorial (two) implementing character movement Unity tutorial (three) implementing character jump Unity tutorial (four) collision detection Unity tutorial (five) implementing character dash Unity tutorial (six) implementing wall sliding Unity tutorial (seven) implementing wall jump Unity tutorial (eight) basic implementation of character attack Unity tutorial (nine) improving character attack

Unity tutorial (ten) building platform levels with Tile Palette Unity tutorial (eleven) camera Unity tutorial (twelve) parallax background

Unity tutorial (thirteen) enemy state machine Unity tutorial (fourteen) implementing enemy idle and movement Unity tutorial (fifteen) implementing enemy combat state Unity tutorial (sixteen) implementing enemy attack state Unity tutorial (seventeen) refining enemy combat state

Unity tutorial (eighteen) combat system - attack logic Unity tutorial (nineteen) combat system - hit feedback Unity tutorial (twenty) combat system - counterattack

Unity tutorial (twenty-one) skill system - basics Unity tutorial (twenty-two) skill system - clone skill Unity tutorial (twentty-three) skill system - throwing sword skill (basic implementation) Unity tutorial (twenty-four) skill system - throwing sword skill (variant implementation)

If you prefer Zhihu, the directory for Unity development of a 2D Metroidvania-style game learning notes

Preface

This article is a study note from the Udemy course The Ultimate Guide to Creating an RPG Game in Unity. If there are any errors, please point them out.

This section implements character movement.

Udemy course link

Corresponding video:

Creating Finite State Machine Setup Animator with State Machine Movement with State Machine Flip

This section requires some assets. Asset link: https://pan.baidu.com/s/1PnN2t7xOcNpPDLteLm4EJw?pwd=7pec. Password: 7pec

I. Overview

This section implements the functionality and state transitions related to the movement state in PlayerMoveState, and improves other classes as needed.

During this process, add a collider and rigidbody to the Player, and obtain the corresponding components and variables in the Player script to support the implementation of the features. After implementing basic movement, add flipping by creating the Flip() and FlipController() functions in the Player.

II. Create Animation

1. Create Enemy_Skeleton and its Animator

Drag the aset folder Graphics into Unity, and the asset path is Graphics->Main->Warrior_Sheet.

The sprites in this resource have already been split. If you want to learn how to split sprite sheets from scratch, refer to Unity tutorial (zero) usage of Unity and VS

Drag the first frame of the sprite sheet into the hierarchy panel under Player as a child object, and rename it to Animator.

At this time, the Animator is not in the center of the Player. Adjust the position of the Animator image to the center of the parent object Player. Note: At this point, the Toggle Tool Handle Position should be set to Pivot, otherwise the parent and child objects will move synchronously.

Pivot: Set the auxiliary icon to the actual pivot point of the game object (defined by the Transform component). Center: Set the auxiliary icon to the center position (based on the selected game object).

Adjust the position of the Animator in the right panel to the center of the Player. In this example, set x to 0.4.

At this point, when you select the Player, the image is in the center position.

Select the Animator in the hierarchy panel and add the Animator component. In the right panel, click Add Component and search for Animator and add it.

Right-click to create an Animator Controller, name it Player_AC. Right-click -> Create -> Animator Controller

Drag Player_AC onto the Animator component of the Animator.

Next, create the animation playerIdle. Select the Animator and click Create in the left Animation panel to create the animation playerIdle.

Select the first six frames of the sprite sheet and drag them into the idle state animation.

Set the sample rate to 10. If the sample rate option is not displayed in the Animation panel, click the three dots in the panel and then click Show Sample Rate to display the sample rate. Then change the sample rate to 10.

The idle animation is complete.

Create the playerMove animation, click Create New Clip to create it, and drag the frames numbered 6-13, following the same steps as above.

Create playerIdle and playerMove animations.

2. Connect the state machine

Create parameters Idle and Move for state transitions, which represent whether the character is in an idle state or moving state. Parameters -> plus -> Bool -> rename to Idle Parameters -> plus -> Bool -> rename to Move

Right-click the state and click Make Transition to connect the states.

Connect as shown in the figure.

The default starting state is playerIdle, and the connection for the idle state is already default. Set the transition condition to Move equals true. Click the transition and click the plus in the Conditions list to add the variable, select Move, and set the value to true.

Set the exit conditions for both states playerIdle -> Exit: Idle is false playerMove -> Exit: Move is false

In addition, some settings need to be made for the transition out of the state. For more detailed information on parameters, refer to the manual.

The parameter diagram is as follows:

Has Exit Time: remove Transition Duration: change to 0

III. Create Platform, Add Colliders and Rigidbodies

The character's movement needs to take place on a platform. We first create a platform. Right-click in the hierarchy panel Click right-click -> 2D Object -> Sprites -> Square -> Rename to Platform

Press T key to use the rectangle tool to edit the platform.

At this point, when you click Play, nothing happens. Because in Unity, collisions require two objects to have colliders, and one must have a rigidbody. Detailed explanation of 2D colliders and 2D rigidbodies can be found in the official 2D physics system reference.

Add colliders to the platform and the character. Click Platform, Add Component -> Box Collider 2D

Click Player, Add Component -> Capsule Collider 2D

As shown in the figure, the size of the collider does not match the character.

Click the button shown in the figure to open the collider editor and adjust the collider to roughly match the character's size.

Then, to make the character react according to physical rules, we also need to add a rigidbody component. Add Component -> Rigidbody 2D

After adding the rigidbody, if we directly implement movement, the following situation will occur:

To solve this problem, we can make the following settings:

In the rigidbody, set Constraints -> Freeze Rotation -> Z, freeze the Z-axis so that the character does not fall over during movement.

Interpolate: choose Interpolate, because sometimes the discrepancy between physics and graphics can cause visual jitter. Enabling interpolation can smooth it out.

Collision Detection: choose Continuous, to prevent the character from passing through other objects without detecting collisions.

IV. Implementation of Character Movement State Switches

The movement state should accept player input to make the character move accordingly. The switch between idle and movement states needs to determine if the player has movement input: if there is input, switch from idle to movement; if there is no input, switch back from movement to idle.

Above, we created two condition variables, Idle and Move, and we will set thier values to implement the switching of the character's animation. The process of switching is as follows:

(1) Switching between movement and idle states

When switching states, we need to switch the character's animation, so as shown in the figure, when entering and exiting the state, we need to set the value of the condition variable accordingly. When entering the state, set the condition variable corresponding to the state to true; when exiting the state, set it to false.

Since all state switches go through the same steps, we can write the part of setting the condition variable in the PlayerState class. The variable animBoolName in the class is the condition variable corresponding to the state in the Unity animator.

According to the official documentation of the Animator, we can call the SetBool function to set it.

Therefore, to set the condition variable, we need to first get the Animator component, which is done in the player since the player inherits from the MonoBehaviour class. Define the variable anim:

public Animator anim { get; private set; }

Then in Awake(), get the component, the function to get the component is as follows:

Since the Animator is a child of the player, use GetComponentInChildren to get it:

anim = GetComponentInChildren<Animator>();

In the Enter() and Exit() of the PlayerState class, set the condition variable, the code is as follows:

//Enter state
public virtual void Enter()
{
    player.anim.SetBool(animBoolName, true);
}

//Exit state
public virtual void Exit()
{
    player.anim.SetBool(animBoolName, false);
}

(2) Reading Input Information

Add the variable xInput to the base class PlayerState, so that input can be used by all states.

float xInput;

In Unity, input can be obtained using the Input class interface, which receives keyboard, mouse, touch screen, etc. input, and can be viewed and changed in Edit > Project Settings > Input Manager.

Here, we use float GetAxisRaw(string axisName), where axisName is the name of the input axis. According to the input information mentioned above, we select the Horizontal axis, which is managed by Left and Right and A and D keys, returning a value between -1 and 1. Details refer to the official Input manual.

In the Update() of PlayerState, assign xInput to get input at any time. The code is as follows:

public virtual void Update()
{
    xInput = Input.GetAxisRaw("Horizontal");
}

Run and press A, D keys, and you will see the animation switch.

(3) Setting Movement Speed

In the above content, we have already completed the state switching, but the character animation is still playing in place, because we have not set the speed in the direction of movement.

Speed should be set in the rigidbody component.

Define variables

public Rigidbody2D rb { get; private set; }

Get in Awake()

rb = GetComponent<Rigidbody2D>();

Write a function to set the speed

public void SetVelocity(float _xVelocity, float _yVelocity)
{
    rb.velocity = new Vector2(_xVelocity, _yVelocity);
}

Next, we need to set the speed when in the movement state, which is done in the Update() function of PlayerMoveState. We are moving horizontally, so set the x-direction speed to the input, and keep the y-direction speed unchanged. Since this process requires the rigidbody, for convenience, we write it in the PlayerSate base class.

protected Rigidbody2D rb;

public virtual void Enter()
{
    player.anim.SetBool(animBoolName, true);
    rb = player.rb;
}

PlayerMoveState sets the speed

public override void Update() { base.Update();

 player.SetVelocity(xInput, rb.velocity.y);

 if(xInput==0)
 {
     stateMachine.ChangeState(player.idleState);
 }

}

At this point, when pressing A and D keys, our character can move left and right, but the speed is too slow. This is because the xInput range is between -1 and 1.

We add a new variable moveSpeed to set the speed. Add a variable in the player and add a title for the movement information in the panel.

[Header("Move Info")] public float moveSpeed = 8.0f;

We can adjust the speed size on the panel until it is appropriate.

Change the code for setting the speed in the MoveState to multiply by moveSpeed

public override void Update()
{
    base.Update();

    player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y);

    if(xInput==0)
    {
        stateMachine.ChangeState(player.idleState);
    }
}

The effect is as shown in the figure:

V. Character Flipping

After the above steps, we have basically completed the movement function, but the character is still running backward when moving to the left.

We add two variables, the character's direction and whether the character is facing right. Set their default values

public int facingDir { get; private set; } = 1;
private bool facingRight = true;

Add the Flip() function in the player to complete the flipping function. Reverse the character's direction facingDir; reverse facingRight, if the character was originally facing right, after flipping, it faces left, and facingRight changes from true to false; if the character was originally facing left, after flipping, facingRight changes from true to false, so just invert it; finally, rotate the character 180 degrees along the y-axis.

public void Flip()
{
    facingDir = -1 * facingDir;
    facingRight = !facingRight; 
    transform.Rotate(0, 180, 0);
}

With the flip function implemented, we now write a function to control when to flip. We need to flip under the following conditions:

The character moves to the right (velocity to the right) but faces left The character moves to the left (velocity to the left) but faces right

Code as follows:

public void FlipController()
{
    if (rb.velocity.x > 0 && !facingRight)
        Flip();
    else if(rb.velocity.x < 0 && facingRight)
        Flip();
}

Call it in Update(). At this point, you will notice a problem, the character flips when stopping

I displayed xInput and rb.velocity.x, and found that when the key is released, although xInput is already 0, the velocity in the x direction of the rigidbody still has a small value. I'm not sure why, maybe because of Unity's physics simulation.

We can use the xInput value as a condition, make the following changes

public void SetVelocity(float _xVelocity, float _yVelocity)
{
    rb.velocity = new Vector2(_xVelocity, _yVelocity);
    FlipController(_xVelocity);
}
public void FlipController(float _x)
{
    if (_x > 0 && !facingRight)
        Flip();
    else if(_x < 0 && facingRight)
        Flip();
 }

The character moves and flips normally, and the movement function is complete.

Summary of complete code

Parts that have been modified

PlayerState.cs

Added animation switching and getting the rigidbody

//PlayerState: state base class using System.Collections; using System.Collections.Generic; using UnityEngine;

public class PlayerState {

protected PlayerStateMachine stateMachine;
protected Player player;

protected Rigidbody2D rb;

protected float xInput;
private string animBoolName;

// constructor
public PlayerState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName)
{
    this.stateMachine = _stateMachine;
    this.player = _player;
    this.animBoolName = _animBoolName;
}

// enter state
public virtual void Enter()
{
    // set animation play condition variable
    player.anim.SetBool(animBoolName, true);

    // get rigidbody
    rb = player.rb;
}

// update state
public virtual void Update()
{
    xInput = Input.GetAxisRaw("Horizontal");
}

// exit state
public virtual void Exit()
{
    // set animation play condition variable
    player.anim.SetBool(animBoolName, false);
}

}

PlayerIdleState.cs

Added switching to the movement state

//PlayerIdleState: idle state using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.XR;

public class PlayerIdleState : PlayerState { // constructor public PlayerIdleState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName) { }

// enter
public override void Enter()
{
    base.Enter();
}

// exit
public override void Exit()
{
    base.Exit();
}

// update
public override void Update()
{
    base.Update();

    // switch to movement state
    if(xInput!=0)
    {
        stateMachine.ChangeState(player.moveState);
    }
}

}

PlayerMoveState.cs

Added setting movement speed and switching to idle state

public class PlayerMoveState : PlayerState { // constructor public PlayerMoveState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName) { }

// enter
public override void Enter()
{
    base.Enter();
}

// exit
public override void Exit()
{
    base.Exit();
}

// update
public override void Update()
{
    base.Update();

    // set movement speed
    player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y);

    // switch to idle state
    if(xInput==0)
    {
        stateMachine.ChangeState(player.idleState);
    }
}

}

Player.cs

Added getting Animator, rb, and other components, defining movement-related variables, setting speed, implementing flipping, etc.

//Player: player using System.Collections; using System.Collections.Generic; using UnityEngine;

public class Player : MonoBehaviour {

[Header("Move Info")]
public float moveSpeed = 8f;
public int facingDir { get; private set; } = 1;
private bool facingRight = true;

#region Components
public Animator anim { get; private set; }
public Rigidbody2D rb { get; private set; }
#endregion


#region States
public PlayerStateMachine StateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
#endregion

// create object
private void Awake()
{
    StateMachine = new PlayerStateMachine();

    idleState = new PlayerIdleState(StateMachine, this, "Idle");
    moveState = new PlayerMoveState(StateMachine, this, "Move");

    anim = GetComponentInChildren<Animator>();
    rb = GetComponent<Rigidbody2D>();

}

// set initial state
private void Start()
{
    StateMachine.Initialize(idleState);
}

// update
private void Update()
{
    StateMachine.currentState.Update();
}

// set speed
public void SetVelocity(float _xVelocity, float _yVelocity)
{
    rb.velocity = new Vector2(_xVelocity, _yVelocity);
    FlipController(_xVelocity);
}

// flip implementation
public void Flip()
{
    facingDir = -1 * facingDir;
    facingRight = !facingRight;
    transform.Rotate(0, 180, 0);
}
// flip control
public void FlipController(float _x)
{
    if (_x > 0 && !facingRight)
        Flip();
    else if(_x < 0 && facingRight)
        Flip();
}

}

Tags: unity 2D Game Development Character Movement State Machine animation

Posted on Sat, 09 May 2026 03:13:04 +0000 by bsamson