Unity 3.x Scripting-Character Controller versus Rigidbody

Exclusive offer: get 50% off this eBook here
Unity 3.x Scripting

Unity 3.x Scripting — Save 50%

Write efficient, reusable scripts to build custom characters, game environments, and control enemy AI in your Unity game with this book and ebook.

$23.99    $12.00
by Devon Kraczla Volodymyr Gerasimov | June 2012 | Open Source

Character controller is extremely important part of any game. Essentially, it allows us to interact with it, control our alter-ego, shoot, explore, and do all kinds of crazy stuff. Most common character controller is humanoid, in general it needs to know how to walk, run, jump, attack, it needs to camera rig that will be accompanying it throughout play. All these actions need to be supported on a programming level and we will look into a way that it can be done in Unity. This article by Volodymyr Gerasimov and Devon Kraczla, the authors of the Unity 3.x Scripting, will help you to:

  • Learn Character Controller versus Rigidbody – pros and cons
  • Teach player-controlled character walk, run, jump, and shoot
  • Program camera controls and switching between different camera types with a press of a single button
  • Learn script animations to follow character's actions

Creating a controllable character

There are two ways to create a controllable character in Unity, by using the Character Controller component or physical Rigidbody. Both of them have their pros and cons, and the choice to use one or the other is usually based on the needs of the project. For instance, if we want to create a basic role playing game, where a character is expected to be able to walk, fight, run, and interact with treasure chests, we would recommend using the Character Controller component. The character is not going to be affected by physical forces, and the Character Controller component gives us the ability to go up slopes and stairs without the need to add extra code. Sounds amazing, doesn't it? There is one caveat. The Character Controller component becomes useless if we decide to make our character non-humanoid. If our character is a dragon, spaceship, ball, or a piece of gum, the Character Controller component won't know what to do with it.

It's not programmed for those entities and their behavior. So, if we want our character to swing across the pit with his whip and dodge traps by rolling over his shoulder, the Character Controller component will cause us many problems.

In this article, we will look into the creation of a character that is greatly affected by physical forces, therefore, we will look into the creation of a custom Character Controller with Rigidbody, as shown in the preceding screenshot.

Custom Character Controller

In this section, we will write a script that will take control of basic character manipulations. It will register a player's input and translate it into movement. We will talk about vectors and vector arithmetic, try out raycasting, make a character obey our controls and see different ways to register input, describe the purpose of the FixedUpdate function, and learn to control Rigidbody.

We shall start with teaching our character to walk in all directions, but before we start coding, there is a bit of theory that we need to know behind character movement.

Most game engines, if not all, use vectors to control the movement of objects. Vectors simply represent direction and magnitude, and they are usually used to define an object's position (specifically its pivot point) in a 3D space. Vector is a structure that consists of three variables—X, Y, and Z. In Unity, this structure is called Vector3:

To make the object move, knowing its vector is not enough.

Length of vectors is known as magnitude. In physics, speed is a pure scalar, or something with a magnitude but no direction. To give an object a direction, we use vectors. Greater magnitude means greater speed. By controlling vectors and magnitude, we can easily change our direction or increase speed at any time we want.

Vectors are very important to understand if we want to create any movement in a game. Through the examples in this article, we will explain some basic vector manipulations and describe their influence on the character. It is recommended that you learn extra material about vectors to be able to perfect a Character Controller based on game needs.

Setting up the project

To start this section, we need an example scene. Perform the following steps:

  1. Select Chapter 2 folder from book assets, and click on on the Unity_chapter2 scene inside the custom_scene folder.
  2. In the Custom scripts folder, create a new JavaScript file. Call it CH_Controller (we will reference this script in the future, so try to remember its name, if you choose a different one):
  3. In a Hierarchy view, click on the object called robot. Translate the mouse to a Scene view and press F; the camera will focus on a funny looking character that we will teach to walk, run, jump, and behave as a character from a video game.

Creating movement

The following is the theory of what needs to be done to make a character move:

  1. Register a player's input.
  2. Store information into a vector variable.
  3. Use it to move a character.

Sounds like a simple task, doesn't it? However, when it comes to moving a player-controlled character, there are a lot of things that we need to keep in mind, such as vector manipulation, registering input from the user, raycasting, Character Controller component manipulation, and so on. All these things are simple on their own, but when it comes to putting them all together, they might bring a few problems. To make sure that none of these problems will catch us by surprise, we will go through each of them step by step.

Manipulating character vector

By receiving input from the player, we will be able to manipulate character movement. The following is the list of actions that we need to perform in Unity:

  1. Open the CH_Character script.
  2. Declare public variables Speed and MoveDirection of types float and Vector3 respectively. Speed is self-explanatory, it will determine at which speed our character will be moving. MoveDirection is a vector that will contain information about the direction in which our character will be moving.
  3. Declare a new function called Movement. It will be checking horizontal and vertical inputs from the player.
  4. Finally, we will use this information and apply movement to the character. An example of the code is as follows:
  5. public var Speed : float = 5.0; public var MoveDirection : Vector3 = Vector3.zero; function Movement (){ if (Input.GetAxis("Horizontal") || Input.GetAxis("Vertical")) MoveDirection = Vector3(Input.GetAxisRaw("Horizontal"),MoveDirecti on.y, Input.GetAxisRaw("Vertical")); this.transform.Translate(MoveDirection); }

