Movement, Camera Controls, and Collisions

One of the first things a player does when starting a new game is to try out character movement and camera controls. Not only is this exciting, but it lets your player know what kind of gameplay they can expect. Hero Born's character will be a capsule object that can be moved and rotated using the W, A, S, D, or arrow keys, respectively. 

We'll start by learning how to manipulate an object's Transform component and then replicate the same control scheme using applied force. This produces a more realistic movement effect. When we move the player, the camera will follow along from a position that is slightly behind and above the playermaking aiming easier when we implement the shooting mechanic. Finally, we'll explore how collisions and physical interactions are handled by Unity's physics system by working with our item pickup prefab. 

All of this will come together at a playable level, albeit without any action mechanics. It's also going to give us our first taste of C# being used to program game features by tying together the following topics:

  • Transform movement and rotation
  • Managing player inputs
  • Scripting camera behavior
  • Unity physics and applied force
  • Basic colliders and collision detection

Moving the player

When you're deciding on how best to move your player character around your virtual world, consider what's going to look the most realistic and not run your game into the ground with expensive computations. This is somewhat of a trade-off in most cases, and Unity is no different. 

The three most common ways of moving GameObjects and their results are as follows:

  • Option A: Use a GameObject's Transform component for movement and rotation. This is the easiest solution and the one we'll be working with first.
  • Option B: Attach a Rigidbody component to a GameObject and apply force in code. This solution relies on Unity's physics system to do the heavy lifting, delivering a far more realistic effect. We'll update our code to use this approach later on in this chapter to get a feel for both methods. 
Unity suggests sticking to a consistent approach when moving or rotating a GameObject; either manipulate an object's Transform or Rigidbody component, but never both at the same time.
  • Option C: Attach a ready-made Unity component or prefab, such as Character Controller or FirstPersonController. This cuts out the boilerplate code and still delivers a realistic effect while speeding up prototyping time.
You can find more information on the Character Controller component and its uses at https://docs.unity3d.com/ScriptReference/CharacterController.html.
The FirstPersonController prefab is available from the Standard Assets Package, which you can download from https://assetstore.unity.com/packages/essentials/asset-packs/standard-assets-32351.

Since you're just getting started with player movement in Unity, you'll start off using the player Transform in the next section, and then move on to Rigidbody physics later in the chapter.

Player setup

We want a third-person adventure setup for Hero Born, so we'll start with a capsule that can be controlled with keyboard input and a camera to follow the capsule as it moves. Even though these two GameObjects will work together in the game, we'll keep them and their scripts separate for better control. 

Before we can do any scripting, you'll need to add a player capsule to the scene, which is your next task.

Time for action – creating the player capsule

We can create a nice player capsule in just a few steps:

  1. Click on Create | 3D Object | Capsule from the Hierarchy panel and name it Player.
  2. Select the Player GameObject and click on Add Component at the bottom of the Inspector tab. Search for Rigidbody and hit Enter to add it.
  3. Expand the Constraints property at the bottom of the Rigidbody component:
    • Check the boxes for Freeze Rotation on the X and Y axes.
  1. Select the Materials folder and click on Create | Material. Name it Player_Mat.
  1. Change the Albedo property to a bright green and drag the material to the Player object in the Hierarchy panel:

You've created the Player out of a capsule primitive, a Rigidbody component, and a new bright green material. Don't worry about what the Rigidbody component is just yet—all you need to know right now is that it allows our capsule to interact with the physics system. We'll go into more detail at the end of this chapter when we discuss how Unity's physics system works. Before we get to that, we need to talk about a very important subject in 3D space: vectors.

Understanding vectors

Now that we have a player capsule and camera set up, we can start looking at how to move and rotate a GameObject using its Transform component. The Translate and Rotate methods are part of the Transform class that Unity provides, and each needs a vector parameter to perform its given function.

