Common Gamepad Input Mistake Caused by Vector Normalization - Unity Game Development Tutorial
In this Unity game development tutorial we're going to at how to solve a common mistake that's often made when processing input from a gamepad.
You can either watch the video version below or continue reading for written instructions.
Right, we're going to start with the project we created in our 'Character Rotation' video. In this video we created a character that can move around our scene based on input from the keyboard or the gamepad.
The problem is that the character always moves at the same speed no matter how far we move the gamepad thumbstick. There is no difference between moving the thumbstick all the way in a direction and moving it a fraction.
This probably isn't what we want. In most games, the character's speed will reflect the thumbstick movement. For example, the character may walk slowly when the thumbstick is moved a fraction and run when it is moved all the way.
Right, let's look at our script to see where we're going wrong.
using UnityEngine; public class PlayerMovement : MonoBehaviour { public float speed; public float rotationAnglePerSecond; void Update() { float horizontalInput = Input.GetAxis("Horizontal"); float verticalInput = Input.GetAxis("Vertical"); Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput); movementDirection.Normalize(); transform.Translate(movementDirection speed * Time.deltaTime, Space.World); if (movementDirection != Vector3.zero) { Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up); transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationAnglePerSecond * Time.deltaTime); } } }
We're getting the input from the horizontal axis and the vertical axis. These input values will range from -1 to 1 and will correctly reflect the movement of the thumbstick. For example, if the thumbstick is halfway to the right, the value of the horizontal input will be 0.5.
We then create a vector from these input values for our movement direction. Then we normalize this vector before moving the character in this direction.
The problem is actually introduced in the line where we normalize the vector.
To understand the problem let's look at what actually happens when we normalize a vector.
If our vector is 1 on the X and Z axis then the magnitude of our vector is 1.414.
Normalising the vector maintains the direction but sets the magnitude to 1. So, in this scenario normalising our vector will reduce the size on the X and Z axis to 0.7, making the magnitude 1 while maintaining the direction.
In this scenario, the normalisation gives us what we want and ensures our direction vector doesn't have a magnitude greater than 1.
Now let's look at this scenario.
If our vector is 0.5 on the X and Z axis then the magnitude of our vector would be 0.707
If we normalize this vector it will increase the size on the X and Z axis to 0.7.
This is what's happening with our gamepad input. No matter how small the thumbstick is, the normalisation will increase the magnitude to 1.
Let's go back to the code and make the following changes to fix this.
using UnityEngine; public class PlayerMovement : MonoBehaviour { public float speed; public float rotationAnglePerSecond; void Update() { float horizontalInput = Input.GetAxis("Horizontal"); float verticalInput = Input.GetAxis("Vertical"); Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput); float magnitude = Mathf.Clamp01(movementDirection.magnitude); movementDirection.Normalize(); transform.Translate(movementDirection * magnitude * speed * Time.deltaTime, Space.World); if (movementDirection != Vector3.zero) { Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up); transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationAnglePerSecond * Time.deltaTime); } } }
We still want to normalize the vector, as a direction vector should have a magnitude of 1. But before we do we're storing the magnitude. We then multiply by this magnitude when moving our character.
Also, to limit the magnitude to a range of 0 to 1 we are using the Clamp01 method.
Let's save the game and switch back to Unity.
We'll press play to try this out.
Now when we partially move the thumbstick in a direction our character moves slowly, and when we move the thumbstick all the way it moves at full speed.
Thanks.
Why normalise it then multiply by the clamped magnitude? Why not just use the clamped value without normalisation?
ReplyDelete