Register input from the user

In order to move the character, we need to register an input from the user. To do that, we will use the Input.GetAxis function. It registers input and returns values from -1 to 1 from the keyboard and joystick. Input.GetAxis can only register input that had been defined by passing a string parameter to it. To find out which options are available, we will go to Edit | Projectsettings | Input. In the Inspector view, we will see Input Manager.

Click on the Axes drop-down menu and you will be able to see all available input information that can be passed to the Input.GetAxis function. Alternatively, we can use Input.GetAxisRaw. The only difference is that we aren't using Unity's built-in smoothing and processing data as it is, which allows us to have greater control over character movement.

To create your own input axes, simply increase the size of the array by 1 and specify your preferences (later we will look into a better way of doing and registering input for different buttons).

this.transform is an access to transformation of this particular object. transform contains all the information about translation, rotation, scale, and children of this object. Translate is a function inside Unity that translates GameObject to a specific direction based on a given vector.

If we simply leave it as it is, our character will move with the speed of light. That happens because translation is being applied on character every frame. Relying on frame rate when dealing with translation is very risky, and as each computer has different processing power, execution of our function will vary based on performance. To solve this problem, we will tell it to apply movement based on a common factor—time:

this.transform.Translate(MoveDirection * Time.deltaTime);

This will make our character move one Unity unit every second, which is still a bit too slow. Therefore, we will multiply our movement speed by the Speed variable:

this.transform.Translate((MoveDirection * Speed) * Time.deltaTime);

Now, when the Movement function is written, we need to call it from Update. A word of warning though—controlling GameObject or Rigidbody from the usual Update function is not recommended since, as mentioned previously, that frame rate is unreliable. Thankfully, there is a FixedUpdate function that will help us by applying movement at every fixed frame. Simply change the Update function to FixedUpdate and call the Movement function from there:

function FixedUpdate (){ Movement(); }

The Rigidbody component

Now, when our character is moving, take a closer look at the Rigidbody component that we have attached to it. Under the Constraints drop-down menu, we will notice that Freeze Rotation for X and Z axes is checked, as shown in the following screenshot:

If we uncheck those boxes and try to move our character, we will notice that it starts to fall in the direction of the movement. Why is this happening? Well, remember, we talked about Rigidbody being affected by physics laws in the engine? That applies to friction as well. To avoid force of friction affecting our character, we forced it to avoid rotation along all axes but Y. We will use the Y axis to rotate our character from left to right in the future.

Another problem that we will see when moving our character around is a significant increase in speed when walking in a diagonal direction. This is not an unusual bug, but an expected behavior of the MoveDirection vector. That happens because for directional movement we use vertical and horizontal vectors. As a result, we have a vector that inherits magnitude from both, in other words, its magnitude is equal to the sum of vertical and horizontal vectors.

To prevent that from happening, we need to set the magnitude of the new vector to 1. This operation is called vector normalization. With normalization and speed multiplier, we can always make sure to control our magnitude:

this.transform.Translate((MoveDirection.normalized * Speed) * Time. deltaTime);

Jumping

Jumping is not as hard as it seems. Thanks to Rigidbody, our character is already affected by gravity, so the only thing we need to do is to send it up in the air. Jump force is different from the speed that we applied to movement. To make a decent jump, we need to set it to 500.0). For this specific example, we don't want our character to be controllable in the air (as in real life, that is physically impossible). Instead, we will make sure that he preserves transition velocity when jumping, to be able to jump in different directions. But, for now, let's limit our movement in air by declaring a separate vector for jumping.

User input verification

In order to make a jump, we need to be sure that we are on the ground and not floating in the air. To check that, we will declare three variables—IsGrounded, Jumping, and inAir—of a type boolean. IsGrounded will check if we are grounded. Jumping will determine if we pressed the jump button to perform a jump. inAir will help us to deal with a jump if we jumped off the platform without pressing the jump button. In this case, we don't want our character to fly with the same speed as he walks; we need to add an airControl variable that will smooth our fall.

Just as we did with movement, we need to register if the player pressed a jump button. To achieve this, we will perform a check right after registering Vertical and Horizontal inputs:

public var jumpSpeed : float = 500.0; public var jumpDirection : Vector3 = Vector3.zero; public var IsGrounded : boolean = false; public var Jumping : boolean = false; public var inAir : boolean = false; public var airControl : float = 0.5; function Movement(){ if (Input.GetAxis("Horizontal") || Input.GetAxis("Vertical")) { MoveDirection = Vector3(Input.GetAxisRaw("Horizontal"),
MoveDirection.y,Input.GetAxisRaw("Vertical")); } if (Input.GetButtonDown("Jump") && isGrounded) {} }

GetButtonDown determines if we pressed a specific button (in this case, Space bar), as specified in Input Manager. We also need to check if our character is grounded to make a jump.

We will apply vertical force to a rigidbody by using the AddForce function that takes the vector as a parameter and pushes a rigidbody in the specified direction. We will also toggle Jumping boolean to true, as we pressed the jump button and preserve velocity with JumpDirection:

if (Input.GetButtonDown("Jump") &&isGrounded){ Jumping = true; jumpDirection = MoveDirection; rigidbody.AddForce((transform.up) * jumpSpeed); } if (isGrounded) this.transform.Translate((MoveDirection.normalized * Speed) *
Time.deltaTime); else if (Jumping || inAir) this.transform.Translate((jumpDirection * Speed * airControl) *
Time.deltaTime);