In Unity, vectors are used to hold position and direction data in 2D and 3D spaces, which is why they come in two varieties—Vector2 and Vector3. These can be used like any other variable type we've seen; they just hold different information. Since our game is in 3D, we'll be using Vector3 objects, which means we'll need to construct them using x, y, and z values. For 2D vectors, only the x and y positions are required. Remember, the most up-to-date orientation in your 3D scene will be displayed in the upper-right graphic that we discussed in the previous chapter, Chapter 6Getting Your Hands Dirty with Unity:

If you would like more information about vectors in Unity, refer to the documentation and scripting reference at https://docs.unity3d.com/ScriptReference/Vector3.html.

For instance, if we wanted to create a new vector to hold the origin position of our scene, we could use the following code:

Vector3 origin = new Vector(0f, 0f, 0f);

All we've done here is create a new Vector3 variable and then initialize it with a 0 for the position, 0 for the position, and 0 for the z position, in that order. Float values can be written with or without a decimal, but they always need to end with a lowercase f.

We can also create directional vectors by using the Vector2 or Vector3 class properties:

Vector3 forwardDirection = Vector3.forward;

Instead of holding a position, forwardDirection references the forward direction in our scene along the z-axis in the 3D space. We'll look at using vectors later in this chapter; however, for now, just get used to thinking about 3D movement in terms of x, y, and z positions and directions.

Don't worry if the concept of vectors is new to you—it's a complicated topic. Unity's vector cookbook is a great place to start: https://docs.unity3d.com/Manual/VectorCookbook.html.

Now that you understand vectors a bit more, you can start implementing the basics of moving the player capsule. For that, you'll need to gather player input from the keyboard, which is the topic of the following section.

Getting player input

Positions and directions are useful in themselves, but they can't generate movement without input from the player. This is where the Input class comes in, which handles everything from keystrokes and mouse position to acceleration and gyroscopic data. 

We're going to be using the W, A, S, D, and arrow keys for movement in Hero Born, coupled with a script that allows the camera to follow where the player points the mouse. To do that, we'll need to understand how input axes work.

First, go to Edit | Project Settings | Input to open up the Input Manager tab shown in the following screenshot:

You'll see a long list of Unity's default inputs already configured, but let's take the Horizontal axis as an example. You can see that the Horizontal input axis has the Positive and Negative buttons set to left and right, and the Alt Negative and Alt Positive buttons set to the a and d keys.

Whenever an input axis is queried from the code, its value will be between -1 and 1. For example, when the left arrow or keys are pushed down, the horizontal axis registers a -1 value. When those keys are released, the value returns to 0. Likewise, when the right arrow or D keys are used, the horizontal axis registers a value of 1. This allows us to capture four different inputs for a single axis with only one line of code, as opposed to writing out a long if-else statement chain for each. 

Capturing input axes is as simple as calling Input.GetAxis() and specifying the axis we want by name, which is what we'll do with the Horizontal and Vertical inputs in the following sections. As a side benefit, Unity applies a smoothing filter, which makes the input frame rate independent. You can also use Input.GetAxisRaw(), which doesn't have the smoothing filter. Documentation for GetAxisRaw can be found at https://docs.unity3d.com/ScriptReference/Input.GetAxisRaw.html.

Default inputs can be modified in any way you need, but you can also create custom axes by increasing the Size property in the Input Manager by 1 and renaming the copy that's been created for you. 
Unity recently released a new input system to account for the increased variety of platforms that it supports. We won't be using it in our project because it's overkill, but you can refer to this article as a starting point if you're interested, at https://blogs.unity3d.com/2019/10/14/introducing-the-new-input-system/.

Let's start getting our player moving using Unity's input system and a custom locomotion script of our own.

Time for action – player locomotion

