Unity – Move character with animation

Move character with animation (Unity)

In my previous post I made a short post about how to make a character move with the Character Controller component in the end. We can continue on this path and add some more to our script to make our character use a walking animation too. This is a tutorial on how to move character with animation through script. Thankfully there are already some great assets out there and one I found useful was the animation package called Basic Motions made by Kevin Iglesias.

If you download Basic Motion there you can drag the Prefab BasicMotionsDummy into your scene.

Then what I did was creating a new Animation controller for your character

Add your new Animation Controller to your Controller for your BasicMotionsDummy character. Then edit the new controller and expand the BasicMotions@Walk animation from the Basic Motions/Animations/Movement folder and pick the one called “BasicMotions@Walk01 – Forwards” into your controller. Also add the idle animation “BasicMotions@Idle01“. You can rename your animations to something more simple like Walk and Idle. Then add transition and add a transition from Any State to your Walk one, and do the same for Idle. Then go to the Parameters tab and add two new parameters, TryWalk and StopWalk. Click on the white arrow from Any State to your Walk animation and plus click to add a Condition, select “TryWalk”. Do the same for idle, click on the white arrow from Any State to your Idle and add “StopWalk”.

Now select so that you use your Animator Controller as Controller in your BasicMotionsDummy character.

Since we now have triggers which will play the animations we can trigger them through our scripts with simply animator.Trigger(“TryWalk”) or animator.Trigger(“StopWalk”) for example. Let’s take a look. Add a script called MainCharacter to your character controller 3D object with the following code:

using UnityEngine;
using UnityEngine.UI;

public class MainCharacter : MonoBehaviour
{
    private CharacterController controller;
    private Vector3 playerVelocity;
    private Animator animator;
    private float speed = 2.0f;
    private float gravityValue = -9.81f;

    private Vector3 prevPos;
    private bool isWalking;
    private bool walkingTriggered;
    private bool stopWalkingTriggered;
    void Start()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
    }

    void triggerWalk()
    {
        animator.SetTrigger("TryWalk");
        walkingTriggered = true;
        stopWalkingTriggered = false;
    }
    void triggerStopWalk()
    {
        animator.SetTrigger("StopWalk");
        stopWalkingTriggered = true;
        walkingTriggered = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (isWalking)
        {
            if(!walkingTriggered)
            {
                triggerWalk();
            }
        } else {
            if(!stopWalkingTriggered)
            {
                triggerStopWalk();
            }
        }

        isWalking = (prevPos.x != transform.position.x) || (prevPos.z != transform.position.z);
        
        prevPos = transform.position;
        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        controller.Move(move * Time.deltaTime * speed);

        if (move != Vector3.zero)
        {
            gameObject.transform.forward = move;
        }

        playerVelocity.y += gravityValue * Time.deltaTime;
        controller.Move(playerVelocity * Time.deltaTime);
    }
} 

I’ve also added the following components to my character (BasicMotionsDummy):

  • Capsule Collider: X -0.06114626 Y 1.019529 Radious: 0.4388537 Height 1.911906
  • Character Controller: X -0.06114626 Y 1.019529

In our code, you can now see that we utulize some new properties such as

    private Vector3 prevPos;
    private bool isWalking;
    private bool walkingTriggered;
    private bool stopWalkingTriggered;

These are all self explanitory properties and which will come handy in our functions for triggering our animations:

    void triggerWalk()
    {
        animator.SetTrigger("TryWalk");
        walkingTriggered = true;
        stopWalkingTriggered = false;
    }
    void triggerStopWalk()
    {
        animator.SetTrigger("StopWalk");
        stopWalkingTriggered = true;
        walkingTriggered = false;
    }

We have in our Start() method initialized the controller and the animator component which we can trigger animations on, which we do when we call the functions above. We will also keep track if we have started the animation as well.

        if (isWalking)
        {
            if(!walkingTriggered)
            {
                triggerWalk();
            }
        } else {
            if(!stopWalkingTriggered)
            {
                triggerStopWalk();
            }
        }

        isWalking = (prevPos.x != transform.position.x) || (prevPos.z != transform.position.z);
        
        prevPos = transform.position;

Whenever we are walking, we will check if the walking trigger event is actually not already triggered, if it is not, then we can proceed with triggerWalk().

The opposite goes for stopWalk, if we are not walking, and we have already not triggered the stopWalk event, then we will triggerStopWalk().

isWalking = (prevPos.x != transform.position.x) || (prevPos.z != transform.position.z);

We are always keeping track of if we are walking by comparing the current transform position of x and z with the previously stored property prevPos. If these two are’nt the same we are walking.

prevPos = transform.position;

Here we store the current position for the next incoming transform position value comparison (as above).

We have only one problem, we start walking the first thing we do, even if we stand still. That is because the isWalking is checking the previous value if it is same as before, which it isn’t since we don’t have a previous value which is the same. We can add another condition and check the input keys if we have touched them, maybe not the prettiest but it works. So with a new property named hasStartedMoving we set it to false first, and if we got Input.GetAxis from Horizontal or Vertical, we set the property to true, and can therefore use it to determine if we are Walking along with the other conditions. So full code below.

using UnityEngine;
using UnityEngine.UI;

public class MainCharacter : MonoBehaviour
{
    private CharacterController controller;
    private Vector3 playerVelocity;
    private Animator animator;
    private float speed = 2.0f;
    private float gravityValue = -9.81f;