To make sure that our character doesn't float in space, we need to restrict its movement and apply translation with MoveDirection only, when our character is on the ground, or else we will use jumpDirection.

Raycasting

The jumping functionality is almost written; we now need to determine whether our character is grounded. The easiest way to check that is to apply raycasting. Raycasting simply casts a ray in a specified direction and length, and returns if it hits any collider on its way (a collider of the object that the ray had been cast from is ignored):

To perform a raycast, we will need to specify a starting position, direction (vector), and length of the ray. In return, we will receive true, if the ray hits something, or false, if it doesn't:

function FixedUpdate () { if (Physics.Raycast(transform.position, -transform.up, collider. height/2 + 2)){ isGrounded = true; Jumping = false; inAir = false; } else if (!inAir){ inAir = true; JumpDirection = MoveDirection; } Movement(); }

As we have already mentioned, we used transform.position to specify the starting position of the ray as a center of our collider. -transform.up is a vector that is pointing downwards and collider.height is the height of the attached collider. We are using half of the height, as the starting position is located in the middle of the collider and extended ray for two units, to make sure that our ray will hit the ground. The rest of the code is simply toggling state booleans.

Improving efficiency in raycasting

But what if the ray didn't hit anything? That can happen in two cases—if we walk off the cliff or are performing a jump. In any case, we have to check for it.

If the ray didn't hit a collider, then obviously we are in the air and need to specify that. As this is our first check, we need to preserve our current velocity to ensure that our character doesn't drop down instantly.

Raycasting is a very handy thing and being used in many games. However, you should not rely on it too often. It is very expensive and can dramatically drop down your frame rate.

Right now, we are casting rays every frame, which is extremely inefficient. To improve our performance, we only need to cast rays when performing a jump, but never when grounded. To ensure this, we will put all our raycasting section in FixedUpdate to fire when the character is not grounded.

function FixedUpdate (){ if (!isGrounded){ if (Physics.Raycast(transform.position, -transform.up, collider.height/2 + 0.2)){ isGrounded = true; Jumping = false; inAir = false; } else if (!inAir){ inAir = true; jumpDirection = MoveDirection; } } Movement(); } function OnCollisionExit(collisionInfo : Collision){ isGrounded = false; }

To determine if our character is not on the ground, we will use a default function— OnCollisionExit(). Unlike OnControllerColliderHit(), which had been used with Character Controller, this function is only for colliders and rigidbodies. So, whenever our character is not touching any collider or rigidbody, we will expect to be in the air, therefore, not grounded.

Let's hit Play and see our character jumping on our command.

Additional jump functionality

Now that we have our character jumping, there are a few issues that should be resolved. First of all, if we decide to jump on the sharp edge of the platform, we will see that our collider penetrates other colliders. Thus, our collider ends up being stuck in the wall without a chance of getting out:

A quick patch to this problem will be pushing the character away from the contact point while jumping. We will use the OnCollisionStay() function that's called at every frame when we are colliding with an object. This function receives collision contact information that can help us determine who we are colliding with, its velocity, name, if it has Rigidbody, and so on. In our case we are interested in contact points. Perform the following steps:

  1. Declare a new private variable contact of a ContactPoint type that describes the collision point of colliding objects.
  2. Declare the OnCollisonStay function.
  3. Inside this function, we will take the first point of contact with the collider and assign it to our private variable.
  4. Add force to the contact position to reverse the character's velocity, but only if the character is not on the ground.
  5. Declare a new variable and call it jumpClimax of boolean type.

Contacts is an array of all contact points.

Finally, we need to move away from that contact point by reversing our velocity. The AddForceAtPosition function will help us here. It is similar to the one that we used for jumping, however, this one applies force at a specified position (contact point):

public var jumpClimax :boolean = false; ... function OnCollisionStay(collisionInfo : Collision){ contact = collisionInfo.contacts[0]; if (inAir || Jumping) rigidbody.AddForceAtPosition(-rigidbody.velocity, contact.point); }

The next patch will aid us in the future, when we will be adding animation to our character later in this article. To make sure that our jumping animation runs smoothly, we need to know when our character reaches jumping climax, in other words, when it stops going up and start a falling.

In the FixedUpdate function, right after the last else if statement, put the following code snippet:

else if (inAir&&rigidbody.velocity.y == 0.0) { jumpClimax = true; }

Nothing complex here. In theory, the moment we stop going up is a climax of our jump, that's why we check if we are in the air (obviously we can't reach jump climax when on the ground), and if vertical velocity of rigidbody is 0.0. The last part is to set our jumping climax to false. We'll do that at the moment when we touch the ground:

if (Physics.Raycast(transform.position, -transform.up, collider. height/2 + 2)){ isGrounded = true; Jumping = false; inAir = false; jumpClimax = false; }

Running

