Creating a farming RPG in Unity - Part 1: Movement

Creating a Farming RPG (like Harvest Moon) in Unity — Part 1: Movement

This article is a part of the series:
Creating a Farming RPG (like Harvest Moon) in Unity

Ever wanted to create a game like Harvest Moon in Unity? Check out Part 1 of our guide here, where we go through how to create animations and movement for the player character.

This is a loose transcription of our accompanying video guide on Youtube. We suggest you watch the video guide instead and use this article as a supplement to it.

Update (2 September 2021): The narrated audio has been amplified, so it should be much easier to hear now.

1. Creating the project

Create a new Unity project with the 3D Template.

Creating the Farming RPG project
Creating our Farming RPG project.

2. Finding assets to work with

You can source assets on your own, but for this particular tutorial we will be using models and animations from Mixamo, a free online service under Adobe that provides 3D Characters, Rigging and Animations for free.

Simply go to their website and log in with an Adobe account (sign-ups are free, with no subscription required).

Mixamo website
The Mixamo website (as of 19 July 2021).

a. Finding a player model

Find a suitable model and download the FBX. For this tutorial, we will be using the Timmy character (you need to log-in to Mixamo to access the link).

Downloading the Timmy Mixamo character
We chose Timmy because he resembles the typical Harvest Moon character.

b. Finding animations

For this tutorial we will need the Idle, Walking, and Running animations. You can find them by switching to the Animations tab, and picking out the animations:

Searching for an appropriate animation
Find a suitable match for each of the animations we need.

As you select an animation, they will show you a preview of the animation with your chosen model. Ensure that ‘In Place‘ is checked for movement animations, so that the animation does not use root motion. Not doing so will mess up the positioning of our character in the game later.

Mixamo movement animations
The parameters you can tweak vary depending on the animation you select.

When you download the animations, make sure that they are in FBX format, and Without Skin under the Skin options.

Mixamo animation download prompt
The Without Skin allows us to download the file without the model, since we already have our model.

Article continues after the advertisement:


3. Prepping the imported files in Unity

We’ll create a new folder called Imported Asset, and import all our downloaded files there.

The Imported Assets folder
The Imported Assets folder.

a. Player character textures

If you were to drag the player character in the scene, it would not have any textures loaded in. In the model file’s Import Settings, go to the Materials tab and click Extract Textures.

Extract textures from the Timmy model
Click on the model file in your Project window to access its import settings.

You will be asked to select the folder to generate the texture files in. Simply select the Imported Asset folder and the textures should load in. You might get the following popup:

Normal Map fix now prompt
If you see this, just click on Fix Now.

Click ‘Fix now‘ and they will mark the texture as a normal map for you.

b. Configuring the Rig

On the character model’s import settings, go to the Rig tab.

The default Rig configuration of the character
The default Rig configuration of the character.

Set the following parameters to these values:

  1. Animation Type: Humanoid
  2. Avatar Definition: Create From This Model

Then, click Apply and an Avatar for your model should be created.

Creating a Humanoid Avatar for our model.
Creating a Humanoid Avatar for our model.

On the FBX files containing our animations, set the following parameters:

  1. Animation Type: Humanoid
  2. Avatar Definition: Copy From Other Avatar
  3. Source: Set to the newly-created Avatar (find it in the Project window)

Similarly, once done, click Apply.

Settings for the animation FBX files.
Settings for the animation FBX files.

4. Setting up the player Prefab

Create a folder called Prefabs. Drag an instance of your model into the scene, rename it to “Player”, and drag it to the Prefabs folder to save it as a new Prefab.

Creating a prefab for the player
Creating a prefab for the player.

Create another folder called Animation. In the folder, create another folder, Controllers. In there, create an empty Animator Controller called Player, then drag it to the Player prefab’s Animator component.

Attaching the Animator Controller to the player
Attaching the Animator Controller to the player.

The thing about imported animations is that they are locked as readonly. This prevents us from modifying the animations in any way, such as tweaking the motion, or adding Animation Events.

On each of the imported animation FBX files, expand the file, select the animation. Duplicate them and rename the copied Mixamo animation to its respective names.

