Description: https://images.manning.com/360/480/resize/book/8/ef327d5-f4c9-4bec-a7f1-968696efef70/Hocking-Unity-3ed-MEAP-HI.png

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.

What does “camera-relative” mean?

The whole notion of “camera-relative” is a bit nonobvious but very crucial to understand. This is similar to the local versus global distinction mentioned in previous chapters: “left” points in different directions when you mean “left of the local object” or “left of the entire world.” In a similar way, when you “move the character to the left,” do you mean toward the character’s left, or the left-hand side of the screen?

The camera in a first-person game is placed inside the character and moves with it, so no distinction exists between the character’s left and the camera’s left. A third-person view places the camera outside the character, though, and thus the camera’s left may be pointed in a different direction from the character’s left. For example, they’re literally opposite directions if the camera is looking at the front of the character. As such, you have to decide what you want to have happen in your specific game and controls setup.

Although games occasionally do it the other way, most third-person games make their controls camera-relative. When the player presses the left button, the character moves to the left of the screen, not the character’s left. Over time and through experiments with trying out different control schemes, game designers have figured out that players find the controls more intuitive and easier to understand when “left” means “left-hand side of the screen” (which, not coincidentally, is also the player’s left).

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!

Smoothly rotating (interpolating) by using Lerp

Currently, the character’s rotation snaps instantly to different facings, but it’d look better if the character smoothly rotated to different facings. You can do so using a mathematical operation called Lerp. First add this variable to the script:

 
 public float rotSpeed = 15.0f;
  

Then replace the existing transform.rotation... line at the end of listing 1 with the following code:

 
       ...
       Quaternion direction = Quaternion.LookRotation(movement);
       transform.rotation = Quaternion.Lerp(transform.rotation,
           direction, rotSpeed * Time.deltaTime);
     }
   }
 }
 

Now, instead of snapping directly to the LookRotation() value, that value is used indirectly as the target direction to rotate toward. The Quaternion.Lerp() method smoothly rotates between the current and target rotations.

The term for smoothly changing between values is interpolate; you can interpolate between two of any kind of value, not just rotation values. Lerp is a quasi-acronym for “linear interpolation,” and Unity provides Lerp methods for vectors and float values, too (to interpolate positions, colors, or anything else). Quaternions also have a closely related alternative method for interpolation called Slerp (for spherical linear interpolation). For slower turns, Slerp rotations may look better than Lerp.

Incidentally, this code uses Lerp() in a somewhat non-traditional way. Normally the third value changes over time, but we are instead keeping the third value constant and changing the FIRST value. In traditional usage the start and end points are constant, but here we keep moving the start closer to the end, resulting in smooth interpolation towards that endpoint. This non-traditional use is explained here:

http://answers.unity.com/answers/730798/view.html

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.