Book Image

Creating E-Learning Games with Unity

By : David Horachek
Book Image

Creating E-Learning Games with Unity

By: David Horachek

Overview of this book

Table of Contents (17 chapters)
Creating E-Learning Games with Unity
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Developing the player controls code


The third system we need to implement is the controls or how the character will respond to the user input. As a first pass, we need to be able to move our player in the world, so we will implement walk forward, walk backwards, walk left, and walk right. Luckily for us, Unity gives us an input system with axes so that we can write our control code once, and it will work with any devices that have an axis (such as keyboard or joypad). Of course, the devil is in the detail and keyboard controls behave differently from joypads, so we will write our code for keyboard input as it is the most responsive and most ubiquitous device. Once this script is finished, its behavior in combination with the GameCam script will control how the player motion feels in the game.

Implementing PlayerControls.cs

For every frame our player updates, perform the following steps that describe our PlayeControls algorithm:

  1. Store the forward and right vectors of the current camera.

  2. Store the raw axis input from the controller (keyboard or joystick). These values will range from -1.0 to 1.0, corresponding to full left or right, or full forward or backwards. Note that if you use a joystick, the rate of change of these values will generally be much slower than if a keyboard is used, so the code that processes it must be adjusted accordingly.

  3. Apply the raw input to transform the current camera basis vectors and compute a camera relative target direction vector.

  4. Interpolate the current movement vector towards the target vector and damp the rate of change of the movement vector, storing the result away.

  5. Compute the displacement of the camera with movement * movespeed and apply this to the camera.

  6. Rotate the camera to the current move direction vector.

Now let's implement this algorithm in C# code:

  1. Right click on the Chapter1 assets folder and select Create New C# Script. Name it PlayerControls.cs. Add this script to GameObject of Player1 by dragging-and-dropping it onto the object.

  2. Add a CharacterController component to the player's GameObject component as well. If Unity asks you whether you want to replace the box collider, agree to the change.

  3. Create public Vector3 moveDirection that will be used to store the current actual direction vector of the player. We initialize it to the zero vector by default as follows:

    public Vector3 moveDirection = Vector3.zero;
  4. Create three public float variables: rotateSpeed, moveSpeed, and speedSmoothing. The first two are coefficients of motion for rotation and translation, and the third is a factor that influences the smoothing of moveSpeed. Note that moveSpeed is private because this will only be computed as the result of the smoothing calculation between moveDirection and targetDirection as shown in the following code:

    public Float rotateSpeed;
    private float moveSpeed = 0.0f;
    public float speedSmoothing = 10.0f;
  5. Inside the update loop of this script, we will call a custom method called UpdateMovement(). This method will contain the code that actually reads input from the user and moves the player in the game as shown in the following code:

    void Update() {
      UpdateMovement()
    }
  6. Above the update loop, let's implement the UpdateMovement() method as follows:

    void UpdateMovement () { 
      // to be filled in
    }
  7. Inside this method, step 1 is accomplished by storing the horizontal projection of the forward and right vectors of the current camera as follows:

    Vector3 cameraForward = Camera.mainCamera.transform.TransformDirection
    (Vector3.forward); 
  8. We project onto the horizontal plane because we want the character's motion to be parallel to the horizontal plane rather than vary with the camera's angle. We also use Normalize to ensure that the vector is well formed, as shown in the following code:

    cameraForward.y = 0.0f; 
    cameraForward.Normalize(); 

    Also, note the trick whereby we find the right vector by flipping the x and z components and negating the last component. This is faster than extracting and transforming the right vector, but returns the same result shown in the following code:

    Vector3 cameraRight = new Vector3
    (cameraForward.z, 0.0f, -cameraForward.x); 
  9. We store the raw axis values from Unity's Input class. Recall that this is the class that handles input for us, from which we can poll button and axes values. For h (which has a range from -1 to 1), the value between this range corresponds to an amount of horizontal displacement on the analog stick, joystick, or a keypress, as shown in the following code:

    float v = Input.GetAxisRaw("Vertical"); 

    For v (which ranges from -1 to 1), the value between this range corresponds to an amount of vertical displacement of the analog stick, joystick, or a different keypress.

    float h = Input.GetAxisRaw("Horizontal"); 

To see the keybindings, please check the input class settings under Edit | ProjectSettings | Input. There, under the Axes field in the object inspector, we can see all of the defined axes in the input manager class, their bindings, their names, and their parameters.

  1. We compute the target direction vector for the character as proportional to the user input (v, h). By transforming (v, h) into camera space, the result is a world space vector that holds a camera relative motion vector that we store in targetDirection as shown in the following code:

    Vector3 targetDirection =
     h * cameraRight + v * cameraForward; 
  2. If this target vector is non-zero (when the user is moving, and hence v, h are non-zero), we update moveDirection by rotating it smoothly (and by a small magnitude), towards moveTarget. By doing this in every frame, the actual direction eventually approximates the target direction, even as targetDirection itself changes.

    We keep moveDirection normalized because our move speed calculation assumes a unit direction vector as shown in the following code:

    moveDirection = Vector3.RotateTowards 
    (moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);
    moveDirection = moveDirection.normalized; 
  3. We smoothly LERP the speed of our character up and down, trailing the actual magnitude of the targetDirection vector. This is to create an appealing effect that reduces jitter in the player and is crucial when we are using keyboard controls, where the variance in v and h raw data is at its highest, as shown in the following code:

    float curSmooth = 
    speedSmoothing * Time.deltaTime;
    float targetSpeed = Mathf.Min
    (targetDirection.magnitude, 1.0f);
    moveSpeed = Mathf.Lerp 
    (moveSpeed, targetSpeed, curSmooth);
  4. We compute the displacement vector for the player in this frame with movementDirection * movespeed (remember that movespeed is smoothly interpolated and moveDirection is smoothly rotated toward targetDirecton).

    We scale displacement by Time.delta time (the amount of real time that has elapsed since the last frame). We do this so that our calculation is time dependent rather than frame rate dependent as shown in the following code:

    Vector3 displacement = 
    moveDirection * moveSpeed * Time.deltaTime;
  5. Then, we move the character by invoking the move method on the CharacterController component of the player, passing the displacement vector as a parameter as follows:

    this.GetComponent<CharacterController>()
    .Move(displacement);
  6. Finally, we assign the rotation of MoveDirection to the rotation of the transform as follows:

    transform.rotation = Quaternion.LookRotation (moveDirection);

Congratulations! You have now written your first player controls class that can read user input from multiple axes and use that to drive a rotating and translating character capsule. To test this class, let's set the following default values in the Inspector pane as seen in the previous screenshot:

  • Track Obj: Set this to the Player1 object by dragging-and-dropping the object reference from the Hierarchy tab to the trackObj reference in the object inspector.

  • Height: Set this to 0.25. In general, the lower the camera, the more dramatic the effect, but the less playable the game will be (because the user can see less of the world on screen).

  • Desired Distance: Set this to 4. At this setting, we can see the character framed nicely on screen when it is both moving and standing still.

  • Rot Damp: Set this to 0.01. The smaller this value, the looser and more interesting the rotation effect. The larger this value, the more tense the spring in the interpolation.

  • Height Damp: Set this to 0.5. The smaller this value, the looser and more interesting the height blending effect.

Try experimenting with the following values and see what happens:

  • Rotate Speed : Set the default to 100. The higher the value, the faster the player will rotate when the horizontal axis is set to full left or right.

  • Speed Smoothing: Set the default to 10. The higher this value, the smoother the character's acceleration and deceleration.

Try experimenting with these values to understand their effect on the player's motion behavior.