We taught our character to walk, jump, and stand aimlessly on the same spot. The next logical step will be to teach him running. From a technical point of view, there is nothing too hard. Running is simply the same thing as walking, but with a greater speed. Perform the following steps:

  1. Declare a new variable IsRunning of a type boolean, which will be used to determine whether our character has been told to run or not.
  2. Inside the Movement function, at the very top, we will check if the player is pressing left or right, and shift and assign an appropriate value to isRunning:
  3. public var isRunning : boolean = false; ... function Movement() { if (Input.GetKey (KeyCode.LeftShift) || Input.GetKey (KeyCode. RightShift)) isRunning = true; else isRunning = false; ... }

Another way to get input from the user is to use KeyCode. It is an enumeration for all physical keys on the keyboard. Look at the KeyCode script reference for a complete list of available keys, on the official website: http://unity3d.com/ support/documentation/ScriptReference/KeyCode

We will return to running later, in the animation section.

Unity 3.x Scripting Write efficient, reusable scripts to build custom characters, game environments, and control enemy AI in your Unity game with this book and ebook.
Published: June 2012
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Cameras

They are important! It does not matter what discipline an individual is in. A camera and its uses are crucial to the development of the game and/or positions that the players will find themselves in. We will build a generic camera script that we will be able to configure for various positions.

Camera scripting

The camera script is quite heavy when it comes to scripting. So, we are going to first script functionality for the fps camera, which will form the basic structure for the other types of cameras.

It will come down to the following steps:

  1. Create the camera script.
  2. Write the camera switching functions.
  3. Write the camera movement functionality.
  4. Influence character movement through camera positioning.

Creating camera script

First, we will create a JavaScript named CameraScr. In that, we will have the functionalities for the reader to be able to manipulate various properties for the camera setup, such as the height that the camera will sit at and the distance from which the camera will be located from the target. In the script:

  • We will set up some simple variables
  • There will be a variable for the object to be tracked, another for distance, for height offset, side offset, smooth follow, and the current camera type

In the case of the object to be tracked, this will be the GameObject character. Make sure to set its type as Transform and make the variable public. We will use a list of variables of specified types to store multiple values of a specific type in a single variable. Be sure to use the square brackets after the following variable types and make them public, as we will be adding values to them in the Inspector menu:

  • The second variable is the current camera state and will be of the type int and defaulted to 0. It is okay if it is private.
  • The third variable is for the distance, which we define as the camera distance and have its type defined as float.
  • The fourth variable is for height and its type is again float. This variable will handle the height offset for the camera.
  • The following is an example of what these variables may look like:

    public var charObj : Transform; public var camNum: int = 0; public var camDistance: float[]; public var heightOffset : float[];

At this point, we only care about the values that are in position 0 of the array variables.

Creating an enumeration list

Next, we will set up an enumeration that will deal with switching the values for the different camera types. An enumeration is a variable that can hold integer values in any form. The user just needs to keep in mind that whatever is put into an enum, enumeration for short, will be converted into an integer. The first value in an enum is considered in the first spot, the second in the second spot, and so on. Create an enum and call it CamType.

Remember that enum is like a class or list of variables (names in this case) and must use curly braces to begin and end its statement. Inside enum, create the camera types (FP, SP, TP). The camera types FP, SP, and TP, will have the variable integer values of 0, 1, and 2.

In order not to get an error at this point, you will have to create a variable, of type enum. Call it CamType. This variable allows the reader to change the enum type at will in the Inspector menu. The variable may be private but remember, if you wish to change the type in Inspector, it must be public. The variable and enum should resemble the following code snippet:

private var cameraType : CamType; enum CamType { FP, SP, TP }

Writing functions

For now, we have taken care of variables, and we have to begin to write the functions.

The Initialize function

We will start with giving values to our variables. Perform the following steps:

  1. Right off the bat, we will need to write an Initialize function. In this function, we want to change the camera type to the default camera type, which we want the character to start off with. In this case, it is camera 1.
  2. This is based upon the camera's value in enum.
  3. After that, we will need to change the camera enum type to the first-person camera.
  4. Set charObj with received Player value.

Right now, the Initialize function will look similar to the following code snippet:

function Initialize(Player : Transform){ camNum = 1; cameraType = CamType.FP; charObj = Player; }

So, we have that function taken care of for the time being; next, we want another function to handle the switching of the camera.

In order to work, the Initialize function needs to be called. Perform the following steps:

  1. Open the CH_Controller script.
  2. Create a public variable CPrefab of a type GameObject.
  3. Inside the Start function, check if this object exists.
  4. If it does, set the charObj value to transform. Call the Initialize function with transform information about the character.
  5. If it doesn't, try to find an object with a MainCamera tag, call the Initialize function and set the charObj value to transform.

This function needs to be called from CH_Controller just in case we forgot to set the camera. In the CH_Controller script, write the following code snippet:

public var CPrefab : GameObject; function Start(){ ... if (CPrefab == null){ CPrefab = GameObject.FindGameObjectWithTag("MainCamera");CPr efab.GetComponent("CameraScr").charObj = transform;CPrefab. GetComponent("CameraScr").Initialize(transform);} else{CPrefab.GetComponent("CameraScr").charObj = transform;CPrefab. GetComponent("CameraScr").Initialize(transform);} }

Changing camera function

The changing camera function will be called ChangeCamType. As its name implies, this function will change the camera from one type to another.