Renaming your animations
For example, for the running animation, rename mixamo.com to Running. Refer to the video for more information.

Move all the copied animations to the Animation folder.

Your animations should be in the right folder
Your animations should be in the right folder.

Drag the newly copied animations into the player’s Animator. Create a float parameter called Speed, and a Boolean parameter called Running.

Setting up animation states and parameters
Setting up the animation states and parameters in the Controller.

Add a Character Controller component to the prefab and resize it to fit the model.

Attaching a Character Controller to our player
Attaching a Character Controller to our player.

5. Coding the player movement

Create a new Scripts folder and a new script file called PlayerController. Add it as a component to the Prefab.

Adding the PlayerController script
Adding the PlayerController script.

a. Getting the components

We will need to access the Unity components in our PlayerController script to get them to work together.

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
       
    }
}

Article continues after the advertisement:


b. Determining the facing direction

First, let’s set up a function within the Update() method to handle the movement-related actions.

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {

    }
}

We want the player character to respond to our button presses. Go to Edit > Project Settings > Input Manager. Take note of the “Horizontal” and “Vertical” axes. These are the Input axes we will be keeping track of in our script.

Input axes in the Input Manager
Input axes in the Input Manager.

We will store the inputs from the Horizontal and Vertical axes as float values. We can then use these float values to determine the direction the player should be facing, like so:

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {
        //Get the horizontal and vertical inputs as a number
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //Direction in a normalised vector
        Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;

        //Check if there is movement
        if (dir.magnitude >= 0.1f)
        {
            // Look towards that direction
            transform.rotation = Quaternion.LookRotation(dir);
        }


    }
}

If everything is working correctly, your player character should face the direction of your W A S D or keys.

c. Moving the player

Calculating the direction is already half the work done. The CharacterController.Move function allows us to move our player in our desired direction. Since we stored our direction as a normalised vector, we just have to multiply it by our desired speed with time to get our player to move accordingly, like so:

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;

    private float moveSpeed = 4f;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {
        //Get the horizontal and vertical inputs as a number
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //Direction in a normalised vector
        Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;
        Vector3 velocity = moveSpeed * Time.deltaTime * dir;

        //Check if there is movement
        if (dir.magnitude >= 0.1f)
        {
            //Look towards that direction
            transform.rotation = Quaternion.LookRotation(dir);

            //Move
            controller.Move(velocity); 
            
        }

    }
}

With this, our character can move around accordingly.

d. Play the walk animation

Although our character is able to move about, it doesn’t have a corresponding animation to match. Having our character float around in space isn’t a good look. Thus, let’s set up our animator transitions with the Speed (float) parameter this way:

Now we just need to work with our Animator parameters through our script:

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;

    private float moveSpeed = 4f;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {
        //Get the horizontal and vertical inputs as a number
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //Direction in a normalised vector
        Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;
        Vector3 velocity = moveSpeed * Time.deltaTime * dir;

        //Check if there is movement
        if (dir.magnitude >= 0.1f)
        {
            //Look towards that direction
            transform.rotation = Quaternion.LookRotation(dir);

            //Move
            controller.Move(velocity); 
            
        }

        //Animation speed parameter
        animator.SetFloat("Speed", velocity.magnitude); 

    }
}

Article continues after the advertisement:

Save Soil

The Save Soil Movement

Poor farming practices and policies across the world have led to the degradation of agricultural soils in the world. This has led to the loss of nutritional value in our food over the years, and it will affect our food production capabilities in the coming decades.

Learn more about Save Soil →


e. Sprinting

We also need a button that we can hold to make the player sprint when moving. Add a Sprint button into the Input Manager by increasing its size and configuring it like so:

Setting the Sprint key
We set our Sprint key to both the Shift buttons.

Let’s also implement the button in our script:

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;

    private float moveSpeed = 4f;

    [Header("Movement System")]
    public float walkSpeed = 4f;
    public float runSpeed = 8f;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {
        //Get the horizontal and vertical inputs as a number
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //Direction in a normalised vector
        Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;
        Vector3 velocity = moveSpeed * Time.deltaTime * dir;

        //Is the sprint key pressed down?
        if (Input.GetButton("Sprint"))
        {
            moveSpeed = runSpeed;
        } else
        {
            moveSpeed = walkSpeed;
        }

        //Check if there is movement
        if (dir.magnitude >= 0.1f)
        {
            //Look towards that direction
            transform.rotation = Quaternion.LookRotation(dir);

            //Move
            controller.Move(velocity); 
            
        }

        //Animation speed parameter
        animator.SetFloat("Speed", velocity.magnitude); 

    }
}

