Unity Certified Programmer: Exam Guide

5 (1 reviews total)
By Philip Walker
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Adding and Manipulating Objects

About 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.

Publication date:
June 2020
Publisher
Packt
Pages
762
ISBN
9781838828424

 
Adding and Manipulating Objects

In the previous chapter, we discussed the importance of the Official Unity Programmer exam and what benefits it can produce for any developer who is looking to reassure either themselves or others in understanding programming in Unity. We also discussed the building blocks of being a programmer in general and our game's design brief.

As we are programmers working on a game engine, it is likely you will be working for a range of industries. In many of these industries, you will be issued with a technical brief/documentation (well, you should be!) for building the application. With this project, we are making a game and the game design brief is effectively the blueprint for making this game. In this chapter, we will be applying the majority of our code, game objects, prefabs, and more, based on the guidance of the brief and the game framework. We will be reminding ourselves of the brief and game framework during this chapter and will transfer specific information across into our code.

With regard to our code, we will be covering the importance of interfaces and scriptable objects to help structure and uniform our code to help it from bloating unnecessarily, which we covered in Chapter 1, Setting Up and Structuring Our Project with SOLID principles. We will also be getting used to the Unity editor and becoming familiar with game objects, prefabs, and importing three-dimensional models to animate.

In this chapter, we'll be covering the following topics:

  • Setting up our Unity project
  • Introducing our interface (IActorTemplate)
  • Introducing our ScriptableObject (SOActorModel)
  • Setting up our Player, PlayerSpawner, and PlayerBullet scripts
  • Planning and creating our enemy
  • Setting up our EnemySpawner and enemy script

The next section will outline the exam objectives covered in this chapter.

 

Core exam skills covered in this chapter

Programming core interactions

  • Implement and configure game object behavior and physics.
  • Implement and configure inputs and controls.
  • Implement and configure camera views and movement.

Working in the art pipeline

  • Understand lighting, and write scripts that interact with Unity’s lighting API.
  • Understand two- and three-dimensional animation, and write scripts that interact with Unity’s animation API.

Programming for scene and environment design

  • Identify methods for implementing game object instantiation, destruction, and management.

Working in professional software development teams

  • Recognize concepts associated with the uses and impact of version control, using technologies such as Unity Collaborate.
  • Demonstrate knowledge of developer testing and its impact on the software development process, including Unity Profiler and traditional debugging and testing techniques.
  • Recognize techniques for structuring scripts for modularity, readability, and re-usability.
 

Technical requirements

The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide/tree/master/Chapter02.

You can download the entire chapter project files at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide/archive/master.zip.

All content for this chapter is held in the relevant unitypackage file, including a Complete folder that contains all of the work we'll carry out in the chapter, so if at any point you need some reference material or extra guidance, be sure to check it out.

Check out the following video to see the Code in Action: https://bit.ly/3i2OnW6.

 

Setting up our Unity project

Things can get messy quickly in a project if we don't manage our files correctly by placing them into the allocated folders. If you want to structure your folders your own way, or during the book you decide to stray away from how I'm doing it, that's also fine. Just try and be conscious of your future self or other people working on this project when it comes to finding and organizing files.

Open the project up if you haven't already and create the following folders:

  1. Model contains 3D models (player ship, enemies, bullets, and so on).
  2. Prefab holds instances of game objects (these are created within Unity).
  3. Scene stores our first-level scene as well as other levels.
  4. Script contains all of our code.
  5. Material stores our game object materials.
You should know what a prefab is, as it's one of the main parts of what makes Unity so quick and easy to use. However, if you don't: it's typically your game object with its settings and components stored in an instance. You can store your game objects in your Project window as prefabs by dragging the game object from the Hierarchy window. A blue box icon will be generated following the game object's name, and if you select the prefab in the Project window, its Inspector window details will show all its stored values. If you would like to know more about prefabs, you can check out the documentation at https://docs.unity3d.com/Manual/Prefabs.html.

The following screenshot shows you how to create these folders:

Follow the steps given below:

  1. Within our Prefab folder, create another two folders, Enemies and Player.
  2. Inside the Script folder, create a folder called ScriptableObject.
  3. Create a folder in the Assets folder called Resources, and move our Model, Prefab, Script, and Material folders into it. The following screenshot shows what our folder structure should look like now:

Resources is a special folder that Unity recognizes. It will allow us to load assets while the game is running. For more information about the Resources folder, check the documentation at https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity6.html.

Provided in the Technical requirements section is the download link for the GitHub repository of this chapter. Once downloaded, double-click the Chapter2.unitypackage file and we will be given a list of assets to import into our Unity project:

  • Player_ship.fbx
  • enemy_wave.fbx

The following screenshot shows the import window of the assets we are about to bring into our project:

Make sure all assets are ticked and click the Import button in the bottom-right corner of the window. We can now move on to organizing our files and folders in the Project window in the next section.

Creating prefabs

In this section, we going to create three prefabs: the player, the player's bullet, and the enemy. These prefabs will hold components, settings, and other property values for our game that we can instantiate throughout our game.

Let's start with making our player_ship.fbx file into a prefab instance by doing the following.

Sometimes, when importing any three-dimensional file, each file may contain extra data that we might not need. For example, our player_ship model comes with its own material and animation properties. We don't require any of these, so let's remove these properties before continuing to import our models fully into our Unity project.

To remove the Animation and Material properties from our player_ship model, we need to do the following:

  1. In the Project window, navigate to Assets/Resources/Model and select the player_ship file.
  2. In the Inspector window, select the Materials button.
  3. Untick the Import Materials box, then click the Apply button.
  4. Now click the Animation button next to the Materials button.
  5. Untick the Import Animation checkbox, followed by clicking the Apply button.
  6. Select the Rig button next to the Animation button.
  7. Select the current value in the AnimationType drop-down menu and select None, followed by the Apply button.
  8. That's all of our Material and Animation information removed from our player_ship model.
Throughout the book whenever we select a three-dimensional model, make sure to run through the same process, as we will not require imported extras such as the ones we just removed. This means I would like you now to repeat the process we have just gone through with the enemy_wave.fbx model.

Let's continue preparing our player_ship model for our game:

  1. Click and drag the player_ship from Assets/Resources/Model into the Hierarchy window.
  2. Select the player_ship in the Hierarchy window and set its name and Transform properties to the following values:
    • Game Object name: player_ship.
    • Tag: Player (easier to detect when colliding with enemies or other collisions).
    • Transform: All values set to zero apart from the Scale thatis set to 1 on all axes.

The following screenshot shows the player_ship values in the Inspector window:

  1. Click and drag the player_ship from the Hierarchy window into the Assets/Resources/Prefab/Player folder.