Before you get the player moving, you'll need to attach a script to the player capsule:

  1. Create a new C# script in the Scripts folder, name it PlayerBehavior, and drag it into the Player capsule.
  2. Add the following code and save:
 public class PlayerBehavior : MonoBehaviour 
{
// 1
public float moveSpeed = 10f;
public float rotateSpeed = 75f;

// 2
private float vInput;
private float hInput;


void Update()
{
// 3
vInput = Input.GetAxis("Vertical") * moveSpeed;

// 4
hInput = Input.GetAxis("Horizontal") * rotateSpeed;


// 5
this.transform.Translate(Vector3.forward * vInput *
Time.deltaTime
);

// 6
this.transform.Rotate(Vector3.up * hInput * Time.deltaTime);
}
}
Using the this keyword is optional. Visual Studio 2019 may suggest that you remove it to simplify the code, but I prefer leaving it in for clarity.
When you have empty methods, such as Start, in this case, it's common to delete them for clarity. However, if you prefer to have them in your script, that's fine too. It's really up to your preference.

Here's a breakdown of the preceding code:

  1. Declares two public variables to be used as multipliers:
    • movespeed for how fast we want the Player to go forward and backward
    • rotateSpeed for how fast we want the Player to rotate left and right
  2. Declares two private variables to hold inputs from the player; initially set with no value:
    • vInput will store the vertical axis input.
    • hInput will store the horizontal axis input.
  3. Input.GetAxis("Vertical") detects when the up arrow, down arrow, W, or S keys are pressed and multiplies that value by moveSpeed:
    • The up arrow and W keys return a value of 1, which will move the player in the forward (positive) direction.
    • The down arrow and S keys return -1, which moves the player backward in the negative direction.
  4. Input.GetAxis("Horizontal") detects when the left arrow, right arrow, A, and D keys are pressed and multiplies that value by rotateSpeed:
    • The right arrow and D keys return a value of 1, which will rotate the capsule to the right.
    • The left arrow and A keys return a -1, rotating the capsule to the left.
If you're wondering whether it's possible to do all the movement calculations on one line, the simple answer is yes. However, it's better to have your code broken down, even if you're the only one reading it.
  1. Uses the Translate method, which takes in a Vector3 parameter, to move the capsule's Transform component:
    • Remember that the this keyword specifies the GameObject the current script is attached to, which, in this case, is the player capsule. 
    •  Vector3.forward multiplied by vInput and Time.deltaTime supplies the direction and speed the capsule needs to move forward or back along the z axis at the speed we've calculated.
    • Time.deltaTime will always return the value in seconds since the last frame of the game was executed. It's commonly used to smooth values that are captured or run in the Update method instead of letting it be determined by a device's frame rate.
  1. Uses the Rotate method to rotate the capsule's Transform component relative to the vector we pass in as a parameter:
    • Vector3.up multiplied by hInput and Time.deltaTime gives us the left/right rotation axis we want.
    • We use the this keyword and Time.deltaTime here for the same reasons.
As we discussed earlier, using direction vectors in the Translate and Rotate functions is only one way to go about this. We could have created new Vector3 variables from our axis inputs and used them as parameters just as easily.

When you click on Play, you'll be able to move the capsule forward and backward using the up/down arrow keys and the W/S keys, while rotating or turning with the left/right arrow keys and the A/D keys. With these few lines of code, you've set up two separate controls that are frame-rate independent and easily modified. However, our camera doesn't follow the capsule as it moves around, so let's fix that in the following section.

Adding a following Camera

The easiest way to get one GameObject to follow another is to make one of them a child of the other. However, this approach means that any kind of movement or rotation that happens to the player capsule also affects the camera, which is something we don't necessarily want. Luckily, we can easily set the position and rotation of the camera relative to the capsule with methods from the Transform class. It's your task to script out the camera logic in the next challenge.

Time for action  scripting camera behavior

Since we want the camera behavior to be entirely separate from how the player moves, we'll be controlling where the camera is positioned relative to a target we can set from the Inspector tab:

  1. Create a new C# script in the Scripts folder, name it CameraBehavior, and drag it into the Main Camera.
  1. Add the following code and save it:
 public class CameraBehavior : MonoBehaviour 
{
// 1
public Vector3 camOffset = new Vector3(0f, 1.2f, -2.6f);

// 2
private Transform target;

void Start()
{
// 3
target = GameObject.Find("Player").transform;
}

// 4
void LateUpdate()
{
// 5
this.transform.position = target.TransformPoint(camOffset);

// 6
this.transform.LookAt(target);
}

}

Here's a breakdown of the preceding code:

  1. Declares a Vector3 variable to store the distance we want between the Main Camera and the Player capsule:
    • We'll be able to manually set the x, y, and z positions of the camera offset in the Inspector because it's public.
    • These default values are what I think looks best, but feel free to experiment.
  2. Creates a variable to hold the player capsule's Transform information:
    • This will give us access to its position, rotation, and scale.
    • We don't want this data to be accessible outside of the CameraBehavior script, which is why it's private.
  3. Uses GameObject.Find to locate the capsule by name and retrieve its Transform property from the scene:
    • This means the capsule's x, y, and z positions are updated and stored in the target variable every frame.
  1. LateUpdate is a MonoBehavior method, like Start or Update, that executes after Update:
    • Since our PlayerBehavior script moves the capsule in its Update method, we want the code in CameraBehavior to run after the movement happens; this guarantees that target has the most up-to-date position to reference.
  2. Sets the camera's position to target.TransformPoint(camOffset) for every frame, which creates the following effect:
    • The TransformPoint method calculates and returns a relative position in the world space.
    • In this case, it returns the position of the target (our capsule) offset by 0 in the x-axis, 1.2 in the y-axis (putting the camera above the capsule), and -2.6 in the z-axis (putting the camera slightly behind the capsule).
  1. The LookAt method updates the capsule's rotation every frame, focusing on the Transform parameter we pass in, which, in this case, is the target:

This was a lot to take in, but it's easier to process if you break it down into its chronological steps:

  1. We created an offset position for the camera.
  2. We found and stored the player capsule's position.
  3. We manually updated its position and rotation every frame so that's it's always following at a set distance and looking at the player.
When using class methods that deliver platform-specific functionality, always remember to break things down to their most basic steps. This will help you to stay above water in new programming environments.

While the code you've written to manage player movement is perfectly functional, you might have noticed that it's a little jerky in places. To create a smoother, more realistic movement effect, you'll need to understand the basics of the Unity physics system, which you'll dive into next. 

Working with Unity physics

Up to this point. we haven't talked about how the Unity engine works, or how it manages to create lifelike interactions and movement in a virtual space. We'll spend the rest of this chapter learning the basics of Unity's physics system:

The two main components that power Unity's NVIDIA PhysX engine are as follows:

  • Rigidbody components, which allow GameObjects to be affected by gravity and add properties such as Mass and Drag. Rigidbody components can also be affected by an applied force, generating a more realistic movement:

  • Collider components, which determine how and when GameObjects enter and exit each other's physical space or simply collide and bounce away. While there should only be one Rigidbody component attached to a given GameObject, there can be several Collider components. This is commonly referred to as a compound Collider setup:

When two GameObjects collide with each other, the Rigidbody properties determine the resulting interaction. For example, if one GameObject's mass is higher than the other, the lighter GameObject will bounce away with more force, just like in real life. These two components are responsible for all physical interactions and simulated movement in Unity.

There are some caveats to using these components, which are best understood in terms of the types of movement Unity allows:

  • Kinematic movement happens when a Rigidbody component is attached to a GameObject, but it doesn't register to the physics system in the scene:
    • This is only used in certain cases and can be enabled by checking the Is Kinematic property of a Rigidbody component. Since we want our capsule to interact with the physics system, we won't be using this kind of motion.
  • Non-Kinematic movement is when a Rigidbody component is moved or rotated by applying force rather than manually changing a GameObject's Transform properties. Our goal for this section is to update the PlayerBehavior script to implement this type of motion.
The setup we have now, that is, manipulating the capsule's Transform component while using a Rigidbody component to interact with the physics system, was meant to get you thinking about movement and rotation in a 3D space. However, it's not meant for production and Unity suggests avoiding a mix of kinematic and non-kinematic movement in your code.

Your next task is to use applied force to convert the current movement system into a more realistic locomotion experience.

Rigidbody components in motion

Since our player has a Rigidbody component attached, we should let the physics engine control our movement instead of manually translating and rotating the Transform. There are two options when it comes to applying force:

  • You can do it directly by using Rigidbody class methods such as AddForce and AddTorque to move and rotate an object, respectively. This approach has its drawbacks and often requires additional code to compensate for unexpected physics behavior.
  • Alternatively, you can use other Rigidbody class methods such as MovePosition and MoveRotation, which still use applied force but take care of edge cases behind the scenes. 
We'll take the second route in the next section, but if you're curious about manually applying force and torque to your GameObjects, then start here: https://docs.unity3d.com/ScriptReference/Rigidbody.AddForce.html.

Either of these will give the player a more lifelike feel and allow us to add in jumping and dashing mechanics in Chapter 8, Scripting Game Mechanics.

If you're curious about what happens when a moving object without a Rigidbody component interacts with pieces of the environment that have them equipped, remove the component from the Player and run around the arena. Congratulations—you're a ghost and can walk through walls! Don't forget to add the Rigidbody component back, though!

The player capsule already has a Rigidbody component attached, which means that you can access and modify its properties. First, though, you'll need to find and store the component, which is your next challenge.

Time for action – accessing the Rigidbody component

You'll need to access and store the Rigidbody component on our player capsule before modifying it:

  1. Update PlayerBehavior with the following changes:
 public class PlayerBehavior : MonoBehaviour 
{
public float moveSpeed = 10f;
public float rotateSpeed = 75f;

private float vInput;
private float hInput;

// 1
private Rigidbody _rb;

// 2
void Start()
{
// 3
_rb = GetComponent<Rigidbody>();
}

void Update()
{
vInput = Input.GetAxis("Vertical") * moveSpeed;
hInput = Input.GetAxis("Horizontal") * rotateSpeed;


/* 4
this.transform.Translate(Vector3.forward * vInput *
Time.deltaTime);
this.transform.Rotate(Vector3.up * hInput * Time.deltaTime);
*/
}

}

Here's a breakdown of the preceding code:

  1. Adds a private Rigidbody-type variable that will contain the capsule's Rigidbody component information.
  2. The Start method fires when a script is initialized in a scene, which happens when you click on Play, and should be used any time variables need to be set at the beginning of a class.
  3. The GetComponent method checks whether the component type we're looking for, in this case, Rigidbody, exists on the GameObject the script is attached to and returns it:
    • If the component isn't attached to the GameObject, the method will return null, but since we know there's one on the player, we won't worry about error checking right now.
  4. Comments the Transform and Rotate method calls in the Update function so that we won't be running two different kinds of player controls:
    • We want to keep our code that captures player input so that we can still use it later on.

You've initialized and stored the Rigidbody component on the player capsule and commented out the obsolete Transform code to set the stage for physics-based movement. The character is now ready for the next challenge, which is to add force.

Time for action – moving the Rigidbody component

Use the following steps to move and rotate the Rigidbody component:

  1. Add in the following code to PlayerBehavior underneath the Update method, and then save the file:
// 1
void FixedUpdate()
{
// 2
Vector3 rotation = Vector3.up * hInput;

// 3
Quaternion angleRot = Quaternion.Euler(rotation *
Time.fixedDeltaTime);

// 4
_rb.MovePosition(this.transform.position +
this.transform.forward * vInput * Time.fixedDeltaTime);

// 5
_rb.MoveRotation(_rb.rotation * angleRot);
}

Here's a breakdown of the preceding code:

  1. Any physics- or Rigidbody-related code always goes inside FixedUpdate, rather than Update or the other MonoBehavior methods:
    • FixedUpdate is frame rate independent and is used for all physics code. 
  2. Creates a new Vector3 variable to store our left and right rotation:
    • Vector3.up * hInput is the same rotation vector we used with the Rotate method in the previous example.
  1. Quaternion.Euler takes a Vector3 parameter and returns a rotation value in Euler angles:
    • We need a Quaternion value instead of a Vector3 parameter to use the MoveRotation method. This is just a conversion to the rotation type that Unity prefers.
    • We multiply by Time.fixedDeltaTime for the same reason we used Time.deltaTime in Update
  1. Calls MovePosition on our _rb component, which takes in a Vector3 parameter and applies force accordingly:
    • The vector that's used can be broken down as follows: the capsule's Transform position in the forward direction, multiplied by the vertical inputs and Time.fixedDeltaTime.
    • The Rigidbody component takes care of applying movement force to satisfy our vector parameter.
  2. Calls the MoveRotate method on the _rb component, which also takes in a Vector3 parameter and applies the corresponding forces under the hood:
    • angleRot already has the horizontal inputs from the keyboard, so all we need to do is multiply the current Rigidbody rotation by angleRot to get the same left and right rotation.
Be aware that MovePosition and MoveRotation work differently for non-kinematic game objects. You can find more information in the Rigidbody scripting reference at https://docs.unity3d.com/ScriptReference/Rigidbody.html.

If you click on Play now, you'll be able to move forward and backward in the direction your looking, as well as rotate around the axis. Applied force produces stronger effects than translating and rotating a Transform component, so you may need to fine-tune the moveSpeed and rotateSpeed variables in the Inspector. You've now recreated the same type of movement scheme as before, just with more realistic physics.

If you run up a ramp or drop off the central platform, you might see the player launch into the air, or slowly drop to the ground. Even though the Rigidbody component is set to use gravity, it's fairly weak. We'll tackle applying our gravity to the player in the next chapter when we implement the jump mechanic. For now, your job is to get comfortable with how Collider components handle collisions in Unity.

Colliders and collisions

Collider components not only allow GameObjects to be recognized by Unity's physics system, but they also make interactions and collisions possible. Think of colliders as invisible force fields that surround GameObjects; they can be passed through or bumped into depending on their settings, and they come with a host of methods that fire during different interactions. 

Unity's physics system works differently for 2D and 3D games, so we will only be covering the 3D topics in this book. If you're interested in making 2D games, refer to the Rigidbody2D component and the list of available 2D colliders.

Take a look at the following screenshot of the Capsule in the Pickup_Prefab object hierarchy:

The green shape around the object is the Capsule Collider, which can be moved and scaled using the Center, Radius, and Height properties. When a primitive is created, the Collider matches the primitive's shape by default; since we created a Capsule primitive, it comes with a Capsule Collider.

Colliders also come in Box, Sphere, and Mesh shapes and can be manually added from the Component | Physics menu or from the Add Component button in the Inspector.

When a Collider comes into contact with other components, it sends out what's called a message, or broadcast. Any script that adds one or more of those methods will receive a notification when the Collider sends out a message. This is called an Event, which is a topic that we'll cover in Chapter 12, The Journey Continues.

For example, when two GameObjects with colliders come into contact, they both send out the OnCollisionEnter message, complete with a reference to the object they ran into. This information can be used for a variety of interactive events, but the simplest one is picking up an item, which we'll tackle next.

A complete list of Collider notifications can be found here underneath the Messages header at https://docs.unity3d.com/ScriptReference/Collider.html.

Collision and trigger events are only sent out when the colliding objects belong to a specific combination of Collider, Trigger, and RigidBody components and kinematic or non-kinematic motion. You can find details under the Collision action matrix section at https://docs.unity3d.com/Manual/CollidersOverview.html.

The health item you previously created is a perfect place to test out how collisions work. You'll tackle that in the next challenge.

Time for action – picking up an item

To update the Pickup_Item object using collision logic, you need to do the following:

  1. Create a new C# script in the Scripts folder, name it ItemBehavior, and then drag it into the Health_Pickup object:
    • Any script that uses collision detection MUST be attached to a GameObject with a Collider component, even if it's the child of a prefab.
  2. Create an empty GameObject, named Item:
    • Make the Health_Pickup object and the Particle System object its children.
    • Drag Item into the Prefabs folder:

  1. Replace the default code in ItemBehavior with the following, and then save it:
 public class ItemBehavior : MonoBehaviour 
{
// 1
void OnCollisionEnter(Collision collision)
{
// 2
if(collision.gameObject.name == "Player")
{
// 3
Destroy(this.transform.parent.gameObject);

// 4
Debug.Log("Item collected!");
}
}
}
  1. Click on Play and move the player over the capsule to pick it up!

Here's a breakdown of the preceding code:

  1. When another object runs into the Item prefab with its isTrigger turned off, Unity automatically calls the OnCollisionEnter method:
    • OnCollisionEnter comes with a parameter that stores a reference to the Collider that ran into it.
    • Notice that the collision is of type Collision, not Collider.
  2. The Collision class has a property, called gameObject, which holds a reference to the colliding GameObject's Collider:
    • We can use this property to get the GameObject's name and use an if statement to check whether the colliding object is the player.
  3. If the colliding object is the player, we'll call the Destroy() method, which takes in a GameObject parameter:
    • We want the entire Item prefab object to be destroyed. 
    • Since ItemBehavior is attached to Health_Item, which is a child object of Pickup_Item, we use this.transform.parent.gameObject to set the Item prefab to be destroyed.
  1. It then prints out a simple log to the console that we have collected an item:

We've set up ItemBehavior to essentially listen for any collisions with the Health_Pickup object inside the Item prefab. Whenever a collision occurs, ItemBehavior uses OnCollisionEnter() and checks whether the colliding object is the player and, if so, destroys (or collects) the item. If you're feeling lost, think of the collision code we wrote as a receiver for notifications from the Item capsule; any time it's hit, the code fires. 

It's also important to understand that we could have created a similar script with an OnCollisionEnter() method, attached it to the player, and then checked whether the colliding object was an Item prefab. Collision logic depends on the perspective of the object being collided with. 

Now the question is, how would you set up a collision without stopping the colliding objects from moving through each other? We'll tackle that in the next section.

Using Collider triggers

By default, colliders are set with the isTrigger property unchecked, meaning that the physics system treats them as solid objects. However, in some cases, you'll want to be able to pass through a Collider component without it stopping your GameObject. This is where triggers come in. With isTrigger checked, a GameObject can pass through it, but the Collider will send out the OnTriggerEnter, OnTriggerExit, and OnTriggerStay notifications.

Triggers are most useful when you need to detect when a GameObject enters a certain area or passes a certain point. We'll use this to set up the areas around our enemies; if the player walks into the trigger zone, the enemies will be alerted, and, later on, attack the player. For now, you're going to focus just on the enemy logic in the following challenge.

Time for action – creating an enemy

Use the following steps to create an enemy:

  1. Create a new primitive using Create | 3D Object | Capsule in the Hierarchy panel and name it Enemy.
  2. Inside the Materials folder, use Create | Material, name it Enemy_Mat, and set its Albedo property to a bright red:
    • Drag and drop Enemy_Mat into the Enemy GameObject.
  3. With Enemy selected, click on Add Component and search for Sphere Collider. Then, hit Enter to add it:
    • Check the isTrigger property box and change the Radius to 8:

Our new Enemy is now surrounded by an 8-unit trigger radius shaped like a sphere. Any time another object enters, stays inside, or exits that area, Unity will send out notifications that we can capture, just like we did with collisions. Your next challenge will be to capture that notification and act on it in code.

Time for action – capturing trigger events

To capture trigger events, you'll need to create a new script by following these steps:

  1. Create a new C# script in the Scripts folder, name it EnemyBehavior, and then drag it into Enemy.
  1. Add the following code and save the file:
 public class EnemyBehavior : MonoBehaviour 
{
// 1
void OnTriggerEnter(Collider other)
{
//2
if(other.name == "Player")
{
Debug.Log("Player detected - attack!");
}
}

// 3
void OnTriggerExit(Collider other)
{
// 4
if(other.name == "Player")
{
Debug.Log("Player out of range, resume patrol");
}
}
}
  1. Click on Play and walk over to the Enemy to set off the first notification:
    • Walk away from the Enemy to set off the second notification.

