Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Unity Certified Programmer Exam Guide - Second Edition
Unity Certified Programmer Exam Guide - Second Edition

Unity Certified Programmer Exam Guide: Pass the Unity certification exam with the help of expert tips and techniques, Second Edition

By Philip Walker
£62.99 £43.99
Book May 2022 766 pages 2nd Edition
eBook
£62.99 £43.99
Print
£77.99
Subscription
£13.99 Monthly
eBook
£62.99 £43.99
Print
£77.99
Subscription
£13.99 Monthly

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : May 2, 2022
Length 766 pages
Edition : 2nd Edition
Language : English
ISBN-13 : 9781803246215
Vendor :
Unity Technologies
Category :
Concepts :
Table of content icon View table of contents Preview book icon Preview Book

Unity Certified Programmer Exam Guide - Second Edition

Chapter 2: 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 an 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 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, preventing 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 input 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.
  • 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-Second-Edition/tree/main/Chapter_02.

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

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/3yfWyt5.

Setting up our Unity project

Things can get messy quickly in a project if we don't manage our files correctly by placing them in 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:

  • Model contains 3D models (the player ship, enemies, bullets, and so on).
  • Prefab holds instances of game objects (these are created within Unity).
  • Scene stores our first-level scene as well as other levels.
  • Script contains all of our code.
  • Material stores our game object materials.
  • Resources stores assets and objects to load into our game.
  • ScriptableObject are data containers that are capable of storing large amounts of data.

    Tip

    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:

Figure 2.1 – Creating a folder in the Unity editor

Figure 2.1 – Creating a folder in the Unity Editor

Next, we will create subfolders; we need to do the following:

  1. Within our Prefab folder, create another two folders, Enemies and Player:
Figure 2.2 – Folders created in the Unity editor

Figure 2.2 – Folders created in the Unity Editor

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.

Further Information

It's worth mentioning the StreamingAssets folder at this point. Even though we aren't using it in this project, it relates well to how similar (and how different) it is to the Resources folder.

The Resources folder imports assets and converts them into internal formats that will be compatible with the target platform. So, for example, a PNG file will get converted and compressed when the project is being built.

The StreamingAssets folder will hold the PNG file, not convert or compress it. For more information on Streaming Assets, check out the following link: https://docs.unity3d.com/Manual/StreamingAssets.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:

Figure 2.3 – Importing assets into your project

Figure 2.3 – Importing assets into your project

Make sure all assets are ticked and click the Import button at 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 are 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 by making our player_ship.fbx file into a prefab instance by doing the following.

Sometimes, when importing any three-dimensional file, it 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/Model and select the player_ship file.
  2. In the Inspector window, select the Materials button.
  3. Make sure the Material Creation Mode is set to None from the drop-down list, and 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 Animation Type drop-down menu and select None, followed by the Apply button.
  8. That's all of the Material and Animation information removed from our player_ship model.

    Important Information

    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 player_ship from Assets/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 0 apart from Scale, which is set to 1 on all axes

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

Figure 2.4 – The player_ship values in the Inspector window

Figure 2.4 – The player_ship values in the Inspector window

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

When creating a prefab, sometimes you might be asked if this is an Original or Variant:

Figure 2.5 – The Create Prefab Dialog

A variant prefab will be a copy of an original prefab but will also carry any changes made from the prefab it originates from. So, for example, if the original prefab was a car with 4 wheels, the variant will have the same. If the original prefab changes it's numbers from 4 to 3, the variant will copy the original prefab.

Note that 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.

An 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/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 0 apart from Scale, which is set to 1 on all axes:

Figure 2.6 – The enemy_wave values in the Inspector window

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, and then click the Save button:

Figure 2.7 – Add a tag to the Tags & Layers list

  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/Prefab/Enemies.
  5. Delete enemy_wave from the Hierarchy window

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, and 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:

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

The following screenshot shows all three changes:

Figure 2.8 – The player_bullet values in the Inspector window

Figure 2.8 – The player_bullet values in the Inspector window

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/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.
  4. A new material file will be made. Rename it player_bullet.
  5. 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:
Figure 2.9 – Creating an Unlit Color Material

Figure 2.9 – Creating an Unlit Color Material

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/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, and then select it.

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

Figure 2.10 – player_bullet now has a player_bulletMat material

Figure 2.10 – player_bullet now has a player_bulletMat 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/Prefab/Player folder, select the player_bullet prefab, and drag it into the Hierarchy window (if it isn't in the Hierarchy window already).
  2. In the Inspector at the bottom of the components listed, click the Add Component button and select 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, and A: 255
    • Intensity: 20

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

Figure 2.11 – The Light component values in the Inspector window

Figure 2.11 – The Light component values in the Inspector window

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 Override button to confirm the new changes.

The following screenshot shows the Overrides button at the top-right corner of the Inspector window for our player_bullet prefab:

Figure 2.12 – Updating the player_bullet prefab

 

Figure 2.12 – Updating the player_bullet prefab

  1. Finally, click on Apply All to accept our updated light settings and then 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 Rigidbody. If a game object is going to collide with at least one other game object, it requires a Rigidbody component, which 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.

Rigidbody Joints

Unity has other physics types apart from the collider. Joints also require the Rigidbody system, and they come in different forms, such as Hinge, Spring, and others.

These Joints will simulate at a fixed point; for example, the Hinge Joint 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 in one go:

  1. In the Project window, navigate to Prefab | Player.
  2. Hold Ctrl (command on a Mac) and click on the player_ship and player_bullet files.
  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 that the Gravity checkbox isn't ticked. If it was, 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/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 in 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:
Figure 2.13 – The triggered sphere collider added to the player_ship prefab

Figure 2.13 – The triggered sphere collider added to the player_ship prefab

  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 the player_ship prefab look for another collider without causing any form of potential physics collision.
  2. Click Override at the top-right corner followed by Apply All in the Inspector window to update the modifications we've made to our prefab with its Rigidbody and SphereCollider components.
  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 the SphereCollider component of our player_bullet:

  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 Overrides and then Apply All at 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. Briefly, I want you to drag and drop the enemy_wave prefab from its location at Assets/Prefab/Enemies in the Project window into the Hierarchy window..
  2. Add a SphereCollider component to the enemy_wave prefab in the Inspector window.
  3. Adjust the SphereCollider component so that Is Trigger is checked and the Radius value fits around the enemy_wave prefab with the 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, Override, Apply All 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:

Figure 2.14 – The trigger collider added and scaled to the enemy_wave prefab

Figure 2.14 – The trigger collider added and scaled to the enemy_wave prefab

Hopefully, that went well for you. If you get stuck at any point, refer to the https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/blob/main/Chapter_02/ProjectFIles/Chapter-02-Complete.unitypackage folder containing all the completed files to check them out and compare.

Before moving on, note that if a game object is pink, such as 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/Prefab/Enemies.
  2. Drag and drop enemy_wave into the Hierarchy window. Expand the drop down next to enemy_wave in the Hierarchy window..
  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), and then select Default-Material (denoted by 2) from the drop-down list, as shown in the following screenshot:
Figure 2.15 – Adding a Default Material to the enemy_wave_core gameobject

Figure 2.15 – Adding a Default Material to the enemy_wave_core gameobject

  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 any changes were made to the prefab be sure to click Override, Apply All.

Attributes

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

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

This code isn't a requirement as such, more of a good practice with components in general.

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 so on.

Saving and publishing our work

It's easy to get stuck into our project, but as a brief reminder, save your work 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 to include 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:
Figure 2.16 – Adding the testLevel scene to the Scenes In Build list

Figure 2.16 – Adding the testLevel scene to the Scenes In Build list

  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.

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

Unity Editor Layout

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:
Figure 2.18 – Setting a custom Game window resolution

Figure 2.17 – Setting a custom Game window resolution

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 later be ported to a console or PC. If that's the case, we need to make the game screen support 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 in Chapter 8, Adding Custom Fonts and UI, and Chapter 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 will 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:

Figure 2.19 – The Unity editor window layout

Figure 2.18 – The Unity Editor window layout

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.

    Tip

    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 that we have the main camera's Transform properties reset to their default settings, make sure that 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 three dots at the top-right corner of the Transform panel in the Inspector window, as shown in the following screenshot:
Figure 2.20 – The Transform settings cog location

Figure 2.19 – The Transform settings cog location

  1. When the dropdown appears, click Reset.

Continuing 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:
Figure 2.21 – Changing Background to a Solid Color

Figure 2.20 – Changing Background to a Solid Color

  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:
Figure 2.22 – Setting Background color values

Figure 2.21 – Setting Background color values

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:

Figure 2.23 – Killer Wave UML

Figure 2.22 – Killer Wave UML

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 the GameManager, 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:
Figure 2.24 – Creating a C# script in the Unity editor

Figure 2.23– Creating a C# script in the Unity Editor

  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:

Figure 2.25 – The GameManager icon

Figure 2.24 – The GameManager icon

As we did when 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 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.

    Tip

    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 that the game object we are resetting is selected. Click the metal cog at the top-right corner of the Inspector window, and then select Reset.

Double-click the GameManager script to open it up in your IDE (Visual Studio or whatever IDE you use), and 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()
             {
         
             }
  2. 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);
  3. 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:

Figure 2.26 – The current code layout for the GameManager script

Figure 2.25– The current code layout for the GameManager script

Tip

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:

Figure 2.26 – The Play, Pause, and Step button locations

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 that 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 that 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:

Figure 2.28 – Main Camera values changing with our script

Figure 2.27 – Main Camera values changing with our script

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 can 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:
Figure 2.29 – The slider in the bottom right of the Project window zooms in and out of thumbnails

Figure 2.28 – The slider in the bottom right of the Project window zooms in and out of thumbnails

  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.

Tip

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 more about 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 also being 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 Transform Rotation values to the following: X: 50, Y: -30, and Z: 0.

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

Figure 2.30 – The player ship lit up

Figure 2.29 – The player ship lit up

Different Lights

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 Transform Rotation 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);
           }
  2. Add LightSetup(); in the scope of the Start function.
  3. Save the script.

The LightSetup method does three things:

  1. It grabs the light from the scene and stores it as a reference.
  2. It sets the rotation of the light with EulerAngles.
  3. Finally, it changes the light's color.

    EulerAngles

    eulerAngles allows us to give Vector3 coordinates instead of Quaternion values. eulerAngles makes rotations 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 the IActorTemplate interface with a partial overview of our game framework:

Figure 2.31 – IActorTemplate UML

Figure 2.30 – IActorTemplate UML

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

  1. Create a script in the Assets/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);
          }
  3. 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 implements them. For example, the Player script that we'll create later on in the chapter inherits the IActorTemplate 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 SOActorModel type. SOActorModel is 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 that 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 Document (GDD) 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 aforementioned game objects 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:

Figure 2.32 – SOActorModel UML

Figure 2.31 – SOActorModel UML

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

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 our SOActorModel is a string named actorName. This actorName will be used to name the type of enemy, spawner, or bullet it is.

Let's create a scriptable object, as follows:

  1. In the Project window in the Unity Editor, create a script in the Assets/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;
         }
  3. Save the script.

Inside the SOActorModel, we will be naming most, if not all, of these variables in the Player script. Similar to how an interface signs a contract with a class, the SOActorModel 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 the SOActorModel code we just entered.

We named our scriptable object SOActorModel as 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 using 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:
Figure 2.33 – Creating an Actor in the Unity editor

Figure 2.32 – Creating an Actor in the Unity Editor

  • Inheritance: We aren't inheriting MonoBehaviour but ScriptableObject instead, 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 our SOActorModel ScriptableObject made, we can now create an asset that will act as a template that can be used not just by programmers 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 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 my own values:

Figure 2.34 – Player values

Figure 2.33 – Player 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).
  • Ship Type: Choose 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 the player can get hit before dying.
  • Speed: The movement speed of the player.
  • Hit Power: Determines how much damage the player will cause if they collide with the enemy.
  • Actor: Place the player_ship prefab here (Assets/Prefab/Player
  • Actors Bullets: Place the player_bullet prefab here (Assets/Prefab /Player/).

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 attach to EnemySpawner for later on in the chapter. For the sake of keeping our work fresh and complete, let's continue with that before moving on to 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/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:

Figure 2.35 – The Basic Wave Enemy values

Figure 2.34 – The Basic Wave Enemy values

Let's briefly go through each of the values for our enemy:

  • Actor Name: enemy_wave.
  • Ship 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 on anything.
  • Health: 1, which means it takes 1 hit to die.
  • Speed: -50, 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/Prefab/Enemies).
  • Actors 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. As with 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:

Figure 2.36 – The Player Bullet values

Figure 2.35 – The Player Bullet values

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

  • Actor Name: player_bullet.
  • Ship 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/Prefab/Player).
  • Actors Bullets: 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: Bullet 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 PlayerSpawner script will also set the player's values when it is created. For example, if our player had a particular speed value, or if they had received an upgrade from the shop, the PlayerSpawner script 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 relationship with the other classes around it:

Figure 2.37 – PlayerSpawner UML

Figure 2.36 – PlayerSpawner UML

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

  • Player: PlayerSpawner is connected to Player because it creates the player.
  • SOActorModel: This is a ScriptableObject that gives the PlayerSpawner its values, which are then passed on to the Player.
  • IActorTemplate: This is the interface that generalizes the script with other common functions.
  • GameManager: This will send and receive general game information from and to the PlayerSpawner 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 following these steps:

  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/Scripts folder with the filename PlayerSpawner.
  2. Open the script and make sure that 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 within Unity. Its common purpose is so the script can be attached to a game object.

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

Inside the PlayerSpawner class, 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 been created from our CreatePlayer method.

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

After the global variables, we add a Start function that will run automatically as soon as the game object holding the PlayerSpawner script is active at runtime.

Inside the scope of the Start function is a method that we are going to create called CreatePlayer.

  1. Continue by entering the CreatePlayer method:
    void CreatePlayer()
           {
             //CREATE PLAYER
             actorModel = Object.Instantiate(Resources.Load
                ("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 PLAYER and //SET PLAYER UP) due to its size.

This first part of the CreatePlayer method will instantiate the player ship's ScriptableObject asset and store it in the actorModel variable. 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 the CreatePlayer method, 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 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 that _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:
Figure 2.38 – Selecting an icon for the PlayerSpawner

Figure 2.37 – Selecting an icon for the PlayerSpawner

  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.

    Tip

    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 is to help set the boundaries of our ship within the game's screen ratio (we will explain more about this in the next chapter); the second is for later on in the book, where we will make the player ship animate in 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:
Figure 2.39 – PlayerSpawner Transform values in the Inspector window

Figure 2.38 – PlayerSpawner Transform values in the Inspector window

  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, and then select Input Manager from the list:
Figure 2.40 – Selecting the Input Manager in the Unity editor

Figure 2.39 – Selecting the Input Manager in the Unity Editor

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 the following:

  • 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 the Axes dropdown by clicking the arrow next to it.
  • Expand Horizontal, as shown in the following screenshot:
Figure 2.41 – The Input Manager

Figure 2.40 – The Input Manager

  • Horizontal: The left button configures horizontal negatively (-1), and the right button configures it positively (+1). Alternative key presses to this effect are A for left and D for 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 that 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 a Mac) (that is, the positive button), with 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 Input Manager window, click the little blue book at the top-right corner of the Input Manager panel.

Our controls are now set in the Input Manager 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 the player ship not go outside of the screenplay area. Before we continue, let's remind ourselves where the Player script lies in our game framework:

Figure 2.42 – Player UML

Figure 2.41 – Player UML

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

  • PlayerBullet: The Player script will create bullets to fire.
  • PlayerSpawner: The Player script is created from the PlayerSpawner.
  • IActorTemplate: Contains damage control and the properties for Player.
  • GameManager: Extra information such as the number of lives, the score, the level, and whatever upgrades the player ship has accumulated will be stored in GameManager.
  • SOActorModel: Holds ScriptableObject properties for Player.

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/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 import 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 will cause when colliding into something that can 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 from which the player fires. If that seemed a little rushed, go back to the Introducing our ScriptableObject – SOActorModel section, where we went 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 that the game is played in. We will discuss these two more in the next block of code.

Before we begin the following Start function code block, you 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 when the object is created. Start is executed when it's enabled, 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 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 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.main component 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:

Figure 2.43 – Screen versus viewport measurements

Figure 2.42 – Screen versus viewport measurements

So, with viewports, no matter what the screen's 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 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 on every frame. We will go into depth about these two methods later on in the chapter, for now we will comment ("//") these two methods out to avoid the script not being able to run.

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 SOActorModel ScriptableObject asset that 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 that 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/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 our Hierarchy window containing the _Player, PlayerSpawner, and the GameManager game objects, it's time to test out the game. We can see the player ship get created in our Game window by pressing Play in the Editor.

The following screenshot shows our game in Play mode; note the Hierarchy window on the left with the PlayerSpawner game object as the parent of the Player game object; also note the Game window 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 shows our Scene window with our PlayerSpawner icon:

Figure 2.44 – The current Player setup in our game

Figure 2.43 – The current Player setup in our game

Tip

Before moving on to the next section, create a prefab of the PlayerSpawner game object by dragging and dropping it into the Project window to Assets/Player. That way, if you lose the scene for whatever reason along with its Hierarchy content, 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 into 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 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 Enemy tag so they are easily identified. If the tag is equal to Enemy, we drop it 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 the third if statement is an else condition where, in the event that the player doesn't have an energy +1(Clone) game object, they get 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.

    Tip

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

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

  1. 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 for the player to 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 scree:.

  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 and by the travelSpeed we set from our ScriptableObject.
  2. 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 whether the player's position is greater than the width and a slightly different buffer.

Apart from that, the if statement and its content serve 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 Vertical string 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 player's view.

  1. Let's move on to the last if statement in the Movement method, moving 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.

Tip

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 ensure 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 with the Player script by adding the Die method.

The Die method

Adding the Die method to the Player script will make it possible for our player to 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 content to the Attack method in the Player script.

The purpose of this Attack method 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.

Note

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 the if statement, we make another if statement 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 on something UI-related, we call return, which means we exit the if statement so that we don't fire a shot.

Because we have entered the movement and attack function content we can scroll back up to the Update function and remove the comments we added.

Our Update function will now look like the following:

void Update()
{
	Movement();
	Attack();
}

Next, we Instantiate our PlayerBullet game object from its instance name, fire. We also face the fire game 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 named bullet.

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

Finally, within the if statement, 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 the PlayerBullet script is to the Player script because it carries the IActorTemplate and SOActorModel scripts, which are already coded into the Player script.

Let's create our PlayerBullet script:

  1. In the Project window of the Unity Editor, create a script in the Assets/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, as previously mentioned.

  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 Awake function is the ActorStats 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 preceding block of code, we run a check to see whether 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 is an obstacle. Otherwise, we deduct health from the Enemy game object and check to see whether 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).

Important Note

If you would like to know more about Time.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 you have saved the script before continuing.

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

  1. Navigate to Assets/Prefab/Player and select player_bullet.
  2. With Player_Bullet selected, click the Add Component button in the Inspector window and type PlayerBullet until you see the PlayerBullet script.
  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:

Figure 2.45 – The player_bullet components in the Inspector window

Figure 2.44 – The player_bullet components in the Inspector window

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://github.com/retrophil/Unity-Certified-Programmer-Exam-Guide-2nd-Edition/blob/main/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 that the layout is basically the same as the player's side of the game framework:

Figure 2.46 – EnemySpawner and Enemy UML

Figure 2.45 – EnemySpawner and Enemy UML

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/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 default UnityEngine library.

We are also going to be using another library, called System.Collections. This is required when we come to use Coroutines, 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 this, 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 Inspector window, as shown in the following screenshot:

Figure 2.47 – Enemy spawn rate slider

Figure 2.46 – Enemy spawn rate slider

  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.

Important Information

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 can consider them a more advanced version of Unity's own Invoke function.

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.

  1. 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.
  2. 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 FireEnemy IEnumerator, 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 turned 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 FireEnemy IEnumerator, 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 called CreateEnemy. Apart from the obvious, this method will do the following:

  1. Instantiate the enemy game object from its ScriptableObject asset.
  2. Apply values to our enemy from its ScriptableObject asset.
  3. 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. As 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's Transform property values in the Inspector window:
Figure 2.48 – The EnemySpawner Transform values in the Inspector window

Figure 2.47 – The EnemySpawner Transform values in the Inspector window

  1. Still in the Inspector window, click Add Component and type EnemySpawner until you see it in the list, and then click it.

Also, for a visual aid in the Scene window, it is 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:

Figure 2.49 – The EnemySpawner icon

Figure 2.48 – The EnemySpawner icon

We can now add an enemy to our Enemy Spawner along with the 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:

Figure 2.50 – The EnemySpawner component holding the BasicWave Enemy actor

Figure 2.49 – The EnemySpawner component holding the BasicWave Enemy actor

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

Setting up our enemy script

As with 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/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.

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

We have a public class named EnemyWave that inherits MonoBehaviour by default but also adds our IActorTemplate 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 ScriptableObject SOActorModel. 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 OnTriggerEnter function 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 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 make the enemy move 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:

Figure 2.51 – The enemies' wave attack pattern

Figure 2.50 – The enemies' wave attack pattern

  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 with Time.deltaTime being collected in a float variable labeled time.

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

The end result is stored in the Vector3 y 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 to what 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:

Figure 2.52 – The Game window with the current gameplay and the Hierarchy game object structured

Figure 2.51 – The Game window with the current gameplay and the Hierarchy game object structured

You have done so much already! The good news is that 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 they will take you to the code that's causing the 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:

Figure 2.53 – Killer Wave UML

Figure 2.52 – Killer Wave UML

We have created a game framework that would need only a few changes whether 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 possible 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.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Learn essentials of game scripting with Unity and C# to customize aspects of your game
  • Tackle challenges in Unity game development and the certification exam using effective techniques and solutions
  • Prepare for the latest Unity certification exam using mock tests, exam tips, and self-assessment questions

Description

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 will also enable you to be a part of the Unity community. This study guide will start by building on your understanding of C# programming and taking you through the process of downloading and installing Unity. You’ll understand how Unity works and get to grips with the Unity exam’s core objectives. 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 modern 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 be able to create impressive Unity applications by making the most of its toolset.

What you will learn

Discover techniques for writing modular, readable, and reusable scripts in Unity Implement and configure objects, physics, controls, and movements for your game projects Understand 2D and 3D animation and write scripts to interact and use Unity s rendering API Explore Unity APIs for adding lighting, materials, and textures to your apps Write Unity scripts for building interfaces for menu systems, UI navigation, application settings, and much more Focus on SOLID principles for writing clean and maintainable Unity applications

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : May 2, 2022
Length 766 pages
Edition : 2nd Edition
Language : English
ISBN-13 : 9781803246215
Vendor :
Unity Technologies
Category :
Concepts :

Table of Contents

17 Chapters
Preface Chevron down icon Chevron up icon
Chapter 1: Setting Up and Structuring Our Project Chevron down icon Chevron up icon
Chapter 2: Adding and Manipulating Objects Chevron down icon Chevron up icon
Chapter 3: Managing Scripts and Taking a Mock Test Chevron down icon Chevron up icon
Chapter 4: Applying Art, Animation, and Particles Chevron down icon Chevron up icon
Chapter 5: Creating a Shop Scene for Our Game Chevron down icon Chevron up icon
Chapter 6: Purchasing In-Game Items and Advertisements Chevron down icon Chevron up icon
Chapter 7: Creating a Game Loop and Mock Test Chevron down icon Chevron up icon
Chapter 8: Adding Custom Fonts and UI Chevron down icon Chevron up icon
Chapter 9: Creating a 2D Shop Interface and In-Game HUD Chevron down icon Chevron up icon
Chapter 10: Pausing the Game, Altering Sound, and a Mock Test Chevron down icon Chevron up icon
Chapter 11: Storing Data and Audio Mixer Chevron down icon Chevron up icon
Chapter 12: NavMesh, Timeline, and a Mock Test Chevron down icon Chevron up icon
Chapter 13: Effects, Testing, Performance, and Alt Controls Chevron down icon Chevron up icon
Chapter 14: Full Unity Programmer Mock Exam Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Appendix Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Empty star icon Empty star icon Empty star icon Empty star icon Empty star icon 0
(0 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


No reviews found
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.