Notice the player_ship in the Hierarchy window has turned blue, which means it has become a prefab.

  1. Delete the player_ship from the Hierarchy window.

We are going to use a similar process to create our enemy_wave prefab, but we will also need to create its own name tag because there isn't an Enemy tag... yet.

Enemy prefab and custom tags

In this section, we are going to create an enemy_wave prefab along with a custom tag. The tag will be used to identify and categorize all related enemies under one tag.

To create an enemy_wave prefab and custom name tag, follow these instructions:

  1. In the Project window, drag the enemy_wave.fbx file from Assets/Resources/Model into the Hierarchy window.
  2. With the enemy_wave file selected in the Hierarchy window, update the following values in the Inspector window:
    • Game Object name: enemy_wave.
    • Transform: All values set to zero apart from the Scale, which is set to 1 on all axes:

Now let's create a new tag for the enemy_wave game object by doing the following:

  1. Choose the Untagged parameter in the Inspector window.
  2. From the Tag drop-down menu, select Add Tag....
  3. The Inspector window will now show the Tags & Layers window.
  4. Click the + to add a new tag, as circled in the following screenshot.
  5. Enter Enemy in the pop-up window, as shown in the following screenshot, then click the Save button:

  1. Back in the Hierarchy window, select the enemy_wave game object to bring back our Inspector window details.
  2. Click the Untagged parameter again.
  3. We can now see Enemy in our drop-down list, so select it.
  4. Drag the enemy_wave game object from the Hierarchy window into Assets/Resources/Prefab/Enemies.

We now move on to our third prefab creation – the player's bullet. But this time, we won't import a three-dimensional model – we are going to create one in the Unity editor, then create a prefab from it in the next section.

Creating the player's bullet prefab

Next, we are going to create the visuals for the player's bullet in the Unity editor. We will make a blue sphere and give it a surrounding light source. Let's start by creating a three-dimensional sphere game object.

In the Hierarchy window, right-click and from the drop-down list select 3D Object | Sphere.

With the newly created Sphere in the Hierarchy window still selected, make the following changes to the Inspector window:

  • Change the game object name from Sphere to player_bullet.
  • Change Tag from Untagged to Player. The tag name makes it easier to identify later on in the chapter.
  • The Transform parameters are all set to zero, apart from the Scale of the bullets, which should be slightly larger, with a Scale of 2 on all axes.

The following screenshot shows all three changes:

Next, we will give the player_bullet game object a new blue material.

Creating and applying a material to the player's bullet

In this section, we will be creating a simple unlit material that will not take up much of the device's performance thanks to the simplicity of the material. To create a basic material and apply it to the player_bullet object, do the following:

  1. In the Project window, navigate to the Assets/Resources/Material folder.
  2. Inside the Material folder, make a new folder (the same way we did in the Setting up our Unity project section) and name the folder Player. That way, any material related to the player can be stored inside.
  3. Double click the newly created Player folder and right-click in the Project window (in the open space in the right section of the window) again and, from the drop-down list, select Create | Material.

A new material file will be made. Rename it to player_bullet.

  1. Select the player_bullet material and, in the Inspector window, change the material from a Standard shader to Unlit | Color by following the three steps in the following screenshot:

The Inspector window will remove the majority of the properties and strip the material back to something simpler and easier to perform on any device.

  1. Still, in the Inspector window, click the Main Color swatch parameter and change it to a cyan color with the following values: R: 0, G: 190, B: 255, and A: 255.

We have created and calibrated our player's bullet, so now, we can apply the material to the player_bullet prefab by doing the following:

  1. Select the player_bullet prefab in the following location of the Project window: Assets/Resources/Prefab/Player.
  2. In the Inspector window, under the Mesh Renderer component, click the small round radio button to the right of the Element 0 parameter and type player_bullet in the drop-down list until you see the material, then select it.

The following screenshot shows the player_bullet prefab's Mesh Renderer component updated to our new unlit material:

In Chapter 4, Applying Art, Animation, and Particles, we will return to materials and art in general, which will be of note if you found this interesting. We will also play around with particle systems to create a fleet of stars rushing past the player's ship.

The last component we will add to our player's bullet is a surrounding light to give our bullet an energy glow.

Adding a light to the player's bullet

In this section, we will be adding a light component to the player's bullet to hide the impression that all that we are doing is firing spheres. It will also introduce us to Unity's point light, which acts as a glowing ball.

To add and customize a ball of light to the player's bullet, we need to do the following:

  1. In the Project window, navigate to the Assets/Resources/Prefab/Player folder and select the player_bullet prefab.
  2. In the Inspector at the bottom of the components listed, click the Add Component button and type Light from the drop-down list.

The player_bullet prefab will now have a Light component attached to it. We just need to change three property values to make the light suit the game object more.

  1. Change the following property values in the player_bullet file's Light component:
    • Range: 50
    • Color: R: 0, G: 190, B: 255, A: 255
    • Intensity: 20

The following screenshot shows the Light component after the values have been updated:

Before moving onto the next section, because we have taken an existing prefab and added a material and a light component, we need to click the Apply button to confirm the new changes.

  1. The following screenshot shows the Apply button in the top-right corner of the Inspector window for our player_bullet prefab:

  1. Finally, delete the player_bullet from the Hierarchy.

In the next section, we will continue to update our three prefabs by applying Unity's own physics system, the Rigidbody component, to help detect collisions.

Adding Rigidbody components and fixing game objects

Because this game involves collisions with game objects, we need to apply collision detection to the player, the player's bullets, and the enemy. Unity offers a range of different shapes to wrap around a game object that functions as an invisible shield; we can set our code to react to contact being made with the shield.

Before we add colliders to the player and enemy game objects (the Sphere game object automatically comes with a collider), we need to add a Unity component called a Rigidbody. If a game object is going to collide with another at least one other game object, it requires a Rigidbody component; Rigidbody components can affect a game object's mass, gravity, drag, constraints, and more. If you would like to know more about Rigidbody components, check out the documentation at https://docs.unity3d.com/Manual/class-Rigidbody.html.

Unity has other physics types apart from the collider. Joints also require the Rigidbody system, and Joints come in different forms, such as Hinge, Spring, and others.
These Joints will simulate at a fixed point; for example, the HingeJoint would be good at making a door swing back and forth around a door hinge's pivot point.
If you would like to know more about joints, check the documentation at https://docs.unity3d.com/Manual/Joints.html.

Let's add the RigidBody component to the player_ship and player_bullet prefabs:

  1. In the Project window, navigate to the Resources | Prefab | Player.
  2. Hold Ctrl (command on Mac) and click on the player_ship and player_bulletfiles.
  3. In the Inspector window, click the Add Component button.
  4. From the drop-down menu, type Rigidbody.
  5. Select Rigidbody (not Rigidbody 2D).
  6. The Rigidbody component has now been assigned to our two game objects.
  7. With the two game objects still selected in the Inspector window, under Rigidbody, make sure the Gravity checkbox isn't ticked. If it were, our game objects would begin to sink into the scene while the game is being played.

Now we can add colliders to our player_ship and enemy_wave game objects (our player_bullet already has a SphereCollider). We will be adding a SphereCollider to our game objects because it's the cheapest collider to use relative to performance costs:

  1. Click and drag the player_ship prefab from the Project window location of Assets/Resources/Prefab/Player into the Hierarchy window.
  2. With the player_ship still selected in the Hierarchy window, click the Add Component button in the Inspector window and type Sphere Collider into the drop-down menu.
  3. As soon as you see SphereCollider in the list, click it to add it to the player_ship game object.

You will notice a green wireframe around the player_ship in the Scene window (with the player_ship still selected in the Hierarchy window, hover your mouse in the Scene window and press F on the keyboard to zoom in on the ship if you can't see it). This is the player_ship collider that will be used to detect hits. It may be too big for the purpose of a hitbox, so let's reduce its size.

  1. With the player_ship prefab still selected in the Hierarchy window, alter the Radius of the SphereCollider component to 0.3 in the Inspector window, as shown in the following screenshot:

  1. Also, while we still have the player_ship prefab selected, check the Is Trigger box as shown in the previous screenshot. This will make it so the player_ship prefab looks for another collider without causing any form of potential physics collision.
  2. Click Apply in the top-right corner of the Inspector window to update the modifications we've made to our prefab with its Rigidbody and SphereCollider component.
  3. We can now select the player_ship prefab in the Hierarchy window and press Delete on our keyboard as we no longer need it in our Scene.

We now need to apply the same methodology to our player_bulletSphereCollider component:

  1. In the Project window, click and drag the player_bullet prefab from Assets/Prefab/Player into the Hierarchy window.
  2. Check the Is Trigger box and adjust the Radius in the SphereCollider component in the Inspector window.
  3. Click Apply in the top-right corner of the Inspector window to confirm the player_bullet changes and delete the player_bullet prefab from the Hierarchy window.

The last game object we need to update is the enemy_wave prefab. We have already covered the steps with the player_ship and player_bullet prefabs so it's not ideal to repeat the instructions in full; however, we need to do the following:

  1. As a short brief, I want you to drag and drop the enemy_wave prefab from its location at Assets/Resources/Prefab/Enemiesin the Project window.
  2. Add a SphereCollider component to the enemy_wave prefab in the Inspector window.
  3. Adjust the SphereCollider component so Is Trigger is checked and the Radius fits around the enemy_wave prefab with correct proportions, as we did with player_ship.
  4. The enemy_wave prefab doesn't require a Rigidbody component, as it will be colliding with relevant game objects that hold one themselves.
  5. Finally, Apply the prefab changes and remove the enemy_wave prefab from the Hierarchy window.

Use the following screenshot as a reference for the preceding mini brief and if you get stuck, use the previous steps that we discussed in this section:

Hopefully, that went well for you. If you get stuck at any point, refer to the Packt/Chapter2/Complete folder containing all the completed files to check them out and compare.

Before moving on, note that if a game object is pink, like our enemy_wave object in the previous screenshot, it simply means that it doesn't have a material attached. In other cases, it can also mean there is something wrong with the shader attached to the material.

We can fix this pink issue by doing the following:

  1. In the Project window, navigate to Assets/Resources/Prefab/Enemies/enemy_wave.
  2. Expand the enemy_wave game object to show the two game objects attached.
  3. Select the first game object titled enemy_wave_core.
  4. In the Inspector window, select the small remote circle next to the Element 0 parameter in the Mesh Renderer component (denoted by 1 in the following screenshot), then select Default-Material(denoted by 2) from the drop-down list, as shown in the following screenshot:

  1. Follow the same steps for its sibling game object, enemy_wave_ring.

The enemy_wave object will now have a default material applied.

If a game object requires a component such asRigidbody, we can place, above the class name, what is effectively a reminder to the script that the game object needs:

[RequireComponent(typeof(Rigidbody))]

If the game object doesn't have the component, the script will create one, and if we try to remove the Rigidbody component, we will receive a message in the Unity editor that it is a required component.

If you would like to know more about the RequireComponent attribute, check the documentation at https://docs.unity3d.com/ScriptReference/RequireComponent.html.

So, now we have our colliders and Rigidbody components applied to our game objects. This gives us the ability to create a reaction when colliders come into contact with each other.

Because we are starting to build up our project, let's quickly discuss saving our scenes, projects, and more.

Saving and publishing our work

It's easy to get stuck into our project, but as a brief reminder, save and publish your work with Unity Collaborate as often as possible. That way, if anything bad happens, you can always revert back.

Because we have created and saved our testLevel scene from the previous chapter, we can also add this scene to the Build Settings window. The reason for this is so that Unity is aware of what scenes we want toinclude in our project. It is also a requirement when it comes to packaging up our game as a build for deployment.

To add our scene to Build Settings, do the following:

  1. At the top of the Unity editor, click File | Build Settings. The Build Settings window will appear.
  2. Click the Add Open Scenes button to add the testLevel scene.
  3. The following screenshot shows the Add Open Scenes button circled, as well as an arrow pointing to the number index of our testLevel scene. When we add more scenes later, each scene will be numbered:

  1. Close the Build Settings window. We will come back to this when we have more scenes to add in the next chapter.
  2. It's a good habit to save the project by clicking File | Save Project.
Also, if you are a keen user of Collaborate, this is also a good time to publish (push) your work to the cloud. The following screenshot shows the top Collab button being clicked, followed byPublish now!to push the work into the cloud:

The end of each section, in this chapter and others, is a good time to save and publish your work. I won't remind you too often, but it's good to get into the habit.

Let's now continue with setting up our scene camera in the Unity editor.

Setting up our camera

For our side-scrolling shooter game Killer Wave, we need control over a camera to display the aspect ratio and visible depth of the scene, and to make sure we show the correct amount of our game's environment.

Let's get started and decide on the screen ratio of our game. We'll create our own resolution, which will be fairly common across most platforms.

To change the Game window's screen ratio to a custom aspect, do the following:

  1. Click the current aspect ratio under the Game window tab and select the + symbol.
  2. Enter the custom aspect ratio values shown in the following screenshot.
  3. Click OK once done, and select the 1080 resolution we have just made:

It is good to be aware of the need to make our game's artwork support (or to give it the scope to extend to) as many screen ratios as possible, especially if we ever wanted to make a game for portable devices such as tablets or mobile phones. This is because nearly every major brand of phone and tablet comes in different ratio sizes, and we don't want to start squashing and squeezing our content as it won't look right. It's also possible that our small mobile games will become successful and could laterbe ported to a console or PC. If that's the case, we need to make it so the game screen supports these ratios too. The main point to take from all of this is that we are targeting our game to cover all possible common screen ratios. The more platforms (consoles, portable devices, and so on) we can cover with flexible screen ratios, the easier it will be to extend our game out to those devices without requiring extra work. We explain more about screen size ratios inChapter 8,Adding Custom Fonts and UI, andChapter 9, Creating a 2D Shop Interface and In-Game HUD, where we discuss UI display settings. Additionally, in Chapter 13,Effects, Testing, Performance, and Alt Controls, we explain how to display our game screen on a raw image component.

Before we continue any further with our project, it's probably a good time to confirm our understanding of Unity's own UI layout. The following screenshot shows the Unity editor, where I have outlined and labeled the relevant windows:

Typically, the Unity editor window is made up of five main windows:

  • Scene: This is our two-/three-dimensional workspace.
  • Game: This window is what the end user will see. By default, the Game tab shares the same space as the Scene window.
  • Hierarchy: All game objects in our scene will be listed here.
  • Inspector: When an object is selected, information about it will be displayed here.
  • Project: This is our Unity project folder. Consider it a structure of files and folders that we can use in our game.
To drag each window around individually, left-click and drag the name of the tab and it will then snap into different locations.

My Game window is set to 1080 and because I don't have the luxury of a second screen, I've clicked its name tab (Game) and pulled it down in the bottom-right corner. The window is small, but as you can see at the top of the Game window, the scale is set to 1x, which means I have a full picture; nothing is hidden or cut out of view.

To check we have the main camera's Transform properties reset to its default settings, make sure its Position, Rotation, and Scale are all set to 0. We can also reset the Transform option as follows:

  1. With the main camera selected in the Hierarchy window, click the silver cog in the top-right corner of the Transform panel in the Inspector window, as shown in the following screenshot:

  1. When the dropdown appears, click Reset.

Continuing on with setting up our main camera, let's get rid of the landscape background in our Scene/Game window by changing its Background setting:

  1. Click the Main Camera in the Hierarchy window.
  2. In the Inspector window, we have the Camera component with a property called Clear Flags. Click the Skybox value.
  3. A dropdown will appear. Click Solid Color, as shown in the following screenshot:

  1. We will now be presented with a blue background, which is less distracting.
  2. If you don't like blue, you can change it to any color in the Background property. I'm going to make mine black by changing the Red, Green, Blue, and Alpha (RGBA) values to R: 0, G:0, B:0, and A:255, as shown in the following screenshot:

Great, now let's move on to coding these properties for our main camera.

Updating our camera properties via a script

We now have our main camera's behavior set in our Scene. Next, we need to code this into a script so that whenever a scene is loaded, Unity will read the script and understand how the main camera should be set up.

Observing our framework again, let's see where the camera script should be placed:

As you can see in the diagram, there is no reference to the camera, so should we make a script to support this? Arguably the only reason to make a script based on the camera would be if the camera had a complex purpose filled with multiple properties and functions. The camera in our game, however, is put in place when the game starts. Later on, on the third level, the camera will move from left to right with a simple component script, but it doesn't hold any other complexity. It would, therefore, be more ideal to use theGameManager, as it only takes up a small role. If the game became bigger and the camera took on more of a role, then this might justify the camera having a class of its own. Others might disagree based on personal preference, but this is the approach we'll take.

Let's make the GameManager script as follows:

  1. Create a script in the same way that we created a folder. Right-click the open space area in the Project window, and a dropdown will appear. Click Create | C# Script, as follows:

  1. The script appears with the title NewBehaviourScript. We don't want to call it that, so type (in camel case) GameManager.
What's camel casing?
Camel casing is a way to avoid spacing between words. This is fairly common with programming as spaces are typically not welcomed for various reasons. Each new word starts with a capital letter, so in this case, the M in GameManager is the hump of the camel. However, variables typically start with lowercase, as you will see shortly.

We now have our GameManager script. Notice how Unity is trying to be helpful by changing the icon to a silver cog because what we are doing is a recognized method with Unity:

As we did with placing our three-dimensional models into the Model folder, move the GameManager into the Script folder.

Good. Now, before we open our script to code in it, we need to attach it to a game object in our scene so that when the scene runs, the script attached to the game object also runs.

To create our GameManager game object, we need to do the following:

  1. Right-click in an open space in the Hierarchy window.
  2. From the drop-down menu, select Create Empty.
  3. Right-click the newly created game object and select Rename from the drop-down menu.
  4. Rename this game object to GameManager.
  5. Finally, with the GameManager game object still selected, click the Add Component button in the far-right Inspector window.
  6. Type GameManager from the drop-down menu until you see the GameManager script, and select it.
Whenever we make an empty game object, we must be sure that all of its Transform property values are reset to their default values unless we are specifically changing them.

To reset a game object's Transform value, make sure the game object we are resetting is selected. Click the metal cog in the top-right corner of the Inspector window, then select Reset.

Double-click the GameManager script to open it up in your IDE (Visual Studio or whatever IDE you use), then proceed as follows:

  1. Inside the GameManager script, we will be faced with the UnityEngine library being imported into our script to add extra functionality to Unity's own components:
          using UnityEngine;

public class GameManager : MonoBehaviour
{

Also in the preceding code, we have the name of our script along with MonoBehaviour being inherited yet again to add more functionality to our script. MonoBehaviour is also required if the game object that attaches to this script needs to be used in the Unity editor.

Let's start adding some of our own code into our GameManager script.

  1. Create an empty method, CameraSetup, and then run this method in the Start function:
    void Start()
{
CameraSetup();
}
void CameraSetup()
{

}
  1. Inside the CameraSetup method, add a reference to the camera and set the position and angle of the camera to zero apart from its z axis. We'll set Z to -300, which will move the camera back and ensure all game objects are in the shot:
GameObject gameCamera =
GameObject.FindGameObjectWithTag("MainCamera");

//Camera Transform
gameCamera.transform.position = new Vector3(0,0,-300);
gameCamera.transform.eulerAngles = new Vector3(0,0,0);
  1. Next, we will change the properties of the camera within our CameraSetup method:
 //Camera Properties
gameCamera.GetComponent<Camera>().clearFlags =
CameraClearFlags.SolidColor;
gameCamera.GetComponent<Camera>().backgroundColor =
new Color32(0,0,0,255);
}

This does the following:

  • Removes the sky background and replaces it with a solid color
  • Changes the solid color from the default blue to black
  1. Finally, save the script.

Now you should have something like this:

If you would like to change other settings relating to the camera, you can find out about them at https://docs.unity3d.com/ScriptReference/Camera.html.

Press the Play button in the upper middle of the editor window, or by using the shortcut Ctrl + P (command + P on the Mac). The following screenshot shows where the Play button is located:

With the scene in play mode, we can now check out the Main Camera game object's properties by doing the following:

  1. In the Hierarchy window, select Main Camera.

Observe the Inspector window in the next screenshot to see the following changes our script has made.

  1. In the Transform component of the Inspector window, we can see the Position and Rotation properties are set to the same values set in our script (denoted by 1 in the following screenshot).
  2. In the Camera component of the Inspector window, we can see the Clear Flags and Background values are also set to the same values set in our script (denoted by 2i and 2ii).

The following screenshot shows the Main Camera component properties being updated in Play mode:

Now, hopefully, our properties should be the same as what we have scripted (with no errors). If not, you will likely have an error message in the Console window. If there is an error, it will likely tell you what line the error is on. You can also double-click the error, and it will take you to the line the error is on.

To double-check everything has worked, change the Position and Rotation of the camera in the editor and then press the Play button. The properties for the camera should now be set to our script's Position and Rotation properties.

At this point, while the editor is still playing, we could also make a prefab of the camera:

  1. Click and drag the Camera from the Hierarchy window down into the Project window, and we will generate a blue cube with the camera's name or an empty icon. Depending on the scale of our icons, the size of the icon can be altered by moving the slider shown in the following screenshot:

  1. Move this camera prefab into the Prefab folder.

You might be thinking, why didn't we just make a prefab of the camera in the first place instead of fiddling with its property settings in code? However, two key things are important here: firstly, we are studying for an exam that is likely to cover such properties; and secondly, you now know how to change these settings dynamically through code.

Another benefit to scripting Unity's components is that we can sometimes be offered more functionality than what is displayed in the Editor. For example, the Camera component has a layerCullDistances property that is only accessible via scripting. This can offer functionality such as skipping the rendering of smaller game objects in the far distance to increase a game's performance.

To read up more on layerCullDistances, check the documentation at https://docs.unity3d.com/ScriptReference/Camera-layerCullDistances.html.

This brings this section to a close. So far, we have covered the following:

  • Setting up a ratio for our game camera
  • Setting up our Unity editor with individual windows
  • Changing the properties of our Camera component in the Unity editor
  • Repeating the changes we made to our camera in the GameManager script
  • Adding our GameManager script to our scene as a game object

As a programmer, the importance of being able to understand and change the settings in the Unity editor (but to also be able to do the same in code) can be expanded to other components that are in the editor. This is what we will do next, with a focus on directional light.

Setting up our light

As a default setup, each scene comes with a directional light, and currently, this is all we need to get going; ideally, we want the scene to be well lit.

With the directional light already present in the scene as the default light, select it in the Hierarchy window. In the Inspector window, set the Directional Light's TransformRotation values to the following:

X: 50, Y:-30, Z:0

When we put our player ship into the scene, this will light it up well, as shown in the following screenshot:

Unity provides three different types of real-time lights. As well as the directional light we mentioned, it also provides a point light, which is like a 360° glow that we will cover in Chapter 4, Applying Art, Animation, and Particles. The third type of light is a spotlight or as Unity refers to it, a spot. The spot can also have masks applied so it can project images known as cookies.

For more information about the three types of lights, check out https://docs.unity3d.com/Manual/Lighting.html.

We can now make sure these settings stay in place by adding them to the GameManager script. We can also alter the light's color.

Updating our light properties via a script

In the GameManager, we will set the TransformRotation values and change the color tint from a light yellow to a cold blue:

  1. Open the GameManager script and enter the following method:
void LightSetup()
{
GameObject dirLight = GameObject.Find("Directional Light");
dirLight.transform.eulerAngles = new Vector3(50,-30,0);
dirLight.GetComponent<Light>().color =
new Color32(152,204,255,255);
}
  1. Add LightSetup(); in the scope of the Start function.
  2. Save the script.

The LightSetup method does three things:

  • It grabs the light from the scene and stores it as a reference.
  • It sets the rotation of the light with EulerAngles.
  • Finally, it changes the light's color.
eulerAngles allows us to give Vector3 coordinates instead of Quaternion values. eulerAngles makes it so rotations are less complicated to work with. More information about eulerAngles can be found at https://docs.unity3d.com/ScriptReference/Transform-eulerAngles.html.

That's all we need to do with our light. As with the camera, we can access the light and change its properties via a script.

We have become familiar with our light by changing its settings in the Unity editor and the GameManager script. Next, we will set up our interface for the majority of our game objects.

 

Introducing our interface – IActorTemplate

The IActorTemplate interface is what we are using to prompt damage control, death, and scriptable object assets. The reason for using an interface such as this is that it ties general uses together between classes that inherit it.

A total of six classes will be using the IActorTemplate interface, which is as follows:

  • Player
  • PlayerBullet
  • PlayerSpawner
  • Enemy
  • EnemyBullet
  • EnemySpawner

The following diagram shows theIActorTemplateinterface with a partial overview of our game framework:

Let's create our interface and explain its content along the way:

  1. Create a script in theAssets/Resources/Scripts folder with the filename IActorTemplate.

  2. Open the script and enter the following code:

public interface IActorTemplate
{
int SendDamage();
void TakeDamage(int incomingDamage);
void Die();
void ActorStats(SOActorModel actorModel);
}
  1. Make sure to save the script.

The code we just entered looks like we have declared a class, but it acts fundamentally differently. Instead of using the class keyword, we enter interface followed by the name of the interface,IActorTemplate. It's not a requirement to start any interface name with an I but it makes the script easily identifiable.

Within the interface, we make a list of methods that act like contracts to whichever class inherits the interface. For example, thePlayerscript that we'll create later on in the chapter inherits theIActorTemplate interface. The Player script must declare the function names from IActorTemplate or the Player script will throw an error.

Inside the scope of the interface, we declare methods without accessors (it doesn't require private or public at the beginning of each method). Methods also don't require any content in them (that is, they are empty bodies).

For more information about interfaces, check out https://learn.unity.com/tutorial/interfaces.

The last method in our interface is ActorStats, which takes a type ofSOActorModel.SOActorModelis a scriptable object that we are going to explain and create in the next section.

 

Introducing our ScriptableObject – SOActorModel

In this section, we are going to cover scriptable objects and their benefits. Similar to our interface, scriptable objects cover the same six classes. The reason for this is because our interface uses the SOActorModel and therefore creates an attachment with the other variables.

It is also good to remind ourselves of the Game Design Brief and how it is incorporated into the overview of the creation of our game.

Our game has three series of game objects that will hold similar properties: EnemyWave, EnemyFlee, and Player. These properties will include health, speed, score value, and more. The difference between each of these as described in the game design brief is the way they act and also how they are instantiated in our game.

Player will be instantiated at every level, EnemyWave will be spawned from EnemySpawner, and EnemyFlee will be placed in particular areas of the third level.

All of the mentioned game objects mentioned will relate to the SOActorModel object.

The following diagram is also a partial view of our game framework showing the scriptable object and the six classes that inherit it:

Similar to what was mentioned with the interface script is that the name of the scriptable object name starts withSO, which isn't a standard way of naming the script, but it's easier to identify as aScriptableObject.

The purpose of this scriptable object is to hold general values for each of the game objects it's being given to. For example, all game objects have a name, so within ourSOActorModelis a string namedactorName. ThisactorNamewill be used to name the type of enemy, spawner, or bullet it is.

Let's create a scriptable object:

  1. In the Project window in the Unity editor, create a script in the Assets/Resources/Scripts folder with the filename SOActorModel.
  2. Open the script and enter the following code:
using UnityEngine; 
[CreateAssetMenu(fileName = "Create Actor", menuName =
"Create Actor")]
public class SOActorModel : ScriptableObject

{
public string actorName;
public AttackType attackType;

public enum AttackType
{
wave, player, flee, bullet
}
public string description;
public int health;
public int speed;
public int hitPower;
public GameObject actor;
public GameObject actorsBullets;
}
  1. Save the script.

Inside the SOActorModel we will be naming most, if not, all of these variables in thePlayerscript. Similar to how an interface signs a contract with a class, theSOActorModel does the same because it's being inherited, but isn't as strict as an interface by throwing an error if the content from the scriptable object isn't applied.

The following is an overview of theSOActorModel code we just entered.

We named our scriptable object SOActorModelas a generic term to try and cover as many game objects as will likely use the scriptable object. This way of working also supports the SOLID principles we covered in the first chapter by encouraging us to try and keep our code concise and efficient.

The main categories we'll cover for this script are as follows:

  • Importing libraries:As you can see, the only library we have imported in the SOActorModel script is theusing UnityEngine; no other libraries are required.
  • Creating an asset:The CreateAssetMenu attribute creates an extra selection from the drop-down list in the Project window in the Unity editor when we right-click and select Create, as shown in the following screenshot:

  • Inheritance: We aren't inheritingMonoBehaviour, but instead inheritingScriptableObject as it's a requirement when it comes to creating an asset.
  • Variables: Finally, these are the variables that will be sent to our selected classes.

In the following sections, we are going to create assets from the scriptable object script to give our scripts different values.

Creating a PlayerSpawner ScriptableObject asset

With ourSOActorModelScriptableObjectmade, we can now create an asset that will act as a template that can be used not just byprogrammers, but also by designers who want to tweak game properties/settings without needing to know how to code.

To create an Actor Model asset, do the following:

  1. Back in the Unity editor, in the Project window, right-click and choose Create | Create Actor.
  2. Rename the newly created asset file in the Project window Player_Default and store the file in the Assets/Resources/Scripts/ScriptableObject folder.
  3. Click on the new asset and, in the Inspector window, you'll see the content of the asset.

The following screenshot shows the Actor Model asset's fields where I have entered in my own values:

Let's break down each of the values that have been added to our newly created asset:

  • Actor Name: The name of the actor (in our case, this is Player).
  • Attack Type: Choose to pick which category this game object belongs to.
  • Description: Designer/internal notes that don't affect the game but can be helpful.
  • Health: How many times can the player get hit before dying.
  • Speed: Movement speed of the player.
  • Hit Power: Determines how much damage the player will cause if it collides with the enemy.
  • Actor: Place the player_ship prefab here (Assets/Resources/Prefab/Player/player_ship).
  • Actors Bullets: Place the player_bullet prefab here (Assets/Resources/Prefab/Player/player_bullet).

We will add this asset to our PlayerSpawner script once it's built later on in the chapter. Let's move on to the next scriptable object asset.

Creating an EnemySpawner ScriptableObject asset

In this section, we are going to make our enemy asset to attach to EnemySpawner for later on in the chapter. For the sake of keeping our work fresh and together, let's continue with that before moving onto the EnemySpawner script.

To make an enemy asset, follow these instructions:

  1. Back in the editor, in the Project window, right-click and choose Create | Create Actor.
  2. Rename the new file to refer to what it's being attached to (BasicWave Enemy) and store the file in the Assets/Resources/Scripts/ScriptableObject location.
  3. Click on the new script and our Inspector window will show the content of our script.

The following screenshot shows what the BasicWave Enemy asset is going to look like once we've finished:

Lets briefly go through each of the values for our enemy:

  • Actor Name: enemy_wave
  • Attack Type: Here, this is Wave. This explains what type of enemy it is and how it attacks the player.
  • Description: Here, this reads Typically in groups. As mentioned before, it's more of a guideline than a rule to comment anything.
  • Health: 1, which means it takes 1 hit to die.
  • Speed: -30– because our enemy is moving from right to left, so we give it a minus figure.
  • Hit Power: 1– which means that if this enemy collides with the player, it will cause 1 hit point of damage.
  • Actor: Place the enemy_wave prefab here (Assets/Resources/Prefab/Enemies/enemy_wave).
  • Actor Bullets: This enemy doesn't fire bullets.

Hopefully, you can see how useful scriptable objects are. Imagine continuing to develop this game with 50 enemies, where all we need to do is create an asset and customize it.

We are going to move on to the final scriptable object asset for this chapter in the next section.

Creating a PlayerBullet ScriptableObject Asset

In this section, we are going to create an asset for the player's bullet for when they fire. Like the last two sections, create an asset, name it PlayerBullet, and store it in the same folder as the other assets.

The following screenshot shows the final results for the PlayerBullet asset:

Let's briefly go through each variable's values:

  • Actor Name: player_bullet.
  • Attack Type: Bullet.
  • Description: It is optional to enter any details about the asset here.
  • Health: Our bullet has a health value of 1.
  • Speed: 700.
  • Hit Power: 1 sends a hit point of 1.
  • Actor: Place the player_bullet prefab here (Assets/Resources/Prefab/Player/player_bullet).
  • ActorBullets: None (Game Object).

In a later chapter, when we build a shop for our game, we will be able to buy power-ups for our player's ship. One of the power-ups will be similar to the one that we just made but the Actor Name will be different and the Hit Power will have a higher number.

Now we can move on to the next section and create the player's scripts and attach these assets to them.

 

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!

 

Planning and creating our enemy

We have a player that moves, shoots, and takes damage; we can now start looking into creating an enemy that shares these attributes.

To remind ourselves of the genre we are making, our game carries the same traits as classic arcade shooters such as Konami's Gradius, Capcom's UN Squadron, and Irem's R-Type (https://raw.githubusercontent.com/retrophil/Unity-Certified-Programmer-Exam-Guide/master/Reference/shootEmUps.png). Typically, with these types of games, the player is swarmed by enemies coming from the right of the screen and exiting to the left.

In this section, we will be repeating similar aspects of the PlayerSpawner and Player scripts. The EnemySpawner script needs to be tweaked so that it will instantiate a given number of enemy ships at a certain rate.

The Enemy game objects will be moving on their own, so there needs to be some extra code applied to their behavior. Before we go into creating our first enemy script, let's look at a part of our game framework and note the layout is basically the same as the player's side of the game framework:

Before we jump into the EnemySpawner script, let's do the same housekeeping we did for our player game objects, namely creating an empty game object and storing all game objects relating to it in that one game object. The reason we did this is to remove the clutter in the Hierarchy window, so let's do the same for our enemies:

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

Let's move on to our enemy scripts.

 

Setting up our EnemySpawner and Enemy scripts

In this section, we are going to make a start on our EnemySpawner script and game object. The purpose of the EnemySpawner script is to have a game object spawn an enemy game object a series of times at a set rate. As soon as our testLevel scene begins, our enemy spawners will start releasing enemies. It will then be up to the enemies to move to the left of the screen. This is fairly simple, and as mentioned briefly in the previous section, the EnemySpawner uses the same interface and scriptable object as the PlayerSpawner to instantiate enemies. Let's start by creating our EnemySpawner script:

  1. In the Project window in the Unity editor, create a script in the Assets/Resources/Scripts folder with the filename EnemySpawner.
  2. Open the script and enter the following code:
          using System.Collections;
          
using UnityEngine;

As usual, we are using the defaultUnityEnginelibrary to gain access to more functionality.

We are also going to be using another library, calledSystem.Collections. This is required when we come to usingCoroutines, which will be explained later in this section.

  1. Next, we will check/enter the class name and its inheritance:
          public class EnemySpawner : MonoBehaviour
{

Make sure the class is named EnemySpawner and that it also inherits MonoBehaviour by default.

  1. Following on, add four global variables to the EnemySpawner script:
 [SerializeField]
SOActorModel actorModel;
[SerializeField]
float spawnRate;
[SerializeField]
[Range(0,10)]
 int quantity;
GameObject enemies;

All variables entered in the previous code have an accessibility level of private, and all of the variables apart from the enemies variable have a SerializeField and a Range attribute of between 0 to 10 applied. The reason for this is so that we or other designers can easily change the spawn rate and quantity of enemies from our EnemySpawner in the Inspectorwindow, as shown in the following screenshot:

  1. Now, let's enter Unity's Awake function along with some content:
void Awake()
{
enemies = GameObject.Find("_Enemies");
StartCoroutine(FireEnemy(quantity, spawnRate));
}

Inside the Awake function, we make an instance from the empty _Enemies game object divider and store it in the enemies variable.

The second line of code inside our Awake function is a StartCoroutine.

StartCoroutine() and IEnumerator go hand in hand with each other. They act similarly to a method, taking parameters and running the code inside it. The main difference with coroutines is that they can be delayed by frame updates or time. You may consider them a more advanced version of Unity's own Invokefunction.

To find out more about coroutines and how to implement them in IEnumerator instances, check Unity's documentation at https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html.

This will be used to run our method of creating an enemy, but as you may also notice, it takes two parameters. The first is the quantity of enemies it holds and the second is the spawnRate, which delays each spawned enemy.

Next, in our EnemySpawner script, we have the FireEnemy, which will be used to run a cycle of creating and positioning each enemy, before waiting to repeat the process again.

  1. Next, below and outside of the Awake function, we can add our IEnumerator:
IEnumerator FireEnemy(int qty, float spwnRte)
{
for (int i = 0; i < qty; i++)
{
GameObject enemyUnit = CreateEnemy();
enemyUnit.gameObject.transform.SetParent(this.transform);
enemyUnit.transform.position = transform.position;
yield return new WaitForSeconds(spwnRte);
}
yield return null;
}

Inside the FireEnemyIEnumerator, we start a for loop that will iterate over its qty value.

Within the for loop, the following is added:

    • A method that we haven't covered yet, called CreateEnemy. The result of CreateEnemy will be returned into a game object instance called enemyUnit.
    • The enemyUnit is the enemy flying out of the EnemySpawner game object.
    • Our EnemySpawner position is issued to our enemyUnit.
    • We then wait however many seconds the spwnRte value is set to.
    • Finally, the process is repeated up until the for loop has reached its total.
  1. Finally, below and outside of the FireEnemyIEnumerator, add the following method:
GameObject CreateEnemy()
{
GameObject enemy = GameObject.Instantiate(actorModel.actor)
as GameObject;
enemy.GetComponent<IActorTemplate>().ActorStats(actorModel);
enemy.name = actorModel.actorName.ToString();
return enemy;
}
}

As we mentioned, there is a method calledCreateEnemy. Apart from the obvious, this method will do the following:

  • Instantiate the enemy game object from its ScriptableObject asset.
  • Apply values to our enemy from its ScriptableObject asset.
  • Name the enemy game object from its ScriptableObject asset.

Don't forget to save the script.

We can now move on to the next section where we will create and prepare the EnemySpawner with its game object.

Adding our script to the EnemySpawner game object

Finally, we need to attach our EnemySpawner script to an empty game object so that we can use it in our testLevel scene. To set up the EnemySpawner game object, do the following:

  1. Create an empty game object and name it EnemySpawner .
  2. Like what we did with the _Player and PlayerSpawner, we need to move the EnemySpawner game object inside the _Enemies game object in the Hierarchy Window.
  3. After moving the EnemySpawner game object into the _Enemies game object, we now need to update the EnemySpawner game object Transform property values in the Inspector window:

  1. Still, in theInspectorwindow, clickAdd Componentand typeEnemySpawneruntil you see it in the list, then click it.

Also, for a visual aid in the Scene window, it is also recommended to add an Inspector icon to the EnemySpawner game object as we did with our PlayerSpawner game object in the Creating the PlayerSpawner game object section.

The following screenshot shows the icon I gave to my EnemySpawner:

We can now add an enemy to our Enemy Spawner along with Spawn Rate and Quantity values specified in the Inspector window. The following screenshot shows an example of a filled-in EnemySpawner game object with its script in the Inspector window:

We can now move on to creating our enemy script in the next section.

Setting up our enemy script

Like our player ship being created from the PlayerSpawner, our first enemy will be created from its EnemySpawner. The enemy script will hold similar variables and functions but it will also have its own movement, similar to the PlayerBullet moving along its x axis.

Let's make a start and create our enemy script:

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

Like the majority of our classes, we require the UnityEngine library for functionality.

  1. Check and enter the class name and its inheritance:
          public class EnemyWave : MonoBehaviour, IActorTemplate
{

We have apublic class named EnemyWave that inherits MonoBehaviour by default but also adds ourIActorTemplate interface.

  1. Within the EnemyWave class, enter the following global variables:
 int health;
int travelSpeed;
int fireSpeed;
int hitPower;

//wave enemy
[SerializeField]
float verticalSpeed = 2;
[SerializeField]
float verticalAmplitude = 1;
Vector3 sineVer;
float time;

The global variables for the EnemyWave class are the top four variables updated with values from its ScriptableObject asset. The other variables are specific to the enemy, and we have given two of these variables SerializeField attributes for debugging purposes in the Inspector window.

  1. Add Unity's Update function along with its content:
void Update ()
{
Attack();
}

After the global variables, we add an Update function containing an Attack method.

  1. Add our ScriptableObject method ActorStats and its content:
public void ActorStats(SOActorModel actorModel)
{
health = actorModel.health;
travelSpeed = actorModel.speed;
hitPower = actorModel.hitPower;
}

We have our ActorStats method that takes in a ScriptableObjectSOActorModel. This ScriptableObject then applies the variable values it holds and applies them to the EnemyWave script's variables.

  1. Still in the EnemyWave script, add the Die method along with its content:
public void Die()
{
Destroy(this.gameObject);
}

Another familiar method if you have been following along is the Die method, which is called when the enemy has been destroyed by the player.

  1. Add Unity's OnTriggerEnter function to the EnemyWave script:
void OnTriggerEnter(Collider other)
{
// if the player or their bullet hits you.
if (other.tag == "Player")
{
if (health >= 1)
{
health -= other.GetComponent<IActorTemplate>
().SendDamage();
}
if (health <= 0)
{
Die();
}
}
}

Unity's own OnTriggerEnterfunction will check to see whether they have collided with the player and if so, will send damage and the enemy will destroy themselves with the Die method.

  1. Continue on and enter the TakeDamage and SendDamage methods:
public void TakeDamage(int incomingDamage)
{
health -= incomingDamage;
}
public int SendDamage()
{
return hitPower;
}

Another common set of methods from the IActorTemplate interface is to send and receive damage from the EnemyWave script.

Next is the Attack method, which controls the movement/attack of the enemy. This method is called in the Update function on every frame.

With this attack, we will have it so the enemy moves from right to left in a wavy animation (like a snake) instead of just going straight right to left. The following image shows our enemies moving from right to left in a wavy line:

  1. Enter the following Attack method code into the EnemyWave script:
public void Attack()
{
time += Time.deltaTime;
sineVer.y = Mathf.Sin(time * verticalSpeed) * verticalAmplitude;
transform.position = new Vector3(transform.position.x
+ travelSpeed * Time.deltaTime,
transform.position.y + sineVer.y,
transform.position.z);
}

The Attack method starts withTime.deltaTimebeing collected into afloatvariable labeledtime.

We then use a premade function from Unity that returns sine (https://docs.unity3d.com/ScriptReference/Mathf.Sin.html) using ourtime variable, multiplied by a set speed from the verticalSpeedvariable, followed by the result being multiplied byverticalAmplitude.

The end result is stored in the Vector3y axis. What this basically does is make our enemy ship move up and down. The verticalSpeed parameter sets its speed and verticalAmplitude alters how far it goes up and down.

Then we do a similar task as we did with the PlayerBullet to make the enemy ship move along the x axis, and we also add a sine calculation to its Y position for it to move up and down.

Make sure to save the script before we wind down this chapter.

Before we summarize, click Play in the Editor and hopefully, if all is well, you will have a player ship that you will be able to fly around within the boundaries of the Game window's aspect ratio; enemies will come floating into the screen and move from right to left; you will be able to destroy these enemies with your bullets. These enemies will also be able to destroy you if they make contact with you. Finally, our Hierarchy window is all neat and well structured both before and after playing our game. The following screenshot shows what I have just explained:

You have done so much already! The good news is you've just conquered one of the biggest chapters in the book – quite sneaky of me, I know. But we already have the backbone of our game and most importantly, we've covered a good chunk of the Unity Programmer exam.

Understandably you may have come across some possible issues on the way, and you may feel stuck. Don't worry if this is the case – check the Complete folder for this chapter to load up the Unity project and compare the code in that folder with your own to double-check. Make sure you have the right game objects in your scene, check that the right game objects are tagged, check the radius size of your Sphere colliders, and if you have any errors or warnings appear in the Console window double-click them and it will take you to the code that's causing an issue.

Let's wrap up this chapter and talk about our game so far.

 

Summary

We have reached the end of this chapter, and we have conquered the majority of our game framework, as we can see in the following diagram:

We have created a game framework that would need only a few changes if we added 1 or 1,000 more enemies to our game. Some of the benefits of this use of reusable code and ScriptableObject is that it will benefit non-programmers, save time, and prevent collaborators from being bogged down in the code.

We have also made it so that if and when we want to add more EnemySpawner points, we can drag and drop more prefabs into our scene and update its ScriptableObject to change the enemy without coding in exact Vector3 locations.

We've covered other common Unity features including instantiating game objects such as enemies and player bullets.

In the next chapter, we will be covering the following scripts:

  • ScoreManager: When an enemy is destroyed, the player will receive a score.
  • ScenesManager: If the player dies, one life will be deducted; if the player loses all of their lives, the level will reset.
  • Sounds: Our ships and bullets will also have added sounds.

Finally, we will be updating the overall structure of our code.

About the Author

  • Philip Walker

    Philip Walker originally started as a 3D games artist, but then decided he wanted to combine his current skills with coding so that he could see through the majority of his games' and apps' development himself. Philip has worked in five different industries as an artist and/or a Unity developer using various types of technology and techniques.

    Browse publications by this author

Latest Reviews

(1 reviews total)
Just as described - extremely good value for the .org !!

Recommended For You

Unity Certified Programmer: Exam Guide
Unlock this book and the full library for $5 a month*
Start now