    private Vector3 prevPos;
    private bool isWalking;
    private bool walkingTriggered;
    private bool stopWalkingTriggered;
    private bool hasStartedMoving = false;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
    }

    void triggerWalk()
    {
        animator.SetTrigger("TryWalk");
        walkingTriggered = true;
        stopWalkingTriggered = false;
    }
    void triggerStopWalk()
    {
        animator.SetTrigger("StopWalk");
        stopWalkingTriggered = true;
        walkingTriggered = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (isWalking)
        {
            if(!walkingTriggered)
            {
                triggerWalk();
            }
        } else {
            if(!stopWalkingTriggered)
            {
                triggerStopWalk();
            }
        }
       
        isWalking = ((prevPos.x != transform.position.x) || (prevPos.z != transform.position.z)) && hasStartedMoving;


        prevPos = transform.position;

        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 move = new Vector3(horizontalInput, 0, verticalInput);

        if(horizontalInput > 0 || verticalInput > 0)
        {
            hasStartedMoving = true;
        }

        controller.Move(move * Time.deltaTime * speed);

        if (move != Vector3.zero)
        {
            gameObject.transform.forward = move;
        }

        playerVelocity.y += gravityValue * Time.deltaTime;
        controller.Move(playerVelocity * Time.deltaTime);
    }
} 

That is about it! Hopefully you learned little bit of how you can make your character moving with an animation using a script in Unity with the CharacterController component.

Using Rigidbody

More popular method is to have so that your player character is using the Rigidbody instead, so that we can take use of the worlds physics easier. It would could look something like this.

using UnityEngine;
using UnityEngine.UI;

public class MainCharacterB : MonoBehaviour
{
    private Rigidbody _rigidbody;

    private Animator animator;
    [SerializeField]
    private float speed = 100.0f;
    [SerializeField]
    private float jumpPower = 150;

    private Vector3 prevPos;
    private bool isWalking;
    private bool walkingTriggered;
    private bool stopWalkingTriggered;
    private bool hasStartedMoving = false;

    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
        animator = GetComponent<Animator>();
    }

    void triggerWalk()
    {
        animator.SetTrigger("TryWalk");
        walkingTriggered = true;
        stopWalkingTriggered = false;
    }
    void triggerStopWalk()
    {
        animator.SetTrigger("StopWalk");
        stopWalkingTriggered = true;
        walkingTriggered = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (isWalking)
        {
            if (!walkingTriggered)
            {
                triggerWalk();
            }
        }
        else
        {
            if (!stopWalkingTriggered)
            {
                triggerStopWalk();
            }
        }

        isWalking = ((prevPos.x != transform.position.x) || (prevPos.z != transform.position.z)) && hasStartedMoving;
        prevPos = transform.position;

        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 move = new Vector3(horizontalInput, yAxis, verticalInput);
        if (horizontalInput > 0 || verticalInput > 0)
        {
            hasStartedMoving = true;
        }

        if (move != Vector3.zero)
        {
            // Set the forward direction of the transform  to be same as where we are going.
            transform.forward = move;
        }

        Vector3 vel = new Vector3(move.x * Time.deltaTime * speed, move.y * Time.deltaTime * speed, move.z * Time.deltaTime * speed);
        _rigidbody.velocity = vel;
    }
}

The “magic” is that we move our character like previously from the input of GetAxis Horizontal and Vertical, but this time we set the velocity of the rigidbody to move forward.

While we are at it, we should use FixedUpdate to get the best performance:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MainCharacterB : MonoBehaviour
{
    private Rigidbody _rigidbody;

    //private Rigidbody _rigidbody;
    private Animator animator;
    [SerializeField]
    private float speed = 100.0f;
    [SerializeField]
    private float jumpPower = 350;

    private Vector3 prevPos;
    private bool isWalking;
    private bool walkingTriggered;
    private bool stopWalkingTriggered;
    private bool hasStartedMoving = false;

    float yAxis;
    private float horizontalInput;
    private float verticalInput;

    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
        animator = GetComponent<Animator>();
    }

    void triggerWalk()
    {
        animator.SetTrigger("TryWalk");
        walkingTriggered = true;
        stopWalkingTriggered = false;
    }
    void triggerStopWalk()
    {
        animator.SetTrigger("StopWalk");
        stopWalkingTriggered = true;
        walkingTriggered = false;
    }

    private void FixedUpdate()
    {
        Vector3 move = new Vector3(horizontalInput, yAxis, verticalInput);

        if (move != Vector3.zero)
        {
            // Set the forward direction of the transform  to be same as where we are going.
            transform.forward = move;
        }
        Vector3 vel = new Vector3(move.x * Time.deltaTime * speed, _rigidbody.velocity.y, move.z * Time.deltaTime * speed);
        _rigidbody.velocity = vel;
    }

    // Update is called once per frame
    void Update()
    {
        if (isWalking)
        {
            if (!walkingTriggered)
            {
                triggerWalk();
            }
        }
        else
        {
            if (!stopWalkingTriggered)
            {
                triggerStopWalk();
            }
        }

        isWalking = ((prevPos.x != transform.position.x) || (prevPos.z != transform.position.z)) && hasStartedMoving;
        prevPos = transform.position;

        horizontalInput = Input.GetAxis("Horizontal");
        verticalInput = Input.GetAxis("Vertical");

        if (horizontalInput > 0 || verticalInput > 0)
        {
            hasStartedMoving = true;
        }
    }
}

0 0 votes
Article rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments