Book Image

Unity Certified Programmer: Exam Guide

By : Philip Walker
Book Image

Unity Certified Programmer: Exam Guide

By: Philip Walker

Overview of this book

Unity Certified Programmer is a global certification program by Unity for anyone looking to become a professional Unity developer. The official Unity programmer exam will not only validate your Unity knowledge and skills, but also enable you to be part of the Unity community. This study guide will start by building on your understanding of C# programming and take you through the process of downloading and installing Unity. You’ll understand how Unity works and get to grips with the core objectives of the Unity exam. As you advance, you’ll enhance your skills by creating an enjoyable side-scrolling shooter game that can be played within the Unity Editor or any recent Android mobile device. This Unity book will test your knowledge with self-assessment questions and help you take your skills to an advanced level by working with Unity tools such as the Animator, Particle Effects, Lighting, UI/UX, Scriptable Objects, and debugging. By the end of this book, you’ll have developed a solid understanding of the different tools in Unity and understand how to create impressive Unity applications by making the most of its toolset.
Table of Contents (17 chapters)
14
Full Unity Programmer Mock Exam

Setting up our Player, PlayerSpawner, and PlayerBullet scripts

In the following series of sections, we are going to create three of the scripts that will cover the following: spawning the player, the player's controls, and the player's bullet.

The scripts we will be creating and including are as follows:

  • PlayerSpawner: Creates and calibrates the player.
  • Player: Player controls and general functionality.
  • PlayerBullet: Movement and general functionality.
  • IActorTemplate: A template of the expected rules assigned to a given object (already made).
  • SOActorModel: A set of values that can be altered by non-programmers (already made).

We will cover all of these scripts thoroughly and break down each of their purposes, as well as how they depend on and communicate with one another. We will start with the PlayerSpawner, which will create the player's ship and issue its values.

Setting up our PlayerSpawner script

The purpose of the PlayerSpawner script is to be attached to a game object, resulting in the player appearing at its position in the game. The PlayerSpawnerscript will also set the player's values when it is created. For example, if our player had a particular speed value, or if it had received an upgrade from the shop; the PlayerSpawnerscript would grab these values and apply them to the Player script.

The following diagram shows a partial view of the PlayerSpawner class in the game's framework and its relation with the other classes around it:

As we can see, the PlayerSpawnerscript is connected to four other scripts:

  1. Player: PlayerSpawner is connected to Player because it creates the player.
  2. SOActorModel - This is a ScriptableObject that gives the PlayerSpawner its values, which are then passed on to the Player.
  3. IActorTemplate- This is theinterfacethat generalizes the script with other common functions.
  4. GameManager- This will send and receive general game information from and to thePlayerSpawner script.

Before we create our PlayerSpawner script, it would be good housekeeping to create an empty game object to store anything to do with our player, their bullets, and whatever else the player might create in our testLevel scene.

Make and name the game object by doing the following:

  1. Right-click the Hierarchy window in its open space.
  2. A drop-down list will appear. From the list, select Create Empty.
  3. Name the game object _Player.

That's all that we need to do. Now, let's make a start with the PlayerSpawner script:

  1. In the Project window, create a script in the Assets/Resources/Scripts folder with the filename PlayerSpawner.
  2. Open the script and make sure we have the following library entered at the top of our script:
          using UnityEngine;
        

We only require using UnityEngine as it covers all of the objects we need in the script.

  1. Continue by making sure our class is labeled as follows :
public class PlayerSpawner : MonoBehaviour
{

It is common in Unity to inherit MonoBehaviour to give the script more functionality.

  1. Continue by entering the script's global variables:
   SOActorModel actorModel;
GameObject playerShip;

Inside thePlayerSpawnerclass, we add two global variables: the first variable is the actorModel, which holds a scriptable object asset that will contain values for the player ship, and the second variable will hold our player ship once it's created from ourCreatePlayermethod.

  1. Continue by entering the script's Start function:
void Start()
{
CreatePlayer();
}

After the global variables, we add aStartfunction that will run automatically as soon as the game object holding thePlayerSpawnerscript is active at runtime.

Inside the scope of theStartfunction is a method that we are going to create calledCreatePlayer.

  1. Continue by entering the CreatePlayer method:
void CreatePlayer()
{
//CREATE PLAYER
actorModel = Object.Instantiate(Resources.Load
("Script/ScriptableObject/Player_Default"))
as SOActorModel;
playerShip = GameObject.Instantiate(actorModel.actor)
as GameObject;
playerShip.GetComponent<Player>().ActorStats(actorModel);

//SET PLAYER UP

}
}

I have split the CreatePlayer method into two commented-out parts (//CREATE PLAYERand//SET PLAYER UP) due to its size.

This first part of the CreatePlayer method will instantiate the player ship ScriptableObject asset and store it in the actorModelvariable. We then instantiate a game object that refers to our ScriptableObject that holds the game object called actor in our game object variable named playerShip. Finally, we apply our ScriptableObject asset to the playerShip method called ActorStats that exists in the Player component script (which we will create later on in this chapter).

  1. Continue on inside the CreatePlayer method to add the second half:
//SET PLAYER UP
playerShip.transform.rotation = Quaternion.Euler(0,180,0);
playerShip.transform.localScale = new Vector3(60,60,60);
playerShip.name = "Player";
playerShip.transform.SetParent(this.transform);
playerShip.transform.position = Vector3.zero;

In the second half of theCreatePlayermethod, we add more code at the same point where we have commented//SET PLAYER UP.

The code from //SET PLAYER UP onward is dedicated to setting up the player's ship in the correct position at the start of the level.

The code does the following:

  • Sets the rotation of the player's ship to face the right way
  • Sets the scale of the player ship to 60 on all axes

When we instantiate any game object, Unity will add (Clone) to the end of the game object's name. We can rename it to Player.

  • We make the playerShip game object a child of the _Player game object in the Hierarchy window so that we can easily find it.
  • Finally, we reset the player ship's position.

That is our PlayerSpawner script coded. Now, in the next section, we need to create and attach this script to a game object and name it. Make sure to save the script before moving on.

Creating the PlayerSpawner game object

In this section, we will create a game object that will hold our newly created PlayerSpawner script, and then we will position the PlayerSpawner game object in the testLevel scene.

To create and set up our PlayerSpawner game object, we need to do the following:

  1. In the Hierarchy window, create an empty game object and name it PlayerSpawner.
  2. Drag and drop the PlayerSpawner game object onto the _Player (remember _Player is the empty game object in our scene) game object to make the PlayerSpawner its child.

Because our PlayerSpawner game object doesn't have anything visually applied to it, we can give it an icon.

  1. With the PlayerSpawner game object still selected in the Inspector window, click the multi-colored box to the left of its name. A selection of colors will be offered, as shown in the following screenshot:

  1. Pick a color. Now the PlayerSpawner game object will be given a label to show us where it is in the scene. This will now appear in the Scene window.

If you still can't see the icon in the Scene window, make sure 3D icons are turned off. You can check by clicking the Gizmos button in the top right of the Scene window and unchecking the 3D Icons box.

With the PlayerSpawner game object sitting inside the _Player game object in the Hierarchy window, we now need to give it the following transform property values, which will help two things. The first thing it will help to set the boundaries of our ship within the games screen ratio (we explain more about this in the next chapter), the second is for later on in the book, where we make it so the player ship will animate into the screen view. For now, we just need to give our PlayerSpawner game object the following values.

  1. With the PlayerSpawner game object still selected, in the Inspector window, give it the following Transform values:

  1. While still in the Inspector window, click Add Component and type PlayerSpawner until you see the script appear in the drop-down list.
  2. Click the PlayerSpawner script to add this to the PlayerSpawner game object.

We can't move the ship yet, nor can we fire because we haven't coded this in yet. In the following section, we will go through the player's controls, then we will move on to coding our player and its bullet to travel across the screen.

Setting up our Input Manager

Remember that this is a side-scrolling shooter game, so the controls will be two-dimensional even though our visuals are three-dimensional. Our focus now is to get the Players controls set up. To do this, we need to access the Input Manager:

  1. Select Edit, followed by Project Settings, then Input, as shown in the following screenshot:

  1. The Inspector window will change to the Input window.

The Input Manager will offer a list of all available controls for our game. We will first check what the controls are set to by default. There are a lot of options here, but as mentioned, we only need to browse through the properties that matter to us, namely:

    • Horizontal: Moves the player's ship along its x-axis
    • Vertical: Moves the player's ship along its y-axis
    • Fire1: Makes our player shoot

To check these three properties, we need to do the following:

    • Expand theAxesdropdown by clicking the arrow next to it.
    • ExpandHorizontal, as shown in the following screenshot:

    • Horizontal: The left button configures horizontal negatively (-1), and the right button configures it positively (+1). Alternative keypresses to this effect areAfor left andDfor right.

If we had analog controls such as a joystick or a steering wheel, we would likely need to be concerned about the influence of gravity when the player releases the controls and it returns to its center. Dead refers to the center of the analog controls. Sometimes controllers can be unbalanced and naturally lean to one side, so by increasing the dead zone, we can eliminate false feedback from the player that could be detected as a movement.

    • Vertical: This is the same as Horizontal, apart from the fact the negative button is down (-1), and the positive button is up (+1). Alternative buttons are S for down and W for up.
    • Fire1: This has a similar layout to Vertical, but with Ctrl as Fire (command on the Mac) (that is, the positive button), with its the alternative (positive) button being mouse 0 (that is, the left mouse button). For now, remove mouse 0 from the alternative button.

To find out more about the InputManager window, click the little blue book in the top right corner of the Input Manager panel.

Our controls are now set in the InputManager window, so let's move on to coding the Player script to take advantage of these controls.

Setting up our Player script

The Player script will be attached to the player ship game object, from which the player will be able to move and shoot, as well as inflict and receive damage. We will also make it so that the player ship won't go outside of the screenplay area. Before we continue, let's remind ourselves where the Player script lies in our game framework:

The Player script will be in contact with the following scripts:

  1. PlayerBullet: ThePlayerscript will create bullets to fire.
  2. AdditionalPlayerBehaviour: If the player ship has any extra information attached to it, as well as additional abilities,theAdditionalPlayerBehaviourwill cover this.
  3. PlayerSpawner: ThePlayerscript is created from thePlayerSpawner.
  4. IActorTemplate:Contains damage control and the properties forPlayer.
  5. GameManager: Extra information such as the number of lives, the score, the level, and whatever upgrades the player ship has accumulatedwill be stored in GameManager.
  6. SOActorModel:HoldsScriptableObjectproperties forPlayer.

Now that we are familiar with the Player script's relation to the other scripts, we can start coding it:

  1. In the Project window of the Unity editor, create a script in the Assets/Resources/Scripts folder with the filename Player.
  2. Open the script and add the IActorTemplate interface to the existing default code:
using UnityEngine;

public class Player : MonoBehaviour, IActorTemplate
{

The script will by default create a UnityEngine library (including some others), the name of the class, and MonoBehaviour. All of these are essential to make the script work in the Unity editor.

  1. Continuing with the Player script, enter the following global variables:

int travelSpeed;
int health;
int hitPower;
GameObject actor;
GameObject fire;

public int Health
{
get {return health;}
set {health = value;}
}

public GameObject Fire
{
get {return fire;}
set {fire = value;}
}

GameObject _Player;

float width;
float height;

We have entered a mixture of integers, floats, and game objects in our global variables; starting from the top, the first six variables will be updated from the player's SOActorModel script. travelSpeed is the speed of the player's ship, health is how many hits the player can take before dying, hitPower is the damage the ship would cause when colliding into something that could receive damage (the enemy), actor is the three-dimensional model used to represent the player, and finally, the fire variable is the three-dimensional model of which the player fires. If that seemed a little rushed, check the Introducing our ScriptableObject – SOActorModelsection, where we go into more detail about these variables.

The two public properties of Health and Fire are there to give access to our two private health and fire variables from other classes that require access.

The _Player variable will be used as a reference to the _Player game object in the scene.

The last two variables of width and height will be used to store the measured results of the world space dimensions of the screen the game is played in. We will discuss these two more in the next block of code.

While we are on the approach to the Start function code block next, some may question why we would pick Start over Awake when it comes to running a function's code content. Both functions run once at runtime; the only noticeable difference is that Awake runs before Start with regards to Unity's execution order, as can be seen in the documentation at https://docs.unity3d.com/Manual/ExecutionOrder.html.

For simplicity in our Unity project, we will vary between which of the two functions to use. This is so we avoid conflicts between several Awake functions running at the same time. As an example, one script may try to update its Text UI, but the variable updating the text may still be null at runtime because the script with the variable is still waiting for its content to be updated.

There is a way around to avoid conflicts between several Awake functions being called by several scripts at runtime by going to Unity's Script Execution Order in Edit | Project Settings | Script Execution Order.

If you would like to know more about the Script Execution Order, check the documentation at https://docs.unity3d.com/Manual/class-MonoManager.html.

  1. Continuing on with entering code into the Player script, next up, we will type out the Start function along with its content:
 void Start()
{
height = 1/(Camera.main.WorldToViewportPoint (new
Vector3(1,1,0)).y - .5f);
width = 1/(Camera.main.WorldToViewportPoint(new Vector3(1,1,0))
.x - .5f);

_Player = GameObject.Find("_Player");
}

As previously mentioned, the height and width variables will store our world space measurements. These are required so that we can clamp the player's ship inside the screen. Both the height and width lines of code use similar methods, the only difference is with the axis we are reading.

The Camera.maincomponent refers to the camera in our scene and the function it uses, WorldToViewportPoint, is to take the results from the game's three-dimensional world space and convert the results into viewport space. If you aren't sure what viewport space is, it's similar to what we know as a screen resolution, except its measurements, are in points and not pixels, and these points are measured from 0 to 1. The following diagram shows the comparison between screen and viewport measurements:

So, with viewports, no matter what the screens resolution is, the full height and width are 1 and everything between that is a fraction. So, for the height, we feed Vector3 to WorldToViewportPoint, where Vector3 represents a world space value followed by -0.5f, which sets its offset back to 0. Then we divide 1 (which is our full-screen size) by the result of our formula. This will give us our current world space height of the screen. We then apply the same principles for the width and use x instead of y and store the result.

Finally, the last line of code takes the reference of the _Player game object in the scene and stores it into our variable.

  1. Continuing on with the Player script, we have our Update function that is called on every frame. Enter the function along with the following two methods:
 void Update ()
{
Movement();
Attack();
}

The Update function runs the Movement method and Attack method every frame. We will go into depth about these two methods later on in the chapter.

The next method we are going to put into our Player script is the ActorStats method. This method is a requirement as we declare it in the interface we are inheriting.

  1. Just after the scope of our Update function, enter the following piece of code:
public void ActorStats(SOActorModel actorModel)
{
health = actorModel.health;
travelSpeed = actorModel.speed;
hitPower = actorModel.hitPower;
fire = actorModel.actorsBullets;
}

The code we have just entered assigns values from the player's SOActorModelScriptableObject asset we made earlier on in the chapter.

This method doesn't get run in our script but gets accessed by other classes, the reason being these variables hold values regarding our player and don't need to be anywhere else.

  1. Save the Player script.

Before we test what we have so far we need to attach our Player script to our player_ship in the Project window.

  1. In the Project window, navigate to Assets/Resources/Prefab and select the player_ship prefab.
  2. Select the Add Component button in the Inspector window. Type Player until the script appears and then select it.

With ourHierarchywindow containing the_Player, PlayerSpawnerand theGameManager game objects. We can see the player ship get created in ourGamewindow by pressingPlayin the Editor.

The followingscreenshotshows our game inPlaymode; note theHierarchywindow on the left with thePlayerSpawner game object as the parent of thePlayergame object; also note theGamewindow with its black background, and in the center, the player's ship is facing right and is located in the center of the screen. Finally, the far-right image showing ourScene window with ourPlayerSpawnericon:

Before moving on to the next section, keep a back up of thePlayerSpawnergame object by dragging and dropping it into the Projectwindow to Assets/Resources/Player. That way, if you lose the scene for whatever reason along with itsHierarchycontent, you can drag and drop your prefab back in. This should be a rule with any common active game object.

Let's move on to the next section where we'll continue to work on the Player script, but this time we will look at what happens when our player's game object comes in to contact with an enemy.

Colliding with an enemy – OnTriggerEnter

In this section, we are going to add a function to our Player script that will check to see what has collided with our player's game object during runtime. Currently, the only thing that can collide with our player is an enemy, but we can still demonstrate the use of Unity's own OnTriggerEnter function, which handles most of the work for us:

  1. Continuing on after the scope of our last method (ActorStats) in the Player script, we are going to add the following code that detects our enemy colliding with the player's ship:
  void OnTriggerEnter(Collider other)
{
if (other.tag == "Enemy")
{
if (health >= 1)
{
if (transform.Find("energy +1(Clone)"))
{
Destroy(transform.Find("energy +1(Clone)").gameObject);
health -= other.GetComponent<IActorTemplate>
().SendDamage();
}
else
{
health -= 1;
}
}

if (health <= 0)
{
Die();
}
}
}

Let's explain some of the code we have just entered into the Player script.

OnTriggerEnter(Collider other) is a function that Unity recognizes to check what has entered into the player's trigger collider.

We use an if statement to check whether the tag to the collider is called Enemy. Note when we create our enemy, we will give them an Enemytag so they are easily identified. If the tag does equal to Enemy, we drop into that if statement.

The next if statement checks to see whether our player's health is equal to or more than 1. If it is, that means the player can take a hit and continue without dying and also means we can go into its if statement.

We approach the third if statement that checks to see whether the collider has a game object named energy +1(Clone). The name of this object is the name of the shield the player can purchase in the game shop which we will add in Chapter 6, Purchasing In-Game Items and Advertisements. If the player has this energy +1(Clone) object, we can Destroy it with Unity's premade function. We also deduct the player's extra health from the enemies' SendDamage function. We will discuss SendDamage later on in the chapter.

Following after the third if statement is an else condition where, in the event that the player doesn't have an energy +1(Clone) game object, the player gets their health deducted.

Finally, if the player's health is at a value of zero or under, we run the Die method, which we will cover later in the chapter.

Don't forget to keep saving your work as we continue to add more code to the project.

Let's continue on with our Player script and add the functionality so the player can receive and send damage from and to the enemy.

In the next method, we are going to add two methods. The first method (TakeDamage) will take an integer called incomingDamage and use whatever the value is to deduct from our player's health value.

The second method (SendDamage) will return an integer of our hitPower value.

  1. Just below and outside of the scope of our ActorStats method, now add the following code:
public void TakeDamage(int incomingDamage)
{
health -= incomingDamage;
}

public int SendDamage()
{
return hitPower;
}

Let's continue with another method for the Player script and make it possible so the player can control the player ship around the Game window.

The Movement method

In this section, we will code the Movement method, which will take input from the player's joypad/keyboard and also make use of the height and width floats to keep the player's ship within the screen.

  1. Still in the Player script, make a start with the following method using the following content to check for the player's input:
void Movement()
{
if (Input.GetAxisRaw("Horizontal") > 0)
{
if (transform.localPosition.x < width + width/0.9f)
{
transform.localPosition += new Vector3
(Input.GetAxisRaw("Horizontal")
*Time.deltaTime*travelSpeed,0,0);
}
}

The Movement method will consist of detecting movement in four directions being made from the player; we'll start with when the player presses right on the controller/keyboard. We run an if statement that checks whether the Input Manager has detected any movement from the Horizontal property. If the GetAxisRaw detects a value higher than zero, we fall into the if statement's condition. Note that GetAxisRaw has no smoothing so the player's ship will instantly move unless extra code is added.

Next, we have another if statement, this checks whether the player has exceeded past the width (that is, of the screen's world space that we calculated earlier on in the chapter). We've also added an extra partial width to avoid the geometry of the player's ship leaving the screen. If the player's position is still under the width (and its buffer) value, we run the content inside the if statement.

The player's position is updated with a Vector3 struct, which holds the value of the Horizontal direction, multiplied by time per frame, multiplied by the travelSpeed we set from our ScriptableObject.

  1. Let's continue in the Movement method and add a similar if statement for moving the player ship to the left:
if (Input.GetAxisRaw("Horizontal") < 0)
{
if (transform.localPosition.x > width + width/6)
{
transform.localPosition += new Vector3
(Input.GetAxisRaw("Horizontal")
*Time.deltaTime*travelSpeed,0,0);
}
}

As we can see, the code is close to the previous block; the only difference is that our first if statement checks whether we are moving left; the second if statement checks if the player's position is greater than the width and a slightly different buffer.

Apart from that, the if statement and its content serves the same position, just in the opposite direction.

  1. Let's continue with our Movement method and add the if statement code for moving the player's ship down:
if (Input.GetAxisRaw("Vertical") < 0)
{
if (transform.localPosition.y > -height/3f)
{
transform.localPosition += new Vector3
(0,Input.GetAxisRaw("Vertical")*Time.deltaTime*travelSpeed,0);
}
}

Yet again, we follow the same rule from the previous two if statements, but this time, instead of Horizontal, we add the Verticalstring property. In the second if statement, we check whether the player's y-axis is higher than a negative height/3. The reason why we divide by this value is that later on in the book (Chapter 9, Creating a 2D Shop Interface and In-Game HUD) we will be adding graphics at the bottom of the screen that will restrict the players view.

  1. Let's move on to the last if statement in the Movement method, up:
if (Input.GetAxisRaw("Vertical") > 0)
{
if (transform.localPosition.y < height/2.5f)
{
transform.localPosition += new Vector3
(0,Input.GetAxisRaw("Vertical")*Time.deltaTime*travelSpeed,0);
}
}
}

As before, this if statement carries a similar role, but this time it's checking whether the player's position is under the height/2.5f value. A buffer is applied to stop the three-dimensional geometry from leaving the top of the screen.

When making a game, sometimes it occurs that when the player moves diagonally, their speed increases. This is because the player is effectively pressing two directions at the same time instead of just one.

To make it so a direction has just the magnitude of 1, we can use Unity's pre-made Normalize function.

To find out more about this function, check the documentation at https://docs.unity3d.com/ScriptReference/Vector3.Normalize.html.
  1. Don't forget to save the script.

We will continue on with the Player script by adding the Die method.

The Die method

Adding the Die method to the Player script will make it so our player can be destroyed. Currently, inside the Die method is a Unity function called Destroy; this function will delete whatever game object is within its parameter.

Enter the following method in the Player script to destroy the player:

public void Die()
{
Destroy(this.gameObject);
}

Let's move on to the last method in the Player script, which is to attack.

The Attack method

In this section, we will add the content to theAttackmethod in thePlayerscript.

The purpose of thisAttackmethod is to receive input from the player, create a bullet, point the bullet in the correct direction, and make the bullet a child of the Player game object to keep our Hierarchy window tidy.

Enter the following Attack method into the Player script to allow the player to fire bullets:

public void Attack()
{
if (Input.GetButtonDown("Fire1"))
{
GameObject bullet = GameObject.Instantiate
(fire,transform.position,Quaternion.Euler
(new Vector3(0, 0, 0))) as GameObject;
bullet.transform.SetParent(_Player.transform);
bullet.transform.localScale = new Vector3(7,7,7);
}
}
}

Inside the Attack method, we call an if statement that checks whether the player has pressed the Fire1 button (Left Ctrl on Windows; command if you are using a Mac). If the player has pressed the Fire1 button, we will drop into the if statement's scope.

When a developer refers to the scope of a function, if statement, class, and so on, they are referring to what is happening between the opening and closing of the curly braces. For example, if the following code has a higher value in its money variable, the following if statement will run:

if (money > costOfPizza)
{
//Whatever happens between the top and bottom of the two curly braces is within the if statements scope.
}

Within theifstatement, we make anotherifstatement to make sure that when clicking the mouse, we are clicking on the screen and not anything UI related. This will become more relevant when we look at adding a Pause button in Chapter 10, Pausing the Game, Altering Sound, and a Mock Test. If we do click something UI related we callreturn, which means we exit theifstatement so that we don't fire a shot.

Next, we Instantiate our PlayerBullet game object from its instance name fire. We also face thefiregame object to the right, relative to the screen, and move it toward oncoming enemies. We store the results of creating and orienting our game object in a variable namedbullet.

We then set the size of the bullet to be seven times larger than its original size, which makes it look bigger.

Finally, within theifstatement, we make our bullet game object sit within a single game object with the variable name_Player.

That is all of the code required for the Player script! Make sure to save the script before moving on.

In the next section, we are going to move on to a different player script that controls what happens when the player fires their bullet.

Setting up our PlayerBullet script

In this section, we will be creating a bullet that will travel across the screen from the player's ship.

You will notice how similar thePlayerBulletscript is to thePlayerscript because it carries theIActorTemplateandSOActorModelscripts, which are already coded into thePlayerscript.

Let's create ourPlayerBulletscript:

  1. In the Project window of the Unity editor, create a script in the Assets/Resources/Scripts folder with the filename PlayerBullet.
  2. Open the script and check/enter the following code at the top of the script:
          using UnityEngine;
        

By default, we require the UnityEngine library to give the script functionality.

  1. Let's continue by checking the correct class name and entering the following inheritance:
          public class PlayerBullet : MonoBehaviour, IActorTemplate {
        

We declare the public class and by default inherit MonoBehaviour. We also inherit the IActorTemplate interface to give our game object-related methods from the other game object scripts, such as SendDamage, and TakeDamage.

  1. Enter the following global variables into the PlayerBullet script:
GameObject actor;
int hitPower;
int health;
int travelSpeed;

[SerializeField]
SOActorModel bulletModel;

All the variables we add are private. The last variable has a SerializeField attribute added. SerializeField makes it possible for this variable to be visible in the Inspector window, so even though it's private, we can still drag and drop assets into its field (which we will do shortly). More information on the SerializeField attributes can be found at https://docs.unity3d.com/ScriptReference/SerializeField.html.

  1. Next, we'll move on and enter the Awake function along with its content:
void Awake()
{
ActorStats(bulletModel);
}

In our Awakefunction is theActorStats method, which is a requirement because we are inheriting an interface that declares it.

  1. Continue by entering the SendDamage and TakeDamage methods:
 public int SendDamage()
{
return hitPower;
}

public void TakeDamage(int incomingDamage)
{
health -= incomingDamage;
}

As mentioned already in this chapter, we require these methods to send and receive damage.

  1. Moving on, we enter the Die method along with its content:
 public void Die()
{
Destroy(this.gameObject);
}

Another method to include from our interface is the Die method.

  1. Next, enter the ActorStats method:
 public void ActorStats(SOActorModel actorModel)
{
hitPower = actorModel.hitPower;
health = actorModel.health;
travelSpeed = actorModel.speed;
actor = actorModel.actor;
}

The last method that we inherit from our interface is the ActorStats method, which will hold our ScriptableObject asset. This asset will then be assigned to our PlayerBullet script's global variables.

  1. The next function is the OnTriggerEnter along with its if statement condition checks, as follows:
void OnTriggerEnter(Collider other)
{
if (other.tag == "Enemy")
{
if(other.GetComponent<IActorTemplate>() != null)
{
if (health >= 1)
{
health -= other.GetComponent<IActorTemplate>
().SendDamage();
}
if (health <= 0)
{
Die();
}
}
}
}

In the previous block of code, we run a check to see if our bullet has collided with an "Enemy" tagged collider. If the collider is tagged as "Enemy" to the player, we then check to see whether the collider holds an IActorTemplate interface. If it doesn't then it's likely the "Enemy" collider could be an obstacle. Otherwise, we deduct health from the Enemy game object and check to see if it's dead.

  1. Now, let's enter Unity's Update function for the bullet's movement:
void Update ()
{
transform.position += new
Vector3(travelSpeed,0,0)*Time.deltaTime;
}

The Update function adds to its x-axis each frame based on its travelSpeed value multiplied by Time.deltaTime (Time.deltaTime is the time in seconds from the last frame).

If you would like to know more aboutTime.deltaTime, check the documentation at https://docs.unity3d.com/ScriptReference/Time-deltaTime.html.
  1. Next, enter Unity's OnBecameInvisible function:
    void OnBecameInvisible() 
{
Destroy(gameObject);
}
}

This last function will remove any unnecessary bullets that have left the screen. This will help the performance of our game and keep it tidy. Make sure to have saved the script before continuing.

Next, we need to apply the PlayerBullet script to our Player_Bullet prefab:

  1. Navigate to Assets/Resources/Prefab/Player and select Player_Bullet.
  2. With Player_Bulletselected, click theAdd Componentbutton in theInspectorwindow and typePlayerBulletuntil you see the PlayerBulletscript.
  3. Select the script and add the PlayerBullet asset to it from the Bullet Model field (drag the asset into the field or click the remote button to the right of its field).

The following screenshot shows our Player_Bullet with its script and asset:

We can now move on to the next section about making enemies for the player to attack!