Your character should be moving faster when you hold the Shift (sprint) key. Let’s make our player character play the running animation when it happens.

On the Animator, set up the transitions accordingly:

Now we get the script to work with the transitions:

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;

    private float moveSpeed = 4f;

    [Header("Movement System")]
    public float walkSpeed = 4f;
    public float runSpeed = 8f;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {
        //Get the horizontal and vertical inputs as a number
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //Direction in a normalised vector
        Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;
        Vector3 velocity = moveSpeed * Time.deltaTime * dir;

        //Is the sprint key pressed down?
        if (Input.GetButton("Sprint"))
        {
            //Set the animation to run and increase our movespeed
            moveSpeed = runSpeed;
            animator.SetBool("Running", true);
        } else
        {
            //Set the animation to walk and decrease our movespeed
            moveSpeed = walkSpeed;
            animator.SetBool("Running", false);
        }

        //Check if there is movement
        if (dir.magnitude >= 0.1f)
        {
            //Look towards that direction
            transform.rotation = Quaternion.LookRotation(dir);

            //Move
            controller.Move(velocity); 
            
        }

        //Animation speed parameter
        animator.SetFloat("Speed", velocity.magnitude); 

    }
}

Everything should work relatively fine now, except for this: if we hold Shift without pressing any of the moving keys, the player character will be stuck in the running animation. To fix this, just add a transition from running to idle, like so:

Creating the running to idle transition
Creating the Running to Idle transition.

Article continues after the advertisement:


6. Conclusion

That’s it for this part. We will be working on the camera movement and farmable land in the next part. Here’s what your code should finally look like:

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    //Movement Components
    private CharacterController controller; 
    private Animator animator;

    private float moveSpeed = 4f;

    [Header("Movement System")]
    public float walkSpeed = 4f;
    public float runSpeed = 8f;


    // Start is called before the first frame update
    void Start()
    {
        //Get movement components
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>(); 


    }

    // Update is called once per frame
    void Update()
    {
        //Runs the function that handles all movement
        Move();
    }

    
    public void Move()
    {
        //Get the horizontal and vertical inputs as a number
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //Direction in a normalised vector
        Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;
        Vector3 velocity = moveSpeed * Time.deltaTime * dir;

        //Is the sprint key pressed down?
        if (Input.GetButton("Sprint"))
        {
            //Set the animation to run and increase our movespeed
            moveSpeed = runSpeed;
            animator.SetBool("Running", true);
        } else
        {
            //Set the animation to walk and decrease our movespeed
            moveSpeed = walkSpeed;
            animator.SetBool("Running", false);
        }


        //Check if there is movement
        if (dir.magnitude >= 0.1f)
        {
            //Look towards that direction
            transform.rotation = Quaternion.LookRotation(dir);

            //Move
            controller.Move(velocity); 
            
        }

        //Animation speed parameter
        animator.SetFloat("Speed", velocity.magnitude); 



    }
}

Here are all the animation transitions:

Creating the running to idle transition
All our animation transitions.
The Idle to Walking transition
The Idle to Walking transition.
The Walking to Idle transition.
The Walking to Idle transition.
The Walking to Running transition.
The Walking to Running transition.
The Running to Walking transition.
The Running to Walking transition.

Article continues after the advertisement:

Save Soil

The Save Soil Movement

Poor farming practices and policies across the world have led to the degradation of agricultural soils in the world. This has led to the loss of nutritional value in our food over the years, and it will affect our food production capabilities in the coming decades.

Learn more about Save Soil →


There is one comment:

Leave a Reply

Your email address will not be published. Required fields are marked *

Note: You can use Markdown to format your comments.

For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

I agree to these terms.

This site uses Akismet to reduce spam. Learn how your comment data is processed.