In this function, we need to check a couple of things, such as identify the current camera type and then change the camera to the next type. Perform the following steps:

  1. First, create the ChangeCamType function.
  2. Check for the camera number, inside of which we want to use the same camera switching line that we used in the Initialize function. After that line, we want to state that the camera number is now equal to the next camera type. This function should look similar to the following code snippet:
  3. function ChangeCamType(){ If(camNum == 1){ cameraType = camType.SP; camNum = 2; } }

Now that we have the camera switching, we need to assign the list variable values to our equation variables. To do this, we will use a switch case statement to change the equation variables based upon the current camera type.

A switch statement is pretty much an if statement except that you can switch variable values without having to reassign them. This type of statement works great for Artificial Intelligence (AI) behaviors and will be used later on in the book for just that purpose.

Changing the camera values function

The changing camera values function will be called SetCamValues. Perform the following steps:

  1. The first thing is to call the ChangeCamType function.
  2. For the switch statement, the first thing that we have to check is that camera type is true. After that, we can use a case statement to switch variables based upon the current camera type. The first case statement will check for when the current camera is first person.

Now, we will create the equation variables. These variables will be used to hold the values from the selected array variables and in the final equations that will determine the final setup of the camera. This is done so that there can be one block of code for all of the camera types instead of a block of code for each of the camera types. Each of these variables will have the same type, minus the square brackets, of the values which they will be taking on but can be made private if preferred.

  1. The variables to be created are as follows:
    • camDist: This variable will deal with camera distance list variable
    • hOffset: This variable will deal with height offset list variable
  2. Inside the case statement, after the reader has matched all of the equation variables with the list variables, we need to make sure that the right list number has been assigned to the variable. For camera number 1, FP, the number is one but with lists, as in most scripting or programming languages, they start at 0. So, make sure that the list variables that the equation variables are equaling, have 0 in the brackets for FP, 1 for SP, and 2 for TP.
  3. At the end of the case statement, we then want to put a break line in. This break line prevents the code from moving on to the next case statement.

In the Initialize function, at the end of it, we want to add this function in there as well. The following is an example of the code:

function SetCamValues(){ ChangeCamValues(); switch(cameraType ){ case CamType.FP : camDist = camDistance[0]; hOffset = heightOffset[0]; break; } } function Initialize(Player : Transform){ ... SetCameraValues(); }

Writing camera switching controls

