|
From Unity in Action, Third Edition by Joseph Hocking This article is an excerpt from chapter 8, in which the reader learns to program movement controls for a character that has been imported into Unity. |
Take 35% off Unity in Action, Third Edition by entering code fcchocking3 into the discount code box at checkout at manning.com.
Once a character model is imported into Unity, it’s time to program controls for moving around the scene. Let’s program camera-relative controls that’ll move the character in various directions when arrow keys are pressed, as well as rotate the character to face those different directions.
Implementing camera-relative controls involves two primary steps: first rotate the player character to face the direction of the controls, and then move the character forward. Let’s write the code for these two steps next.
Rotating the character to face movement direction
First you’ll write code to make the character face in the direction of the arrow keys. Create a C# script called RelativeMovement
, and write the code from listing 1. Drag that script onto the player character, and then link the camera to the target
property of the script component (just as you linked the character to the target of the camera script). Now the character will face different directions when you press the controls, facing directions relative to the camera, or stand still when you’re not pressing any arrow keys (that is, when rotating using the mouse).
Listing 1. Rotating the character relative to the camera
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RelativeMovement : MonoBehaviour { [SerializeField] Transform target; #A void Update() { Vector3 movement = Vector3.zero; #B float horInput = Input.GetAxis("Horizontal"); float vertInput = Input.GetAxis("Vertical"); if (horInput != 0 || vertInput != 0) { #C Vector3 right = target.right; Vector3 forward = Vector3.Cross(right, Vector3.up); #D movement = (right * horInput) + (forward * vertInput); #E transform.rotation = Quaternion.LookRotation(movement); #F } } }
#A This script needs a reference to the object to move relative to.
#B Start with vector (0, 0, 0) and add movement components progressively.
#C Only handle movement while arrow keys are pressed.
#D Calculate the player’s forward direction using cross product of the target’s right direction.
#E Add together the input in each direction to get the combined movement vector.
#F LookRotation() calculates a quaternion facing in that direction.
The code in this listing starts with a serialized variable for target
, because this script needs a reference to the object it’ll move relative to. Then we get to the Update()
function. The first line of the function declares a Vector3
value of 0
, 0
, 0
. The remaining code will replace this vector if the player is pressing any buttons, but it’s important to have a default value in case there isn’t any input.
Next, check the input controls, just as you have in previous scripts. Here’s where X and Z values are set in the movement vector, for horizontal movement around the scene. Remember that Input.GetAxis()
returns 0
if no button is pressed, and it varies between 1
and –1
when those keys are being pressed; putting that value in the movement vector sets the movement to the positive or negative direction of that axis (the X-axis is left-right, and the Z-axis is forward-backward).
The next several lines calculate the camera-relative movement vector. Specifically, we need to determine the sideways and forward directions to move in. The sideways direction is easy; the target transform has a property called right
, and that will point to the camera’s right because the camera was set as the target object. The forward direction is trickier, because the camera is angled forward and down into the ground, but we want the character to move around perpendicular to the ground. This forward direction can be determined using the cross product.
DEFINITION The cross product is one kind of mathematical operation that can be done on two vectors. Long story short, the cross product of two vectors is a new vector pointed perpendicular to both input vectors. Think about the 3D coordinate axes: the Z axis is perpendicular to both the X and Y axes. Don’t confuse cross product with dot product; the dot product (explained later in the chapter) is a different but also commonly seen vector math operation.
In this case, the two input vectors are the right and up directions. Remember that we already determined the camera’s right. Meanwhile, Vector3
has several shortcut properties for common directions, including the direction pointed straight up from the ground. The vector perpendicular to both of those points in the direction the camera faces, but aligned perpendicular to the ground.
Add the inputs in each direction to get the combined movement vector. The final line of code applies that movement direction to the character by converting the Vector3
into a Quaternion
using Quaternion.LookRotation()
and assigning that value. Try running the game now to see what happens!
public float rotSpeed = 15.0f;
... Quaternion direction = Quaternion.LookRotation(movement); transform.rotation = Quaternion.Lerp(transform.rotation, direction, rotSpeed * Time.deltaTime); } } }
Currently, the character is rotating in place without moving; in the next section, you’ll add code for moving the character around.
Moving forward in that direction
In order to move the player around the scene, you need to add a character controller component to the player object. Select the player and then choose Component > Physics > Character Controller. In the Inspector, you should slightly reduce the controller’s radius to .4
, but otherwise the default settings are all fine for this character model.
Here’s what you need to add in the RelativeMovement
script.
Listing 2. Adding code to change the player’s position
using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(CharacterController))] #A public class RelativeMovement : MonoBehaviour { ... public float moveSpeed = 6.0f; private CharacterController charController; void Start() { charController = GetComponent<CharacterController>(); #B } void Update() { ... movement = (right * horInput) + (forward * vertInput); movement *= moveSpeed; #C movement = Vector3.ClampMagnitude(movement, moveSpeed); #D ... } movement *= Time.deltaTime; #E charController.Move(movement); } }
#A The surrounding lines are context for placing the RequireComponent() method.
#B A pattern you’ve seen in previous chapters, used for getting access to other components.
#C The facing directions are magnitude 1, so multiply with the desired speed value.
#D Limit diagonal movement to the same speed as movement along an axis.
#E Always multiply movements by deltaTime to make them frame-rate independent.
If you play the game now, you will see the character (stuck in a T-pose) moving around in the scene. Pretty much the entirety of this listing is code you’ve already seen, so I’ll review everything briefly.
First, there’s a RequireComponent()
method at the top of the code. RequireComponent()
will force Unity to make sure the GameObject has a component of the type passed into the command. This line is optional; you don’t have to require it, but without this component the script will have errors.
Next there’s a movement value declared, followed by getting this script a reference to the character controller. As you’ll recall from previous chapters, GetComponent()
returns other components attached to the given object, and if the object to search on isn’t explicitly defined, then it’s assumed to be this.gameObject.GetComponent()
(that is, the same object as this script).
Movement values are still assigned based on the input controls, but now you also account for the movement speed. Multiply all movement axes by the movement speed, and then use Vector3.ClampMagnitude()
to limit the vector’s magnitude to the movement speed; the clamp is needed because, otherwise, diagonal movement would have a greater magnitude than movement directly along an axis (picture the sides and hypotenuse of a right triangle).
Finally, at the end, you multiply the movement values by deltaTime
in order to get frame rate–independent movement (recall that frame rate-independent means the character moves at the same speed on different computers with different frame rates). Pass the movement values to CharacterController.Move()
to make the movement.
This handles all the horizontal movement; if you want to see more, like vertical movement, check out the book here.