Doers of Stuff.org

A place to Do Stuff and see Stuff Done…

Simple Player Movement in Unity

Today I started interacting with my player object. Simple left, right, up and down. The big thing about doing this was the reminder of the link between the Unity Editor and the corresponding code. To get player input, you have to interact with the Unity Input system. You can find this in the Unity Editor by navigating to Edit -> Project Settings -> Input Manager -> Axes. Expanding the Axes, you will see all the options and their default values.

By using a little intuition and the tool tips/code completion in your editor, you can often just figure out what you need. In this case, type ‘Input.’ into your editor. In fact, you will only have to get as far as ‘In’ when the tool tip offers up a suggestion.

Visual Studio is suggesting the Input class which is part of the UnityEngine package (which we included at the top of our code with using UnityEngine;) It even tells us this is the Interface to the Input System. So we know we want Input, not InputManager or any other such thing. If we accept that and then add the period we get our next hint.

Now, you might have guessed we’d be looking for something like Input.Horizontal(). However, if you scroll through the whole list you won’t find that. In fact, you won’t find any methods directly corresponding to any of the many Axes listed in the Input Manager. However, scrolling through more slowly and examining the help text, we get to GetAxis()which we see takes a string as input. This is the second way Unity will allow you to access data. Where it doesn’t make sense to have a dedicated method, you will instead be given a generic ‘Get()‘ method where you pass the name of the parameter you are looking for. So again, using a well-reasoned guess, it is reasonable to try Input.GetAxis("Horizontal").

This still leaves the question of what exactly we are getting from this parameter. One way to find out is to make use of the Unity Editor and the Inspector. Create a public variable of type float. This is a guess, sort of inferred by the help text but is something we can validate, again with the help of our every helpful code editor. Try the following:

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;
    public string horizontalInput;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        horizontalInput = Input.GetAxis("Horizontal");
    }
}

You should notice the red squiggly line under Intput.GetAxis(). If you mouse over it, you should see a pop up with an error message. This message is telling you that horizontalInput is set as a string, but GetAxis() is returning a float. This tells us our variable horizontalInput in the code above needs to be changed to a float.

If we do this and play our game we can now hit the left and right keys. As we saw in the Input Manger, this would mean using either the left/right arrow keys or the A/D keys. As you do this, watch the value in the Inspector. Don’t look at the player game object. We haven’t hooked the script up to it yet. In the Inspector however, you will see the value start at 0 and then swing from -1 to 1.

So know we know what method to call and with it returns. Namely, 0 for no input, -1 for left and 1 for right. So now we have to decide how to make use of that.

Well, we already know how to make the player move using the transform.Translate() method. We know that transform.Translate(new Vector3(1,0,0)) will move the player endlessly to the right. This is functionally the same as transform.Translate(Vector3.right) but will arguably make the next step a bit more obvious. All we need to do is capture the user input in a variable like we did above and multiply it by the movement. Yep, multiply it. After all, the value returned by GetAxis() is either -1, 0 or 1. If we multiple that means our right movement of 1 dictated by new Vector3(1,0,0) or Vector3.right will either be 0 if there is no input at all, -1 if we press the left key and 1 if we press the right.

If we extend that same thinking to the Vertical (up/down) input we get the following code.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput   = Input.GetAxis("Vertical");
        transform.Translate(new Vector3( 1 * horizontalInput
                                       , 1 * verticalInput
                                       , 0
                                       ) );

    }
}

Since we already know what the GetAxis() calls will return, we can move them out of the global area and keep them local to the Update() method. Furthermore, if we examine our code, we see the chance for a few optimizations. First, multiplying by 1 for the x and y axis in our Vector3 is not really necessary and can be removed. Second, do we really need to store the user input in a variable? Maybe, maybe not. It depends on if we will need those values anywhere else in the Update() method. It also depends on what you think is more readable. Sometimes, the chain of methods is so long and cryptic it makes sense to save it in a well named variable, just so the code remains self-documenting, rather than turning itself into a brain teaser.

In this case however, the method call is short enough and obvious enough, we might consider converting the above to the following.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                       , Input.GetAxis("Vertical")
                                       , 0
                                       ) );

    }
}

This leaves just two more things. First, the Update() method is called once per frame, which is approximately 60 frames per second. The default unit of movement is one meter. This means our player will move at a rate of sixty meters per second. That’s fast. To slow it down to a more sensible one meter per second we need to do more math. Simplistically, this means if we want our player game object to move one meter per second, then on every update, we want to move 1/60th of that distance. Thus, we could just divide our whole Vector3 by sixty. We don’t have to divide each argument individually, we can simply divide the entire Vector3 and the math will distribute through.

There is a flaw with that though. Due to the processing vagaries of each individual computer, Update() is called approximately sixty times per second. To get something more exact, Unity provides a useful property called Time.deltaTime. This property holds the actual time between the current and previous call to Update()which provides for more accurate calculations.

The final thing is you may find one meter per second too slow. That’s where the mySpeed variable comes in. We can use that to fine tune our player speed. Adding these two in, we get the following.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                       , Input.GetAxis("Vertical")
                                       , 0
                                       ) * Time.deltaTime * mySpeed);

    }
}

Attach our script to the player game object and our simple player movement is now complete.

Leave a Reply

Simple Player Movement in Unity

by Robert time to read: 5 min
0