Now that the primary functionality is done, we need to write one more script that will deal with the player's input to toggle between camera types. Perform the following steps:

  1. Create another JavaScript called Player_Input. Hook it up to camera.
  2. In this script, we will need an Update function to always be checking for player's pressing of the toggle camera button, in this case T.
  3. Create an Update function and put an if statement inside. This statement will check for the pressed status of T using Unity's built-in function—Input. GetKeyDown(KeyCode.T).
  4. Inside the if statement, we will call the SetCameraValues function in CameraScr.
  5. The script should resemble the following code snippet:

    function Update(){ If(Input.GetKeyDown(KeyCode.T){ this.gameObject.GetComponent(CameraScr).SetCameraValues(); } }

Make sure to hook up the cameraObj as your target camera.

Character movement and camera positioning

Now for secondary functionality of the camera, orbiting and character movement based upon camera positioning and the coding for the two other cameras. Third-person view camera is demonstrated in the following screenshot:

Updating camera type changing

First we will tackle the two other cameras as they are the easier of the two functionalities to implement. Let's venture back to the ChangeCamType function. In here, we only had a change statement for one camera. Now we need to add the other two in. Perform the following steps:

  1. We just need to copy and paste the existing if statement two times.
  2. Change the if statements to the else if statements. The camera numbers should be from 1 to 2 for the middle statement and 1 to 3 for the lower statement.
  3. The CampType value for the middle statement should be changed from SP to TP as well and for the lower statement, SP to FP.
  4. Lastly, the camera numbers found within the if statement blocks should be changed to 3 for the middle one and 1 for the lower one.
  5. These statements allow the camera to change its enum type whenever the T button is pressed. This function should now look like the following code snippet:

    function ChangeCamType(){ if (camNum == 1){ cameraType = camType.SP; camNum = 2; } if (camNum == 2){ cameraType = camType.TP; camNum = 3; } if (camNum == 3){ cameraType = camType.FP; camNum = 1; } }

  6. Next, we will add in the SetCameraValues function to the switch case statement. All we have to do again is copy the case statement and change some values in the copies.
  7. After copying and pasting the current case statement twice below the current one, we need to change CamType for the middle one to SP and the lower one to TP.
  8. The bracket values need to change as well. For the middle statement, all bracket numbers need to be 1 and for the lower statement, all bracket numbers need to be 2:
  9. function SetCamValue(){ ChangeCamValues(); switch(cameraType ){ case CamType.FP : camDist = camDistance[0]; hOffset = heightOffset[0]; break; case CamType.SP : camDist = camDistance[1]; hOffset = heightOffset[1]; break; case CamType.TP : camDist = camDistance[2]; hOffset = heightOffset[2]; break; } }

Influencing camera with a mouse

The last two things to write now for the camera are mouse input for camera control and the ClampAngle function. First, we will add the mouse control to the Apply function. Perform the following steps:

  1. At the bottom of the Apply function, we want to get the mouse X positioning and add it to the x angle of the camera and grab the mouse Y positioning and subtract it from the y angle of the camera.
  2. As we want to limit the angle by which the camera can move on the Y axis, we will call the ClampAngle function.
  3. As we wish to follow the rotation of the player, which is the Y axis, we grab the player's angle by using Unity's built-in euler angles functionality.
  4. Then, we grab the camera's euler angle on Y.
  5. eulerAngles is a representation of a rotation around a specific axis. eulerAngles.x is, therefore, a rotation around the X axis and the same goes for the Y and Z axes.

  6. After this, we want to create a variable for rotation and position.
  7. The rotation variable will be equal to Quaternion Euler angles using the x value of the mouse, and the y value of the mouse.
  8. Starting position of the character should be recorded when camera initializes.
  9. For the positioning variable, we will take the rotation and multiply it by the camera distance and add the target object's position.
  10. Lastly, we will have the camera's rotation equal to the variable rotation and the camera's position equal to the variable position.

The following code will go into the Initialize function and variable section of the script:

private var x : float = 0.0; private var y : float = 0.0; private var startRotation : int; function Initialize(Player : Transform){ camNum = 1; cameraType = CamType.FP; startRotation = charObj.transform.eulerAngles.y; x = transform.eulerAngles.y; y = transform.eulerAngles.x; charObj = Player; SetCameraValues(); }

The following code shows what should have been added to the bottom of the Apply function:

x += Input.GetAxis("Mouse X") * mouseSpeed[0] * Time.deltaTime; y -= Input.GetAxis("Mouse Y") * mouseSpeed[1] * Time.deltaTime; y = ClampAngle(y, yLimit[0], yLimit[1]); var targPos = Quaternion.Euler(y, x + startRotation, 0); var position = rotation * Vector3(0.0, 0.0, camDist) + charObj. position;

transform.rotation = rotation; transform.position = targPos;

Clamping angles

The last function to write for this script is the ClampAngle function. It has the following characteristics:

  • The ClampAngle function is going to be taking three parameters.
  • Those parameters are angle, min, and max.
  • There will be two if statements in the function and then a return function.
  • The parameters that are coming in are the angle, which we want to check and see if it is smaller or greater than 360 degrees. If greater, we subtract 360 so that the angle becomes within the acceptable range. If lower, we add 360 degrees. We return the result back to the function so that it makes sure that the angle never goes out of range.

The following is an example of the code:

function ClampAngle(angle:float, min:float, max:float){ If(angle < -360) Angle += 360; If(angle > 360){ Angle -=360; } return Mathf.Clamp(angle, min, max); }

Now that everything is compiled, we need to go back to Inspector and add in the values for the list variables and the target object. These variables can be set to your own discretion but the following is a screenshot of our values:

Camera's late update

There is one more function to write for this stage of the camera and that is the LateUpdate function.

This function is used because during the Update function, the target object of the script might have moved beyond an area where the camera can see, that is, inside of a building. This function will handle the calling of the remaining functions. Perform the following steps:

  1. Create the function and inside of it, do a simple check to make sure that a target exists (charObj).
  2. Inside of this check, we want to call the Apply function.
  3. The following code snippet shows what it should look like:

    function LateUpdate(){ If(charObj) Apply(); }

Rotating character with a camera

One more function before we are done. In the Character Controller script, inside of the FixedUpdate function, right before the calling of the Movement function, we will add the following line of code:

transform.Rotate(Vector3(0, Input.GetAxis("Mouse X"), 0) * Time. deltaTime * 250.0);

This line allows the character to rotate with the rotation of the camera. We grab the mouse x and rotate the character by it and we dampen it by the delta time to make sure that it becomes smooth gradually. The following code snippet, which shows the complete CamerScr script is the final code:

public var charObj: Transform; public var camDistance: float[]; public var heightOffset: float[]; public var mouseSpeed : float[]; public var yAngleLimit : float[]; private var camNum : float = 0; private var cameraType : CamType; private var camDist : float; private var hOffset : float; private var x = 0.0; private var y = 0.0; enum CamType{FP,SP,TP} function Initialize(Player : Transform){ camNum = 1; cameraType = CamType.FP; startRotation = charObj.transform.eulerAngles.y; x = transform.eulerAngles.y; y = transform.eulerAngles.x; charObj = Player; SetCameraValues(); } function ChangeCamType(){ if(camNum == 1 ){ cameraType = CamType.SP; camNum = 2; } else if( camNum == 2 ){ cameraType = CamType.TP; camNum = 3; } else if( camNum == 3 ){ cameraType = CamType.FP; camNum = 1; } } function SetCameraValues(){ ChangeCamType(); switch(cameraType ){ caseCamType.FP : camDist = camDistance[0]; hOffset = heightOffset[0]; break; caseCamType.SP : camDist = camDistance[1]; hOffset = heightOffset[1]; break; case CamType.TP : camDist = camDistance[2]; hOffset = heightOffset[2]; break; } } function Apply(){ x += Input.GetAxis("Mouse X") * mouseSpeed[0] * Time.deltaTime; y -= Input.GetAxis("Mouse Y") * mouseSpeed[1] * Time.deltaTime; y = ClampAngle(y, yAngleLimit[0], yAngleLimit[1]);\ var rotation = Quaternion.Euler(y, x + startRotation, 0); var targPos = rotation * Vector3(0.0, 0.0, camDist) + charObj.position; targPos.y += hOffset; transform.rotation = rotation; transform.position = targPos; } function ClampAngle (angle : float, min : float, max : float) { if (angle < -360) angle += 360; if (angle > 360) angle -= 360; return Mathf.Clamp (angle, min, max); } function LateUpdate () { Apply(); }

In the Player_Input script, add the following code snippet:

function Update (){ if(Input.GetKeyDown(KeyCode.T)) this.gameObject.GetComponent(CameraScr).SetCameraValues(); }

Congratulations! You can now have a camera rig that will give you a lot of functionality in a small limited package.

Animation controls

In the last part of this article, we will talk about what makes games look awesome—animations. We will learn how to control animations through code, learn the truth about the Start and Awake functions, and figure out how to make smooth transactions in between animations.

Playing simple animations

Time to add some visual indication to our movement and jump into the world of animations. Thankfully, we don't have to worry about animating our character, all animations are already done for us and are included with the model.

In this section, we will talk about basic animations and how to play them. As our game continues to grow, we will add more advanced techniques to handle various animations. Let's create a new script whose main purpose will be to handle and control all animations for our character, such as their speed, play order, and modes. Perform the following steps:

  1. Create a new script in the Custom scripts folder and call it CH_Animation.
  2. Declare a private variable of a CH_Controller type (script that handles movement, if you name it differently, use your name to declare its type), call it Controller. This way we can reference any scripts, just by declaring them with a type of script's name.
  3. Declare two functions—Start and Awake:
  4. private var Controller : CH_Controller; function Start (){} function Awake (){}

Start function versus Awake function

Let's talk a bit about the difference between these two functions. At first glance, there is none, and many people make the same mistake by mismatching them. This is a mistake that can lead to problems.

The Awake() function is the first function that is called when you start a game. Right after you press the Play key, the engine goes through all scripts and executes the Awake function in each of them.

The Start() function is called right after all the Awake() functions on all objects are executed.

We can give both these functions a small test. Let's test this:

  1. Add debug logs in both of these functions. In Awake, write something like I'm awake and I'm ready to start in the Start function.
  2. Attach this script to our character and hit Play. Double-click at the debug line at the bottom and look at what we got—I'm awake printed before I'm ready to start as planned:
  3. function Start (){ Debug.Log("I'm ready to start"); } function Awake (){ Debug.Log("I'm awake"); }

Your console messages should be similar to those displayed on the following screenshot:

Remember, there is no order in which the engine calls the Awake or Start functions among the objects by default, it can randomly choose one or another and call it from there. Another interesting thing is that the Start() function won't be called if an object is disabled. In other words, if we disable an object in the Awake() function, we can save some performance for our game to run faster at start-up.

Using specific of these functions we should be prepared to use Awake() for referencing objects, scripts, variables etc. Assigning default properties and start-up functionality is better in the Start() function.

Animation component and playing speed

We will use this script to control speed of animations and movement speed for the character; therefore, we need to declare the following variables to control them:

public var forwardSpeed : float = 5.0; public var backwardSpeed : float = 3.0; public var strafingSpeed : float = 4.0; public var runningSpeed : float = 10.0; public var idleAnimationSpeed : float = 1.0; public var forwardAnimationSpeed : float = 6.0; public var runningAnimationSpeed : float = 3.0; public var backwardAnimationSpeed : float = 1.0; public var strafingAnimationSpeed : float = 3.0; public var jumpingAnimationSpeed : float = 1.5;

Variables in the preceding code snippet will control movement speed for our character based on direction, animation, and animation speed.

Let's get back to animations:

  1. Remove the debug logs from this script and reference CH_Controller from this object in the Awake function:
  2. function Awake(){ Controller = this.gameObject.GetComponent(CH_Controller); }

  3. In order for the object to play animations, we need to attach an animation component to our character. Select character and go to Component | Miscellaneous | Animation, as shown in the following screenshot:
  4. Inside Animation Controller, click on a small circle, it will lead you to the Select AnimationClip window. Click on any of the available animations.
  5. Under the Animations drop-down menu, increase the size to 4 and assign a unique animation to each Element.
  6. Uncheck the Play Automatically box. We don't want Unity to play random animation for us; we will take care of it through the code:

All the animation manipulations will be done through Animation Controller. The first thing that we need to learn about animations is WrapMode. WrapMode controls the play of animation—or repeating, to be more precise. There are a number of repeating modes available in Unity. They are as follows:

 

  • Once: It plays the animation once and stops
  • Loop: It plays the animation over and over again until told to stop
  • Ping-pong: It plays the animation till the end, then reverses and plays it backwards
  • Default: It reads a default repeat mode set higher up
  • ClampForever: It will play the animation till the end and then continuously keeps playing its last frame

We can specify WrapMode for all animations by referencing just an animation component or an individual animation, by specifying a name in square brackets: animation.wrapMode = WrapMode.Loop; or animation["idle"].wrapMode = WrapMode.Loop;

 

To play an animation, we simply call the Play function with name of the animation.

Animation scripting

In this section, we will put information learned in the preceding section into action. Perform the following steps:

  1. When script initializes, we need to set WrapMode to looping by default.
  2. Specify ClampForever WrapMode for "jump" animation.
  3. Set speed for all known animations.
  4. First animation to play should be "idle".

Put the following code snippet inside the Start function:

function Start(){ animation.wrapMode = WrapMode.Loop; animation["jump"].wrapMode = WrapMode.ClampForever; animation["idle"].speed = idleAnimationSpeed; animation["walk_forward"].speed = forwardAnimationSpeed; animation["run"].speed = runningAnimationSpeed; animation["walk_backward"].speed = backwardAnimationSpeed; animation["walk_side"].speed = strafingAnimationSpeed; animation["jump"].speed = jumpingAnimationSpeed; animation.Play("idle"); }

Now that we have that, it's about time to add animation to our character's jump. Perform the following steps:

  1. Create a new function and call it DetermineDirection().
  2. We will start with jumping animations; first, we need to determine if the character is in the air.
  3. We will utilize jumpClimax, implemented earlier in this article, to check if the character reached a jump climax.
  4. Call DetermineDirection function from Update:
  5. function Update (){ DetermineDirection(); } function DetermineDirection (){ if (Controller.inAir){ if(!Controller.jumpClimax) {} } }

Jump can be performed from any height, therefore, we have no idea how long animation should be played for. ClampForever, a loop playing the last frame of the animation, will help us here.

CrossFade is used to blend in between animations. Blending is a very important aspect of animations, as it helps to create numerous transitions from one animation to another.

Imagine that there was no blending. Our character would be walking, then instantly changing animation to jumping, shooting, landing, and so on. That will look weird and hard-edged. If we want to make smooth transactions from one animation to another, from jumping to landing to walking, for instance, we will have to manually create numerous animations. Thankfully, Unity can blend in between animations for us, with the CrossFade function. CrossFade interpolates one basic animation into another, creating more complex and unique animations for our character to play. We can even specify a speed of fading by adding an extra float parameter, like the following one:

animation.CrossFade("jump", 0.3);

0.3 seconds is a default value.

We will now add this functionality to our jump, right after we checked if our character didn't reach climax:

if(!Controller.jumpClimax){ animation.CrossFade("jump", 0.5,PlayMode.StopSameLayer); }

But what if our character reached jump climax? To fix that, we need to do the same thing we did before climax, but reverse the animation with the Rewind function:

else{ animation.Rewind ("jump"); }

The only difference is that, once a character reaches climax, we want to reverse the animation. We will give its speed a negative value to make it play backwards; the rest of it is the same as before.

Walk, run, and idle animations

The rest of the animations are as simple as jump animation, so here we go.

If the character is not moving in any direction (stands on the same spot), he should be playing idle animation:

else if (Controller.MoveDirection == Vector3.zero){ animation.CrossFade("idle"); }

This script goes after the first if statement, at the very top. To determine whether the character is moving or not, we used the MoveDirection vector from CH_Controller.

Now we are left to deal with different movements. Realistically, we don't want our character to move with exactly the same speed in all directions. We will assign different values to the Speed variable in the Controller script based on the direction in which the character is moving:

else if (Controller.MoveDirection.z> 0){} else if (Controller.MoveDirection.z< 0){} else if (Controller.MoveDirection.x> 0 || Controller.MoveDirection.x< 0){}

We will use the MoveDirection vector to check the player's movement direction. Positive or negative Z axis will tell us if the character is moving forward or backwards; X axis controls side walk.

To play those animations we need to do three things. They are as follows:

  1. Modify speed variable in CH_Controller.
  2. Assign animation speed.
  3. Crossfade the animation.

We can crossfade the animation as follows:

else if (Controller.MoveDirection.z> 0){ Controller.Speed = forwardSpeed; animation.CrossFade("walk_forward",0.5, PlayMode. StopSameLayer); } else if (Controller.MoveDirection.z< 0){ Controller.Speed = backwardSpeed; animation.CrossFade("walk_backward"); } else if (Controller.MoveDirection.x> 0 || Controller.
MoveDirection.x<0){ Controller.Speed = strafingSpeed; animation.CrossFade("walk_side",0.5, PlayMode.
StopSameLayer); }

We did exactly the same thing to every direction movement. The only exception should be forward movement. That's where we will implement running. In theory, we will check isRunning from CH_Controller and rewrite the function for moving forward as follows:

if (Controller.isRunning){ Controller.Speed = runningSpeed; animation.CrossFade("run",0.5, PlayMode.StopSameLayer); } else{ Controller.Speed = forwardSpeed; animation.CrossFade("walk_forward",0.5,PlayMode.StopSameLayer); }

The animation is now officially done.

 

 

 

Unity 3.x Scripting Write efficient, reusable scripts to build custom characters, game environments, and control enemy AI in your Unity game with this book and ebook.
Published: June 2012
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Books From Packt


 Unity iOS Essentials
Unity iOS Essentials

Unity 3D Game Development by Example Beginner's Guide
Unity 3D Game Development by Example Beginner's Guide

 Unity 3.x Game Development by Example Beginner's Guide
Unity 3.x Game Development by Example Beginner's Guide

Papervision3D Essentials
Papervision3D Essentials

Construct Game Development Beginners Guide
Construct Game Development Beginners Guide

OGRE 3D 1.7 Application Development Cookbook
OGRE 3D 1.7 Application Development Cookbook

Monkey Game Development: Beginner's Guide
Monkey Game Development: Beginner's Guide

Unity iOS Game Development Beginners Guide
Unity iOS Game Development Beginners Guide


Your rating: None Average: 3.5 (2 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
D
1
v
w
Z
r
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software