Here's a breakdown of the preceding code:

  1. OnTriggerEnter() is fired whenever an object enters the Enemy Sphere Collider radius:
    • Similar to OnCollisionEnter(), OnTriggerEnter() stores a reference to the trespassing object's Collider component.
    • Note that other is of type Collider, not Collision.
  2. We can use other to access the name of the colliding GameObject, and check whether it's the Player with an if statement:
    • If it is, the console prints out a log that the Player is in the danger zone.
  1. OnTriggerExit() is fired when an object leaves the Enemy Sphere Collider radius:
    • This method also has a reference to the colliding object's Collider component:

  1. We check the object leaving the Sphere Collider radius by name using another if statement:
    • If it's the Player, we print out another log to the console saying that they're safe:

The Sphere Collider on our Enemy sends out notifications when its area is invaded, and the EnemyBehavior script captures two of those events. Whenever the Player enters or exits the collision radius, a debug log appears in the console to let us know that the code is working. We'll continue to build on this in Chapter 9, Basic AI and Enemy Behavior

Unity makes use of something called the Component design pattern. Without going into too much detail, that's a fancy way of saying objects (and, by extension, their classes) should be responsible for their behavior. This is why we put separate collision scripts on the pickup item and enemy instead of having a single class handle everything. We'll discuss this further in Chapter 12, The Journey Continues.

Since this book is all about instilling as many good programming habits as possible, your last task for the chapter is to make sure all your core objects are converted into prefabs.

Hero's trial  all the prefabs!

To get the project ready for the next chapter, go ahead and drag the Player and Enemy objects into the Prefabs folder. Remember that, from now on, you always need to click on Apply in the Inspector tab to solidify any changes you make to these GameObjects.

With that done, continue to the Physics roundup section and make sure that you've internalized all the major topics we've covered before moving on.

Physics roundup

Before we wrap up the chapter, here are a few high-level concepts to cement what we've learned so far:

  • Rigidbody components add simulated real-world physics to GameObjects they are attached to.
  • Collider components interact with each other, as well as objects, using Rigidbody components:
    • If a Collider component is not a trigger, it acts as a solid object.
    • If a Collider component is a trigger, it can be walked through.
  • An object is kinematic if it uses a Rigidbody component and has Is Kinematic checked, telling the physics system to ignore it.
  • An object is non-kinematic if it uses a Rigidbody component and applied force or torque to power its movement and rotation.
  • Colliders send out notifications based on their interactions:
    • These notifications depend on whether the Collider component is set to be triggered or not.
    • Notifications can be received from either colliding party, and they come with reference variables that hold an object's collision information.

Remember, a topic as broad and complex as the Unity physics system isn't learned in a day. Use what you've learned here as a springboard to launch yourself into more intricate topics!

Summary

This wraps up your first experience of creating independent gameplay behaviors and tying them all together into a cohesive, albeit simple, game. You've used vectors and basic vector math to determine positions and angles in a 3D space, and you're familiar with player input and the two main methods of moving and rotating GameObjects. You've even gone down into the bowels of the Unity physics system to get comfortable with Rigidbody physics, collisions, triggers, and event notifications. All in all, Hero Born is off to a great start.

In the next chapter, we'll start tackling more game mechanics, including jumping, dashing, shooting projectiles, and interacting with parts of the environment. This will give you more hands-on experience of using force with Rigidbody components, gathering player input, and executing logic based on the desired scenario.

Pop quiz – player controls and physics

  1. What data type would you use to store 3D movement and rotation information?
  2. What built-in Unity component allows you to track and modify player controls?
  3. Which component adds real-world physics to a GameObject?
  4. What method does Unity suggest using to execute physics-related code on GameObjects?
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.213.209