Learning C# by Developing Games with Unity 2020
Fifth Edition

An enjoyable and intuitive approach to getting started with C# programming and Unity

Harrison Ferrone

BIRMINGHAM - MUMBAI

Learning C# by Developing Games with Unity 2020
Fifth Edition

Copyright © 2020 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Commissioning Editor: Pavan Ramchandani
Acquisition Editor: Ashitosh Gupta
Content Development Editor: Akhil Nair
Senior Editor: Hayden Edwards
Technical Editor: Deepesh Patel
Copy Editor: Safis Editing
Project Coordinator: Kinjal Bari
Proofreader: Safis Editing
Indexer: Pratik Shirodkar
Production Designer: Nilesh Mohite

First published: September 2013
Second edition: March 2016
Third edition: December 2017
Fourth edition: March 2019
Fifth edition: August 2020

Production reference: 1200820

Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.

ISBN 978-1-80020-780-6

www.packt.com

"If people reach perfection they vanish, you know."

The Once and Future King

Packt.com

Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.

Why subscribe?

Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals

Improve your learning with Skill Plans built especially for you

Get a free eBook or video every month

Fully searchable for easy access to vital information

Copy and paste, print, and bookmark content

Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at customercare@packtpub.com for more details.

At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks. 

Contributors

About the author

Harrison Ferrone was born in Chicago, IL, and was raised all over. Most days you can find him writing technical documentation at Microsoft, creating instructional content for LinkedIn Learning and Pluralsight, or tech editing for the Ray Wenderlich website.

He holds various fancy looking pieces of paper from the University of Colorado at Boulder and Columbia College, Chicago. Despite being a proud alumnus, most of these are stored in a basement somewhere.

After a few years as an iOS developer at small start-ups, and one Fortune 500 company, he fell into a teaching career and never looked back. Throughout all this, he's bought many books, acquired a few cats, worked abroad, and continually wondered why Neuromancer isn't on more course syllabi.

Completing this book wouldn't have been possible without the support of Kelsey, my partner in crime on this journey, and Wilbur, Merlin, Walter, and Evey for their courageous spirits and gracious hearts.

About the reviewers

Andrew Edmonds is an experienced programmer, game developer, and educator. He has a Bachelor of Science degree in Computer Science from Washburn University and is a Unity Certified Programmer and Instructor. After college, he worked as a software engineer for the Kansas State Legislature for three years before spending the next five years teaching high school kids how to write code and make video games. As a teacher, he helped many young aspiring game developers achieve beyond what they ever thought possible, including winning the SkillsUSA National Championship for video game development in 2019 with a virtual reality game made in Unity. Andrew lives in Washington with his wife, Jessica, and daughters, Alice and Ada.

Adam Brzozowski is an experienced software engineer who develops games and client applications. Working with Unity, Unreal Engine, C++, Swift, and Java, he finds the right solution for each project.

Packt is searching for authors like you

If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.

Preface

Unity is one of the most popular game engines in the world, catering to amateur hobbyists, professional AAA studios, and cinematic production houses. While mainly considered a 3D tool, Unity has a host of dedicated features that support everything from 2D games and virtual reality to post-production and cross-platform publishing.

Developers love its drag-and-drop interface and built-in features, but it's the ability to write custom C# scripts for behaviors and game mechanics that really take Unity the extra mile. Learning to write C# code might not be a huge obstacle to a seasoned programmer with other languages under their belt, but it can be daunting for those of you who have no programming experience. That's where this book comes in, as I'll be taking you through the building blocks of programming and the C# language from scratch, all while building a fun and playable game in Unity.

Who this book is for

This book was written primarily for those of you who don't have any experience with the basic tenets of programming or the C# language. If you're a competent novice or seasoned programmer coming from another language, or even C#, but need to get hands-on with game development in Unity, then this is where you need to be.

What this book covers

Chapter 1, Getting to Know Your Environment, gets you started with the Unity installation process, the main features of the editor, and finding documentation for both C# and Unity-specific topics. We'll also go through creating C# scripts from inside Unity and take a look at the Visual Studio application, where all our code editing will take place.

Chapter 2, The Building Blocks of Programming, begins by laying out the atomic-level concepts of programming, giving you the opportunity to relate variables, methods, and classes to situations in everyday life. From there, we move on to simple debugging techniques, proper formatting and commenting, and a look at how Unity turns C# scripts into components.

Chapter 3, Diving into Variables, Types, and Methods, takes a deeper look at variables. This includes C# data types, naming conventions, access modifiers, and everything else you'll need for the foundation of a program. We'll also go over how to write methods, incorporate parameters, and use return types effectively, ending with an overview of standard Unity methods belonging to the MonoBehavior class.

Chapter 4, Control Flow and Collection Types, introduces the common approaches to making decisions in code, consisting of the if...else and switch statements. From there, we move on to working with arrays, lists, and dictionaries, and incorporating iteration statements for looping through collection types. We end the chapter with a look at conditional looping statements and a special C# data type called enumerations.

Chapter 5, Working with Classes, Structs, and OOP, details our first contact with constructing and instantiating classes and structs. We'll go through the basic steps of creating constructors, adding variables and methods, and the fundamentals of subclassing and inheritance. The chapter will end with a comprehensive explanation of object-oriented programming and how it applies to C#.

Chapter 6, Getting Your Hands Dirty with Unity, marks our departure from C# syntax into the world of game design, level building, and Unity's featured tools. We'll start by going over the basics of a game design document and then move on to blocking out our level geometry and adding lighting and a simple particle system.

Chapter 7, Movement, Camera Controls, and Collisions, explains different approaches to moving a player object and setting up a third-person camera. We'll discuss incorporating Unity physics for more realistic locomotion effects, as well as how to work with collider components and capture interactions within a scene.

Chapter 8, Scripting Game Mechanics, introduces the concept of game mechanics and how to effectively implement them. We'll start by adding a simple jump action, create a shooting mechanic, and build on the previous chapters' code by adding logic to handle item collection.

Chapter 9, Basic AI and Enemy Behavior, starts with a brief overview of artificial intelligence in games and the concepts we will be applying to Hero Born. Topics covered in this chapter will include navigation in Unity, using the level geometry and a navigation mesh, smart agents, and automated enemy movement.

Chapter 10, Revisiting Types, Methods, and Classes, takes a more in-depth look at data types, intermediate method features, and additional behaviors that can be used for more complex classes. This chapter will give you a deeper understanding of the versatility and breadth of the C# language.

Chapter 11, Introducing Stacks, Queues, and HashSets, dives into intermediate collection types and their features. Topics covered in this chapter include using Stacks, Queues, and HashSets and the different development scenarios that each is uniquely suited for.

Chapter 12, Exploring Generics, Delegates, and Beyond, details intermediate features of the C# language and how to apply them in practical, real-world scenarios. We'll start with an overview of generic programming and progress to concepts such as delegation, events, and exception handling. The chapter will end with a brief discussion of common design patterns and set you up for further study.

Chapter 13, The Journey Continues, reviews the main topics you've learned throughout the book and leaves you with resources for further study in both C# and Unity. Included in these resources will be online reading material, certification information, and a host of my favorite video tutorial channels.

To get the most out of this book

The only thing you need to get the most from your upcoming C# and Unity adventure is a curious mind and a willingness to learn. Having said that, doing all the Time for Action, Heroes Trial, and Quiz sections is a must if you hope to cement the knowledge you're learning. Lastly, revisiting topics and entire chapters to refresh or solidify your understanding before moving on is always a good idea. There is no sense in building a house on an unstable foundation.

You'll also need a current version of Unity installed on your computer – 2020 or later is recommended. All code examples have been tested with Unity 2020.1 and should work with future versions without issues.

Software/hardware covered in the book

Unity 2020.1 or later

Visual Studio 2019 or later

C# 8.0 or later

Before starting, check that your computer setup meets the Unity system requirements at https://docs.unity3d.com/2019.1/Documentation/Manual/system-requirements.html. These are for Unity 2019 but hold true for 2020 and above.

Download the example code files

You can download the example code files for this book from your account at www.packt.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you.

You can download the code files by following these steps:

Log in or register at www.packt.com.

Select the Support tab.

Click on Code Downloads.

Enter the name of the book in the Search box and follow the onscreen instructions.

Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:

WinRAR/7-Zip for Windows

Zipeg/iZip/UnRarX for Mac

7-Zip/PeaZip for Linux

The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Learning-C-8-by-Developing-Games-with-Unity-2020. In case there's an update to the code, it will be updated on the existing GitHub repository.

We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!

Download the color images

We also provide a PDF file that has color images of the screenshots/diagrams used in this book. You can download it here: https://static.packt-cdn.com/downloads/9781800207806_ColorImages.pdf.

Conventions used

There are a number of text conventions used throughout this book.

CodeInText: Indicates code words in the text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "Select the Materials folder."

A block of code is set as follows:

public string firstName = "Harrison";

When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:

accessModifier returnType UniqueName(parameterType parameterName) {
    method body
}

Bold: Indicates a new term, an important word, or words that you see on screen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: "Click on Create | 3D Object | Capsule from the Hierarchy panel."

Warnings or important notes appear like this.

Tips and tricks appear like this.

Get in touch

Feedback from our readers is always welcome.

General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at customercare@packtpub.com.

Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.

Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at copyright@packt.com with a link to the material.

If you are interested in becoming an author: If there is a topic that you have expertise in, and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.

Reviews

Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!

For more information about Packt, please visit packt.com.

Getting to Know Your Environment

Pop culture has taught us that computer programmers are often outsiders, lone wolves, or geeky hackers who possess extraordinary mental gifts for algorithmic thought, little social IQ, and the odd anarchic bent. While this is not the case, there is something to the idea that learning to code fundamentally changes the way you look at the world. The good news is that your naturally curious mind will quickly adapt to this new way of thinking and may even come to enjoy it.

You already use analytical skills in your everyday life that translate to programming – you're just missing the right language and syntax to map those life skills into code. You know your age, right? That's a variable. When you cross the street, I presume you look down the road in both directions before stepping off the curb like the rest of us. That's evaluating different conditions, or what we call control flow in programming parlance. When you look at a can of pop, you instinctively identify that it has certain properties such as shape, weight, and contents. That's a class object! You get the idea.

With all that real-world experience at your fingertips, you're more than ready to cross over into the realm of programming. You'll need to know how to set up your development environment, work with the applications involved, and know exactly where to go when you need help. To that end, we're going to begin our adventure delving into C# by covering the following topics:

Getting started with Unity

Working with Visual Studio

Using C# with Unity

Exploring the documentation

Let's get started!

Technical requirements

Sometimes, it's easier to start with what a thing isn't, rather than what it is. The main goal of this book isn't to learn the vast ins and outs of the Unity game engine or all of game development. By necessity, we'll cover these topics at a basic level here at the beginning of our journey, and in more detail in Chapter 6, Getting Your Hands Dirty with Unity. However, these topics are simply to provide a fun, accessible way for us to learn the C# programming language from the ground up. 

Since this book is aimed at complete beginners to programming, if you have no previous experience with either C# or Unity, you're in the right place! If you've had some experience with the Unity Editor but not with programming, guess what? This is still the place to be. Even if you've dabbled in a bit of C# mixed with Unity, but want to explore some more intermediate or advanced topics, the later chapters of this book can provide you with what you're looking for.

If you're an experienced programmer in other languages, feel free to skip the beginner theory and dive right into the parts you're interested in, or stick around and refresh your fundamentals.

Getting started with Unity 2020

If you don’t have Unity installed already, or are running an earlier version, you'll need to do a little setup. Follow these steps:

Head over to https://www.unity.com/.

Select Get Started (shown in the following screenshot), which will take you to the Unity store page:

If the Unity home page looks different for you than what you can see in the preceding screenshot, you can go directly to https://store.unity.com.

Don't feel overwhelmed by this – you can get Unity completely free!

Click the Individual tab and select the Personal option on the left. The other paid options offer more advanced functionality and services subscribers, but you can check these out on your own:

After selecting the personal plan, you'll be asked if you're a first-time or returning user.

Select Start here under First-time Users: 

Select Agree and download to get your copy of Unity Hub:

Once the download is complete, follow these steps: 

Open up the package (by double-clicking it).

Accept the user agreement.

Follow the installation instructions. When you get the green light, go ahead and fire up the Unity Hub application! You'll see the following screen:

The newest version of Unity Hub has a wizard or getting started path when you first open the application. If you'd like to follow that, feel free. The following steps show you how to start a new project without any help from the application since that's only available on the first launch.

With Unity Hub open, switch to the Installs tab from the left-hand menu and select ADD:

At the time of writing, Unity 2020 is still in its Alpha phase, but you should be able to select a 2020 version from the Latest Official Releases list:

You won't need any specific platform modules to follow along with future examples, so go ahead and leave this as-is. If you do want to add them at any time, you can click the More button (three-dot icon) at the upper right of any version in the Installs window:

When the installation is complete, you'll see a new version in your Installs panel, as follows:

You can find additional information and resources about the Unity Hub application at https://docs.unity3d.com/Manual/GettingStartedInstallingHub.html.

There's always a chance of something going wrong, so be sure to check the following section if you're using macOS Catalina or higher. 

Using macOS 

If you're working on a Mac with OS Catalina or later, there is a known issue with using Unity Hub 2.2.2 (and earlier versions) to install Unity with the preceding steps. If that's the case for you, take a deep breath and go to the Unity download archive and grab the version you need (https://unity3d.com/get-unity/download/archive). Remember to use the Downloads (Mac) option instead of the Unity Hub download:

If you're working on Windows and run into similar install issues, the steps we just took will also work just fine.

The download is a normal application installer since it's a .dmg file. Open it up, follow the instructions, and you'll be ready to go in no time!

All of the examples and screenshots for this book were created and captured using Unity 2020.1.0a20. If you're using a newer version, things might look slightly different in the Unity editor, but this shouldn't affect you following along.

Now that Unity Hub and Unity 2020 are installed, it's time to create a new project!

Creating a new project

Launch the Unity Hub application to start a new project. If you have a Unity account, go ahead and sign-in; if not, you can either create one or hit Skip at the bottom of the screen.

Now, let's set up a new project by selecting the arrow icon next to the NEW tab at the top-right:

Choose your 2020 version and set the following fields:

Project Name: I'll be calling mine Hero Born.

Location: Wherever you'd like the project to be saved.

Template: The project will default to 3D, so hit CREATE:

With the project created, you're all set to explore the Unity interface.

Navigating the editor

When the new project finishes initializing, you'll see the glorious Unity Editor! I've marked the important tabs (or panels, if you prefer) in the following screenshot:

This is a lot to take in, so we'll look at each of these panels in more detail:

The Toolbar panel is the topmost part of the Unity editor. From here, you can manipulate objects (far-left button group) and play and pause the game (center buttons). The rightmost button group contains Unity Services, layer masks, and layout scheme features, which we won't be using in this book.

The Hierarchy window shows every item currently in the game scene. In the starter project, this is just the default camera and directional light, but when we create our prototype environment, this window will start to get filled in.

The Game and Scene views are the most visual aspects of the editor. Think of the Scene view as your stage, where you can move and arrange 2D and 3D objects. When you hit the Play button, the Game view will take over, rendering the Scene view and any programmed interactions.

The Inspector window is your one-stop-shop for viewing and editing the properties of your objects. If you select the Main Camera component, you'll see several parts (Unity calls them components) are displayed – all of which are accessible from here.

The Project window holds every asset that's currently in your project. Think of this as a representation of your project's folders and files.

The Console panel is where any output we want our scripts to print will show up. From here on out, if we talk about the console or debug output, this panel is where it will be displayed.

You can find more in-depth breakdowns of each window's functionality in the Unity docs at https://docs.unity3d.com/Manual/UsingTheEditor.html.

I know that was a lot to process if you're new to Unity, but rest assured that any instructions going forward will always reference the necessary steps. I won't leave you hanging or wondering what button to push. With that out of the way, let's start creating some actual C# scripts.

Using C# with Unity 

Going forward, it's important to think of Unity and C# as symbiotic entities. Unity is the engine where you'll create scripts and eventually run them, but the actual programming takes place in another program called Visual Studio. Don't worry about that right now – we'll get to that in a moment.

Working with C# scripts

Even though we haven't covered any basic programming concepts yet, they won't have a home until we know how to create an actual C# script in Unity. 

There are several ways to create C# scripts from the editor:

Select Assets | Create | C# Script.

In the Project tab, select Create | C# Script.

Right-click in the Project tab (on the right-hand side) and select Create | C# Script from the pop-up menu.

Select a GameObject in the Hierarchy window and click Add Component | New Script.

Going forward, whenever you're instructed to create a C# script, please use whichever method you prefer.

Resources and objects other than C# scripts can be created in the editor using the preceding methods. I'm not going to call out each of these variations every time we create something new, so just keep the options in the back of your mind.

For the sake of organization, we're going to store our various assets and scripts inside their marked folders. This isn't just a Unity-related task – it's something you should always do, and your coworkers will thank you (I promise):

Click Create | Folder (or whichever method you like best) from the Project tab and name it Scripts:

Double-click on the Scripts folder and create a new C# script. By default, the script will be named NewBehaviourScript, but you'll see the filename highlighted, so you have the option to immediately rename it. Type in LearningCurve and hit Enter:

We've just created a subfolder named Scripts, as shown in the preceding screenshot. Inside that parent folder, we've created a C# script named LearningCurve.cs (the .cs file type stands for C-Sharp, in case you were wondering), which is now saved as part of our Hero Born project assets. All that's left to do is open it up in Visual Studio!

Introducing the Visual Studio editor

While Unity can create and store C# scripts, they need to be edited using Visual Studio. A copy of Visual Studio comes pre-packaged with Unity and will open it up automatically when you double-click any C# script from inside the editor.

Time for action – opening a C# file

Unity will synchronize with Visual Studio the first time you open a file. The simplest way to do this is by selecting the script from the Projects tab.

Double-click on LearningCurve.cs, as follows:

This will open up the C# file in Visual Studio:

If the file opens up in another default application, follow these steps:

Select Unity | Preferences from the top menu and choose External Tools in the left-hand panel.

Change the External Script Editor to Visual Studio, as shown in the following screenshot:

You'll see a folder structure on the left-hand side of the interface that mirrors the one in Unity, which you can access like any other. On the right-hand side is the actual code editor where the magic happens. There are far more features to the Visual Studio application, but this is all we need to get the programmatic ball rolling.

The Visual Studio interface is different for Windows and Mac environments, but the code we'll be using throughout this book will work equally well with both. All the screenshots in this book have been taken in a Mac environment, so if things look different on your computer, there's no need to worry.

Beware of naming mismatches

One common pitfall that trips up new programmers is file naming – more specifically, naming mismatches – which we can illustrate using line 5 from the previous screenshot of the C# file in Visual Studio:

public class LearningCurve : MonoBehaviour

The LearningCurve class name is the same as the LearningCurve.cs filename. This is an essential requirement. It's OK if you don't know what a class is quite yet. The important thing to remember is that, in Unity, the filename and the class name need to be the same. If you're using C# outside of Unity, the filename and class name don't have to match.

When you create a C# script file in Unity, the filename in the Project tab is already in Edit mode, ready to be renamed. It's a good habit to rename it then and there. If you rename the script later, the filename and the class name won't match. The filename would change, but line five would be as follows:

public class NewBehaviourScript : MonoBehaviour

If you accidentally do this, it's not the end of the world. All you need to do is go into Visual Studio and change NewBehaviourScript to the name of your C# script.

Syncing C# files

As part of their symbiotic relationship, Unity and Visual Studio keep in touch with each other to synchronize their content. This means that if you add, delete, or change a script file in one application, the other application will see the changes automatically.

So, what happens when Murphy's Law strikes and syncing just doesn't seem to be working correctly? If you run into this situation, take a deep breath, select the troublesome script, right-click, and select Refresh. 

You now have the basics of script creation under your belt, so it's time we talk about finding and efficiently using helpful resources.

Exploring the documentation

The last topic we'll touch on in this first foray into Unity and C# scripts is documentation. Not sexy, I know, but it's important to form good habits early when dealing with new programming languages or development environments.

Accessing Unity's documentation

Once you start writing scripts in earnest, you'll be using Unity's documentation quite often, so it's beneficial to know how to access it early on. The Reference Manual will give you an overview of a component or topic, while specific programming examples can be found in the Scripting Reference.

Time for action – opening the Reference Manual

Every GameObject (an item in the Hierarchy window) in a Scene has a Transform component that controls its Position, Rotation, and Scale. To keep things simple, we'll just look up the camera's Transform component in the Reference Manual:

In the Hierarchy tab, select the Main Camera object.

Move over to the Inspector tab and click on the information icon (question mark) at the top-right of the Transform component:

You'll see a web browser open on the Transform page of the Reference Manual. All the components in Unity have this feature, so if you ever want to know more about how something works, you know what to do:

Time for action – using the Scripting Reference

So, we've got the Reference Manual open, but what if we wanted concrete coding examples related to the Transform component? It's pretty simple – all we need to do is ask the Scripting Reference.

Click on Scripting API, or the Switch to Scripting link underneath the component or class name (Transform, in this case).

By doing this, the Reference Manual automatically switches to the Scripting Reference for the Transform component:

The Scripting Reference is a large document because it has to be. However, this doesn't mean you have to memorize it or even be familiar with all of its information to start writing scripts. As the name suggests, it's a reference, not a test. 

If you find yourself lost in the documentation, or just out of ideas regarding where to look, you can also find solutions within the rich Unity development community in the following places:

Unity Forums

Unity Answers

Unity Discord

On the other side of things, you'll need to know where to find resources on any C# question, which we'll cover next. 

Locating C# resources

Now that we've got our Unity resources taken care of, let's take a look at some of Microsoft's C# documents at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/index.

There's a ton of other C# resources, ranging from tutorials and quickstart guides to version specifications (if you're into that), all of which are available at https://docs.microsoft.com/en-us/dotnet/csharp.

Time for action – looking up a C# class

Let's load up the programming guide link and look up the C# String class. Do either of the following:

Enter Strings in the search bar in the top-left corner of the web page

Scroll down to Language Sections and click on the Strings link directly:

You should see something like the following for the class description page. Unlike Unity's documentation, the C# reference and scripting information is all bundled up into one, but its saving grace is the subtopic list on the right-hand side. Use it well:

It's extremely important to know where to find help when you're stuck or have a question, so be sure to circle back to this section whenever you hit a roadblock. 

Summary

We covered quite a bit of logistical information in this chapter, so I can understand if you're itching to write some code. Starting new projects, creating folders and scripts, and accessing documentation are topics that are easily forgotten in the excitement of a new adventure. Just remember that this chapter has a lot of resources you might need in the coming pages, so don't be afraid to come back and visit. Thinking like a programmer is a muscle: the more you work it, the stronger it gets.

In the next chapter, we'll start laying out the theory, vocabulary, and main concepts you'll need to prime your coding brain. Even though the material is conceptual, we'll still be writing our first lines of code in the LearningCurve script. Get ready!

Pop quiz – dealing with scripts

What type of relationship do Unity and Visual Studio share?

The Scripting Reference supplies example code in regards to using a particular Unity component or feature. Where can you find more detailed (non-code-related) information about Unity components?

The Scripting Reference is a large document. How much of it do you have to memorize before attempting to write a script?

When is the best time to name a C# script?

The Building Blocks of Programming

Any programming language starts off looking like ancient Greek to the unaccustomed eye, and C# is no exception. The good news is that underneath the initial mystery, all programming languages are made up of the same essential building blocks. Variables, methods, and classes (or objects) make up the DNA of conventional programming; understanding these simple concepts opens up an entire world of diverse and complex applications. After all, there are only four different DNA nucleobases in every person on earth; yet, here we are, unique organisms to the last.

If you're new to programming, there's going to be a lot of information coming at you in this chapter, and this could mark the first lines of code that you've ever written. The point is not to overload your brain with facts and figures; it's to give you a holistic look at the building blocks of programming using examples from everyday life.

This chapter is all about the high-level view of the bits and pieces that make up a program. Getting the hang of how things work before getting into the code directly will not only help you new coders find your feet, it will also solidify the topics with easy-to-remember references. Ramblings aside, we'll focus on the following topics throughout this chapter:

Defining what variables are and how to use them

Understanding the purpose of methods

Classes and their role as objects

Turning C# scripts into Unity components

Component communication and dot notation

Defining variables

Let's start with a simple question: what is a variable? Depending on your point of view, there are a few different ways of answering that question:

Conceptually, a variable is the most basic unit of programming, as an atom is to the physical world (excepting string theory). Everything starts with variables, and programs can't exist without them.

Technically, a variable is a tiny section of your computer's memory that holds an assigned value. Every variable keeps track of where its information is stored (this is called a memory address), its value, and its type (for instance, numbers, words, or lists). 

Practically, a variable is a container. You can create new ones at will, fill them with stuff, move them around, change what they're holding, and reference them as needed. They can even be empty and still be useful.

A practical real-life example of a variable is a mailbox; remember those?

They can hold letters, bills, a picture from your aunt Mabel – anything. The point is that what's in a mailbox can vary: they can have names, hold information (physical mail), and their contents can even be changed if you have the right security clearance.

Names are important

Referring to the preceding photo, if I asked you to go over and open the mailbox, the first thing you'd probably ask is: which one? If I said the Smith family mailbox, or the brown mailbox, or the round mailbox, then you'd have the necessary context to open the mailbox I was referencing. Similarly, when you are creating variables, you have to give them unique names that you can reference later. We'll get into the specifics of proper formatting and descriptive naming in Chapter 3, Diving into Variables, Types, and Methods.

Variables act as placeholders

When you create and name a variable, you're creating a placeholder for the value that you want to store. Let's take the following simple math equation as an example:

2 + 9 = 11

Okay, no mystery here, but what if we wanted the number 9 to be its variable? Consider the following code block:

myVariable = 9

Now we can use the variable name, myVariable, as a substitute for 9 anywhere we need it:

2 + myVariable = 11

If you're wondering whether variables have other rules or regulations, they do. We'll get to those in the next chapter, so sit tight.

Even though this example isn't real C# code, it illustrates the power of variables and their use as placeholder references. In the next section you'll start creating variables of your own, so keep on rolling!

Time for action – creating a variable

Alright, enough theory; let's create a real variable in our LearningCurve script:

Double-click on LearningCurve to open it in Visual Studio and add lines 7, 12, and 14 (don't worry about the syntax right now – just make sure your script is the same as the script that is shown in the following screenshot):

Save the file using command + S on a Mac keyboard, or Ctrl + S on a Windows keyboard.

For scripts to run in Unity, they have to be attached to GameObjects in the scene. HeroBorn has a camera and directional light by default, which provides the lighting for the scene, so let's attach LearningCurve to the camera to keep things simple: 

Drag and drop LearningCurve.cs onto the Main Camera.

Select the Main Camera so that it appears in the Inspector panel, and verify that the LearningCurve.cs (Script) component is attached properly.

Click Play and watch for the output in the Console panel:

The Debug.Log() statements printed out the result of the simple math equations we put in between the parentheses. As you can see in the following Console screenshot, the equation that used our variable worked the same as if it was a real number:

We'll get into how Unity converts C# scripts into components at the end of this chapter, but first, let's work on changing the value of one of our variables.

Time for action – changing a variable's value

Since currentAge was declared as a variable on line 7, the value it stores can be changed. The updated value will then trickle down to wherever the variable is used in code; let's see this in action:

Stop the game by clicking the Play button if the scene is still running.

Change Current Age to 18 in the Inspector panel and play the scene again, looking at the new output in the Console panel:

The first output will still be 31, but the second output is now 19 because we changed the value of our variable. 

The goal here wasn't to go over variable syntax but to show how variables act as containers that can be created once and referenced elsewhere. We'll go into more detail in Chapter 3, Diving into Variables, Types, and Methods.

Now that we know how to create variables in C# and assign them values, we're ready to dive into the next important programming building block: methods!

Understanding methods

On their own, variables can't do much more than keep track of their assigned values. While this is vital, they are not very useful on their own in terms of creating meaningful applications. So, how do we go about creating actions and driving behavior in our code? The short answer is by using methods. 

Before we get to what methods are and how to use them, we should clarify a small point of terminology. In the world of programming, you'll commonly see the terms method and function used interchangeably, especially in regards to Unity. Since C# is an object-oriented language (this is something that we'll cover in Chapter 5, Working with Classes and Object-Oriented Programming), we'll be using the term method for the rest of the book to conform to standard C# guidelines.

When you come across the word function in the Scripting Reference or any other documentation, think method. 

Methods drive actions

Similarly to variables, defining programming methods can be tediously long-winded or dangerously brief; here's another three-pronged approach to consider:

Conceptually, methods are how work gets done in an application. 

Technically, a method is a block of code containing executable statements that run when the method is called by name. Methods can take in arguments (also called parameters), which can be used inside the method's scope.

Practically, a method is a container for a set of instructions that run every time it's executed. These containers can also take in variables as inputs, which can only be referenced inside the method itself. 

Taken all together, methods are the bones of any program – they connect everything and almost everything is built off of their structure.

Methods are placeholders too

Let's take an oversimplified example of adding two numbers together to drive the concept home. When writing a script, you're essentially laying down lines of code for the computer to execute in sequential order. The first time you need to add two numbers together, you could just brute-force it like in the following code block:

someNumber + anotherNumber

But then you conclude that these numbers need to be added together somewhere else. Instead of copying and pasting the same line of code, which results in sloppy or "spaghetti" code and should be avoided at all costs, you can create a named method that will take care of this action:

AddNumbers
{
    someNumber + anotherNumber
}

Now AddNumbers is holding a place in memory, just like a variable; however, instead of a value, it holds a block of instructions. Using the name of the method (or calling it) anywhere in a script puts the stored instructions at your fingertips without having to repeat any code. 

If you find yourself writing the same lines of code over and over, you're likely missing a chance to simplify or condense repeated actions into common methods.

This produces what programmers jokingly call spaghetti code because it can get messy. You'll also hear programmers refer to a solution called the Don't Repeat Yourself (DRY) principle, which is a mantra you should keep in mind.

As before, once we've seen a new concept in pseudocode, it's best if we implement it ourselves, which is what we'll do in the next section to drive it home.

Time for action – creating a simple method

Let's open up LearningCurve again and see how a method works in C#. Just like with the variables example, you'll want to copy the code into your script exactly as it appears in the following screenshot. I've deleted the previous example code to make things neater, but you can, of course, keep it in your script for reference: 

Open up LearningCurve in Visual Studio and add in lines 8, 13, and 16 - 19.

Save the file, and then go back and hit Play in Unity to see the new Console output:

You defined your first method on lines 16 to 19 and called it on line 13. Now, wherever AddNumbers() is called, the two variables will be added together and printed to the console, even if their values change:

Go ahead and try out different variable values in the Inspector panel to see this in action! More details on the actual code syntax of what you just wrote are coming up in the next chapter.

With a bird's-eye view of methods under our belts, we're ready to tackle the biggest topic in the programming landscape – classes!

Introducing classes

We've seen how variables store information and how methods perform actions, but our programming toolkit is still somewhat limited. We need a way of creating a sort of super container that has its variables and methods that can be referenced from the container itself. Enter classes:

Conceptually, a class holds related information, actions, and behaviors inside a single container. They can even communicate with each other.

Technically, classes are data structures. They can contain variables, methods, and other programmatic information, all of which can be referenced when an object of the class is created.

Practically, a class is a blueprint. It sets out the rules and regulations for any object (called an instance) created using the class blueprint.

You've probably realized that classes surround us not only in Unity but in the real world as well. Next, we'll take a look at the most common Unity class and how they function in the wild.

A common Unity class

Before you wonder what a class looks like in C#, you should know that you've been working with a class this whole chapter. By default, every script created in Unity is a class, which you can see from the class keyword on line 5:

public class LearningCurve: MonoBehavior

MonoBehavior just means that this class can be attached to a GameObject in the Unity scene. In C#, classes can exist on their own, which we'll see when we create standalone classes in Chapter 5, Working with Classes and Object-Oriented Programming.

The terms script and class are sometimes used interchangeably in Unity resources. For consistency, I'll be referring to C# files as scripts if they're attached to GameObjects and as classes if they are standalone. 

Classes are blueprints

For our last example, let's think about a local post office. It's a separate, self-contained environment that has properties, such as a physical address (a variable), and the ability to execute actions, such as sending in your secret decoder ring voucher (methods). 

This makes a post office a great example of a potential class that we can outline in the following block of pseudocode:

PostOffice
{
    // Variables
    Address = "1234 Letter Opener Dr."

    // Methods
    DeliverMail()
    SendMail()
}

The main takeaway here is that when information and behaviors follow a predefined blueprint, complex actions and inter-class communication becomes possible.

For instance, if we had another class that wanted to send a letter through our PostOffice class, it wouldn't have to wonder where to go to fire this action. It could simply call the SendMail function from the PostOffice class, as follows:

PostOffice.SendMail()

Alternatively, you could use it to look up the address of the Post Office so you know where to post your letters:

PostOffice.Address

If you're wondering about the use of periods (called dot notation) between words, we'll be diving into that at the end of the chapter – hold tight.

Your basic programming toolkit is now complete (well, the theory drawer, at least). We'll spend the rest of this section taking you deeper into the syntax and practical uses of variables, methods, and classes.

Working with comments

You might have noticed that LearningCurve has two odd lines of gray text (10 and 21 in the last screenshots) starting with two backslashes, which were created by default with the script. These are code comments, a very powerful, if simple, tool for programmers. 

In C#, there are a few ways that you can use to create comments, and Visual Studio (and other code editing applications) will often make it even easier with built-in shortcuts. 

Some professionals wouldn't call commenting an essential building block of programming, but I'll have to respectfully disagree. Correctly commenting out your code with meaningful information is one of the most fundamental habits a new programmer should have. 

Practical backslashes

The single-line comment is exactly what's already in LearningCurve: 

// This is a single-line comment

Visual Studio doesn't see lines starting with two backslashes (without empty space) as code, so you can use them as much as needed.

Multi-line comments

Since it's in the name, you'd be right to assume that single-line comments only apply to one line of code. If you want multi-line comments, you'll need to use a backslash and an asterisk as opening and closing characters around the comment text:

/* this is a
      multi-line comment */

You can also comment and uncomment blocks of code by highlighting them and using the command + ? shortcut on macOS and Ctrl + K + C on Windows.

Seeing example comments is good, but putting them in your code is always better. It's never too early to start commenting!

Time for action – adding comments

Visual Studio also provides a handy auto-generated commenting feature; type in three backslashes on the line preceding any line of code (variables, methods, classes, and more) and a summary comment block will appear. Open up LearningCurve and add in three backslashes above the ComputeAge() method:

You should see a three-line comment with a description of the method generated by Visual Studio from the method's name, sandwiched between two <summary> tags. You can, of course, change the text, or add new lines by hitting Enter just as you would in a text document; just make sure not to touch the tags.

The useful part about these detailed comments is clear when you want to know something about a method you've written. If you've used a triple forward-slash comment, all you need to do is hover over the method name anywhere it's called and Visual Studio will pop your summary:

We still need to understand how everything we've learned in this chapter applies in the Unity game engine, which is what we'll be focusing on in the next section!

Putting the building blocks together

With the building blocks squared away, it's time to do a little Unity-specific housekeeping before wrapping up this chapter. Specifically, we need to know more about how Unity handles C# scripts attached to GameObjects. For this example, we'll keep using our LearningCurve script and Main Camera GameObject.

Scripts become components

All GameObject components are scripts, whether you or the good people at Unity wrote them. Unity-specific components such as Transform, and their respective scripts, just aren't supposed to be edited by us.

The moment a script that you have created is dropped onto a GameObject, it becomes another component of that object, which is why it appears in the Inspector panel. To Unity, it walks, talks, and acts like any other component, complete with public variables underneath the component that can be changed at any time. Even though we aren't supposed to edit the components provided by Unity, we can still access their properties and methods, making them powerful development tools.

Unity also makes some automatic readability adjustments when a script becomes a component. You might have noticed that when we added LearningCurve to Main Camera, Unity displayed it as Learning Curve, with currentAge changing to Current Age. 

Part of a previous Time for action section already had you update a variable in the Inspector panel, but it's important to touch on how this works in more detail. There are two situations in which you can modify a property value:

In Play mode

In development mode

Changes made in Play mode take effect immediately in real-time, which is great for testing and fine-tuning gameplay. However, it's important to note that any changes made while in Play mode will be lost when you stop the game and return to development mode. 

When you're in development mode, any changes that you make to the variables will be saved by Unity. This means that if you were to quit Unity and then restart it, the changes would be retained.

The changes that you make to values in the Inspector panel do not modify your script, but they will override any values you had assigned in your script when in Play mode.

If you need to undo any changes made in the Inspector panel, you can reset the script to its default (sometimes called initial) values. Click on the three vertical dots icon to the right of any component, and then select Reset, as shown in the following screenshot:

This should give you some peace of mind – if your variables get out of hand, there's always the hard reset. 

A helping hand from MonoBehavior

Since C# scripts are classes, how does Unity know to make some scripts components and not others? The short answer is that LearningCurve (and any script created in Unity) inherits from MonoBehavior (another class). This tells Unity that the C# class can be transformed into a component.

The topic of class inheritance is a bit advanced for this point of your programming journey; think of it as the MonoBehaviour class lending a few of its variables and methods to LearningCurve. Chapter 5, Working with Classes, Struct, and OOP, will cover class inheritance in practical detail.

The Start() and Update() methods that we've used belong to MonoBehavior, which Unity runs automatically on any script attached to a GameObject. The Start() method runs once when the scene starts playing, while the Update() method runs once per frame (depending on the frame rate of your machine). 

Now that you're familiarity with Unity's documentation has gotten a nice bump, I've put together a short optional challenge for you to tackle!

Hero's trial – MonoBehavior in the Scripting API

Now it's time for you to get comfortable using the Unity documentation on your own, and what better way than to look up some of the common MonoBehavior methods:

Try searching for the Start() and Update() methods in the Scripting API to gain a better understanding of what they do in Unity, and when. 

If you're feeling brave, go the extra step and have a look at the MonoBehavior class in the manual for a more detailed explanation.

Before jumping into our C# programming adventure too deeply, we need to address one final, crucial building block – communication between classes. 

Communication among classes

Up until now, we've described classes and, by extension, Unity components, as separate standalone entities; in reality, they are deeply intertwined. You'd be hard-pressed to create any kind of meaningful software application without invoking some kind of interaction or communication between classes.

If you remember the post-office example from earlier, the example code made use of periods (or dots) to reference classes, variables, and methods. If you think of classes as directories of information, then dot notation is the indexing tool: 

PostOffice.Address

Any variables, methods, or other data types within a class can be accessed with dot notation. This applies to nested, or subclass information as well, but we'll tackle all those subjects when we get to Chapter 5, Working with Classes and Object-Oriented Programming. 

Dot notation is also what drives communication between classes. Whenever a class needs information about another class or wants to execute one of its methods, dot notation is used:

PostOffice.DeliverMail()

Dot notation is sometimes referred to as (.) Operator, so don't be thrown off if you see it mentioned this way in the documentation.

If dot notation doesn't quite click with you yet, don't worry, it will. It's the bloodstream of the entire programming body, carrying information and context wherever it's needed.

Summary

We've come a long way in a few short pages, but understanding the overarching theory of fundamental concepts such as variables, methods, and classes will give you a strong foundation to build on. Bear in mind that these building blocks have very real counterparts in the real world. Variables hold values like mailboxes hold letters; methods store instructions like recipes, to be followed for a predefined result; and classes are blueprints just like real blueprints. You can't build a house without a well thought out design to follow if you expect it to stay standing.

The rest of this book will take you on a deep dive into C# syntax from scratch, starting with more detail in the next chapter on how to create variables, manage value types, and work with simple and complex methods.

Pop quiz – C# building blocks

What is the main purpose of a variable?

What role do methods play in scripts?

How does a script become a component?

What's the purpose of dot notation?

Diving into Variables, Types, and Methods

The initial steps into any programming language are plagued with a fundamental issue – you can understand the words being typed out, but not the meaning behind them. Normally, this would be cause for a paradox, but programming is a special case. 

C# is not its own language; it's written in English. The discrepancy between the words you use every day and the code in Visual Studio comes from missing context, which is something that has to be learned all over again. You know how to say and spell the words used in C#, but what you don't know is where, when, why, and, most importantly, how they make up the syntax of the language. 

This chapter marks our departure from programming theory and the beginning of our journey into actual coding. We'll talk about accepted formatting, debugging techniques, and putting together more complex examples of variables and methods. There's a lot of ground to cover, but by the time you reach the last quiz, you'll be comfortable with the following high-level topics:

Writing proper C# 

Debugging your code

Declaring variables

Using access modifiers

Understanding variable scope

Working with methods

Dissecting common Unity methods

Let's get started!

Writing proper C#

Lines of code function like sentences, meaning they need to have some sort of separating or ending character. Every line of C#, called a statement, MUST end with a semicolon to separate them for the code compiler to process.

However, there's a catch that you need to be aware of. Unlike the written word we're all familiar with, a C# statement doesn't technically have to be on a single line; whitespace and newlines are ignored by the code compiler. For example, a simple variable could be written like this:

public int firstName = "Harrison";

Alternatively, it could also be written as follows:

public
int
firstName
=
"Harrison";

These two code snippets are both perfectly acceptable to Visual Studio, but the second option is highly discouraged in the software community as it makes code extremely hard to read. The idea is to write your programs as efficiently and clearly as possible.

There will be times when a statement will be too long to reasonably fit on a single line, but those are few and far between. Just make sure that it's formatted in a way someone else could understand, and don't forget the semicolon.

The second formatting rule you need to drill into your coding muscle memory is the use of curly brackets or braces. Methods, classes, and interfaces all need a set of curly brackets after their declaration. We'll talk about each of these in-depth later on, but it's important to get the standard formatting in your head early on. The traditional practice in C# is to include each bracket on a new line, as shown here:

public void MethodName()
{

}

However, when you create a new script from the Unity Editor or go online to the Unity documentation, you'll see the first curly bracket located on the same line as the declaration:

public void MethodName() {

}

While this isn't something to tear your hair out over, the important thing is to be consistent. "Pure" C# code will always put each bracket on a new line, while C# examples that have to do with Unity and game development will most often follow the second example.

Good, consistent formatting style is paramount when starting in programming, but so is being able to see the fruits of your work. In the next section, we'll talk about how to print out variables and information straight to the Unity console.

Debugging your code

While we're working through practical examples, we'll need a way to print out information and feedback to the Console window in the Unity editor. The programmatic term for this is debugging, and both C# and Unity provide helper methods to make this process easier for developers. Whenever I ask you to debug or print something out, please use one of the following methods:

For simple text or individual variables, use the standard Debug.Log() method. The text needs to be inside a set of parentheses, and variables can be used directly with no added characters; for example:

Debug.Log("Text goes here.");
Debug.Log(yourVariable);

For more complex debugging, use Debug.LogFormat(). This will let you place variables inside the printed text by using placeholders. These are marked with a pair of curly brackets, each containing an index. An index is a regular number, starting at 0 and increasing sequentially by 1.

In the following example, the {0} placeholder is replaced with the variable1 value, {1} with variable2, and so on:

Debug.LogFormat("Text goes here, add {0} and {1} as variable
   placeholders", variable1, variable2);

You might have noticed that we're using dot notation in our debugging techniques, and you'd be right! Debug is the class we're using, and Log() and LogFormat() are different methods that we can use from that class. More on this at the end of this chapter.

With the power of debugging under our belts, we can safely move on and do a deeper dive into how variables are declared, as well as the different ways that syntax can play out.

Declaring variables

In the previous chapter, we saw how variables are written and touched on the high-level functionality that they provide. However, we're still missing the syntax that makes all of that possible. Variables don't just appear at the top of a C# script; they have to be declared according to certain rules and requirements. At its most basic level, a variable statement needs to satisfy the following requirements:

The type of data the variable will store needs to be specified.

The variable has to have a unique name.

If there is an assigned value, it must match the specified type.

The variable declaration needs to end with a semicolon.

The result of adhering to these rules is the following syntax:

dataType uniqueName = value;

Variables need unique names to avoid conflicts with words that have already been taken by C#, which are called keywords. You can find the full list of protected keywords at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/index.

This is simple, neat, and efficient. However, a programming language wouldn't be useful in the long run if there was only one way of creating something as pervasive as variables. Complex applications and games have different use cases and scenarios, all of which have unique C# syntax.

Type and value declarations

The most common scenario for creating variables is one that has all of the required information available when the declaration is made. For instance, if we knew a player's age, storing it would be as easy as doing the following:

int currentAge = 32;

Here, all of the basic requirements have been met:

A data type is specified, which is int (short for integer).

A unique name is used, which is currentAge.

32 is an integer, which matches the specified data type.

The statement ends with a semicolon.

However, there will be scenarios where you'll want to declare a variable without knowing its value right away. We'll talk about this topic in the following section.

Type-only declarations

Consider another scenario: you know the type of data you want a variable to store, as well as its name, but not its value. The value will be computed and assigned somewhere else, but you still need to declare the variable at the top of the script.

This situation is perfect for a type-only declaration:

int currentAge;

Only the type (int) and unique name (currentAge) are defined, but the statement is still valid because we've followed the rules. With no assigned value, default values will be assigned according to the variable's type. In this case, currentAge will be set to 0, which matches the int type. Whenever the actual value is available, it can easily be set in a separate statement by referencing the variable name and assigning it a value:

currentAge = 32;

You can find a complete list of all C# types and their default values at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/default-values.

At this point, you might be asking why, so far, our variables haven't included the public keyword, called an access modifier, which we saw in earlier scripting examples. The answer is that we didn't have the necessary foundation to talk about them with any clarity. Now that we have that foundation, it's time to revisit them in detail.

Using access modifiers

Now that the basic syntax is no longer a mystery, let's get into the finer details of variable statements. Since we read code from left to right, it makes sense to begin our variable deep-dive with the keyword that traditionally comes first – an access modifier.

Take a quick look back at the variables we used in the preceding chapter in LearningCurve and you'll see they had an extra keyword at the front of their statements: public. This is the variable's access modifier. Think of it as a security setting, determining who and what can access the variable's information.

Any variable that isn't marked public is defaulted to private and won't show up in the Unity Inspector panel.

If you include a modifier, the updated syntax recipe we put together at the beginning of this chapter will look like this:

accessModifier dataType uniqueName = value;

While explicit access modifiers aren't necessary when declaring a variable, it's a good habit to get into as a new programmer. That extra word goes a long way toward readability and professionalism in your code.

Choosing a security level

There are four main access modifiers available in C#, but the two you'll be working with most often as a beginner are the following:

Public: This is available to any script without restriction.

Private: This is only available in the class they're created in (which is called the containing class). Any variable without an access modifier defaults to private.

The two advanced modifiers have the following characteristics:

Protected: Accessible from their containing class or types derived from it

Internal: Only available in the current assembly

There are specific use cases for each of these modifiers, but until we get to the advanced chapters, don't worry about protected and internal.

Two combined modifiers also exist, but we won't be using them in this book. You can find more information about them at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/access-modifiers.

Let's try out some access modifiers of our own!

Time for action – making a variable private

Just like information in real life, some data needs to be protected or shared with specific people. If there's no need for a variable to be changed in the Inspector window or accessed from other scripts, it's a good candidate for a private access modifier.

Perform the following steps to update LearningCurve:

Change the access modifier in front of currentAge from public to private and save the file.

Go back into Unity, select the Main Camera, and take a look at what changed in the LearningCurve section.

Since currentAge is now private, it's no longer visible in the Inspector window and can only be accessed within the LearningCurve script. If we click Play, the script will still work exactly as it did before:

This is a good start on our journey into variables, but we still need to know more about what kinds of data they can store. This is where data types come in, which we'll look at in the next section.

Working with types

Assigning a specific type to a variable is an important choice, one that trickles down into every interaction a variable has over its entire lifespan. Since C# is what's called a strongly-typed or type-safe language, every variable has to have a data type without exception. This means that there are specific rules when it comes to performing operations with certain types, and regulations when converting a given variable type into another. 

Common built-in types

All data types in C# trickle down (or derive, in programmatic terms) from a common ancestor: System.Object. This hierarchy, called the Common Type System (CTS), means that different types have a lot of shared functionality. The following table lays out some of the most common data type options and the values they store:

In addition to specifying the kind of value a variable can store, types contain added information about themselves, including the following:

Required storage space

Minimum and maximum values

Allowed operations

Location in memory

Accessible methods 

Base (derived) type

If this seems overwhelming, take a deep breath. Working with all of the types C# offers is a perfect example of using documentation over memorization. Pretty soon, using even the most complex custom types will feel like second nature.

You can find a complete list of all of the C# built-in types and their specifications at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/index.

Before the list of types becomes a sticking point, it's best to experiment with them. After all, the best way to learn something new is to use it, break it, and then learn to fix it.

Time for action – playing with different types

Go ahead and open up LearningCurve and add a new variable for each type in the preceding chart from the Common built-in types section. The names and values you use are up to you; just make sure they're marked as public so we can see them in the Inspector window. If you need inspiration, take a look at my code, which is shown in the following screenshot:

When dealing with string types, the actual text value needs to be inside a pair of double quotes, while float values need to end with a lowercase f, as you can see with pi and firstName.

All our different variable types are now visible. Take note of the bool variable that Unity displays as a checkbox (true is checked and false is unchecked):

Before we move on to conversions, we need to touch on a common and powerful application of the string data type; namely, the creation of strings that have variables interspersed at will.

Time for action – creating interpolated strings

While number types behave as you'd expect from grade school math, strings are a different story. It's possible to insert variables and literal values directly into text by starting with a $ character, which is called string interpolation. The interpolated values are added inside curly brackets, just like using the LogFormat() method. Let's create a simple interpolated string of our own inside LearningCurve to see this in action:

Print out the interpolated string inside the Start() method directly after ComputeAge() is called:

Thanks to the curly brackets, the value of firstName is treated as a value and is printed out inside the interpolated string:

It's also possible to create interpolated strings using the + operator, which we'll get to right after we talk about type conversions.

Type conversions

We've already seen that variables can only hold values of their declared types, but there will be situations where you'll need to combine variables of different types. In programming terminology, these are called conversions, and they come in two main flavors:

Implicit conversions take place automatically, usually when a smaller value will fit into another variable type without any rounding. For example, any integer can be implicitly converted into a double or float without additional code:

float implicitConversion = 3;

Explicit conversions are needed when there is a risk of losing a variable's information during the conversion. For example, if we wanted to convert a double into an int, we would have to explicitly cast (convert) it by adding the destination type in parentheses before the value we want to convert. This tells the compiler that we are aware that data (or precision) might be lost. 

In this explicit conversion, 3.14 would be rounded down to 3, losing the decimal values:

int explicitConversion = (int)3.14;

C# provides built-in methods for explicitly converting values to common types. For example, any type can be converted into a string value with the ToString() method, while the Convert class can handle more complicated conversions. You can find more info about these features under the Methods section at https://docs.microsoft.com/en-us/dotnet/api/system.convert?view=netframework-4.7.2. 

So far, we've learned that types have rules regarding their interactions, operations, and conversion, but how do we handle a situation where we need to store a variable of an unknown type? This might sound crazy, but think about a data-download scenario – you know the information is coming into your game, but you're not sure what form it will take. We'll discuss how to handle this in the following section.

Inferred declarations

Luckily, C# can infer a variable's type from its assigned value. For example, the var keyword can let the program know that the type of the data, currentAge, needs to be determined by its value of 32, which is an integer:

var currentAge = 32;

While this is handy in certain situations, don't be suckered into the lazy programming habit of using inferred variable declarations for everything. This adds a lot of guesswork to your code, where it should be crystal clear.

Before we wrap up our discussion on data types and conversion, we do need to briefly touch on the idea of creating custom types, which we'll do next.

Custom types

When we're talking about data types, it's important to understand early on that numbers and words (referred to as literal values) are not the only kinds of values a variable can store. For instance, a class, struct, or enumeration can be stored as variables. We will introduce these topics in Chapter 5, Working with Classes and OOP, and explore them in greater detail in Chapter 10, Revisiting Types, Methods, and Classes.

Types roundup

Types are complicated, and the only way to get comfortable with them is by using them. However, here are some important things to keep in mind:

All variables need to have a specified type (be it explicit or inferred).

Variables can only hold values of their assigned type (string can't be assigned to int).

Each type has a set of operations that it can and can't apply (bool can't be subtracted from another value).

If a variable needs to be assigned or combined with a variable of a different type, a conversion needs to take place (either implicit or explicit).

The C# compiler can infer a variable's type from its value using the var keyword, but should only be used when the type isn't known when it's created.

That's a lot of nitty-gritty detail we've just jammed into a few sections, but we're not done yet. We still need to understand how naming conventions work in C#, as well as where the variables live in our scripts.

Naming variables

Picking names for your variables might seem like an afterthought in light of everything we've learned about access modifiers and types, but it shouldn't be a trivial choice. Clear and consistent naming conventions in your code will not only make it more readable but will also ensure that other developers on your team understand your intentions without having to ask.

Best practices

The first rule when it comes to naming a variable is that the name you give it should be meaningful; the second rule is that you use camel case. Let's take a common example from games and declare a variable to store a player's health:

public int health = 100;

If you find yourself declaring a variable like this, alarm bells should be going off in your head. Whose health? Is it storing the maximum or minimum value? What other code will be affected when this value changes? These are all questions that should be easily answered by a meaningful variable name; you don't want to find yourself confused by your code in a week or a month.

With that said, let's try to make this a bit better using a camel case name:

public int maxHealth = 100;

Remember, camel casing starts the variable's name with a lowercase letter, then capitalizes the first letter in each word thereafter. It also makes a clear distinction between variable and class names, which start with an uppercase letter.

That's much better. With a little thought, we've updated the variable name with meaning and context. Since there is no technical limit in terms of how long a variable name can be, you might find yourself going overboard and writing out ridiculously descriptive names, which will give you problems just as much as a short, non-descriptive name would. 

As a general rule, make a variable name as descriptive as it needs to be – no more, no less. Find your style and stick to it.

Understanding variable scope

We're getting to the end of our dive into variables, but there's still one more important topic we need to cover: scope. Similar to access modifiers, which determine which outside classes can grab a variable's information, the variable scope is the term used to describe where a given variable exists and its access point within its containing class. 

There are three main levels of variable scope in C#:

Global scope refers to a variable that can be accessed by an entire program; in this case, a game. C# doesn't directly support global variables, but the concept is useful in certain cases, which we'll cover in Chapter 10, Revisiting Types, Methods, and Classes.

Class or member scope refers to a variable that is accessible anywhere in its containing class.

Local scope refers to a variable that is only accessible inside the specific block of code it's created in.

Take a look at the following screenshot. You don't need to put this into LearningCurve if you don't want to; it's only for visualization purposes at this point:

When we talk about code blocks, we're referring to the area inside any set of curly brackets. These brackets serve as a kind of visual hierarchy in programming; the farther right-indented they are, the deeper they are nested in the class.

Let's break down the class and local scope variables in the preceding screenshot:

characterClass is declared at the very top of the class, which means we can reference it by name anywhere inside LearningCurve. You might hear this concept referred to as variable visibility, which is a good way of thinking about it.

characterHealth is declared inside the Start() method, which means it is only visible inside that block of code. We can still access characterClass from Start() with no issue, but if we attempted to access characterHealth from anywhere but Start(), we would get an error. 

characterName is in the same boat as characterHealth; it can only be accessed from the CreateCharacter() method. This was just to illustrate that there can be multiple, even nested, local scopes in a single class.

If you spend enough time around programmers, you'll hear discussions (or arguments, depending on the time of day) about the best place to declare a variable. The answer is simpler than you might think: variables should be declared with their use in mind. If you have a variable that needs to be accessed throughout a class, make it a class variable. If you only need a variable in a specific section of code, declare it as a local variable.

Note that only class variables can be viewed in the Inspector window, which isn't an option for local or global variables.

With naming and scope in our toolbox, let's transport ourselves back to middle school math class and relearn how arithmetic operations work all over again!

Introducing operators

Operator symbols in programming languages represent the arithmetic, assignment, relational, and logical functionality that types can perform. Arithmetic operators represent basic math functions, while assignment operators perform math and assignment functions together on a given value. Relational and logical operators evaluate conditions between multiple values, such as greater than, less than, and equal to.

C# also offers bitwise and miscellaneous operators, but these won't come into play for you until you're well on your way to creating more complex applications. 

At this point, it only makes sense to cover arithmetic and assignment operators, but we'll get to relational and logical functionality when it becomes relevant in the next chapter.

Arithmetic and assignments

You're already familiar with the arithmetic operator symbols from school:

+ for addition

- for subtraction

/ for division

* for multiplication

C# operators follow the conventional order of operations, that is, evaluating parentheses first, then exponents, then multiplication, then division, then addition, and finally subtraction (BEDMAS). For instance, the following equations will provide different results, even though they contain the same values and operators:

5 + 4 - 3 / 2 * 1 = 8
5 + (4 - 3) / 2 * 1 = 5

Operators work the same when applied to variables, as they do with literal values. 

Assignment operators can be used as a shorthand replacement for any math operation by using any arithmetic and equals symbol together. For example, if we wanted to multiply a variable, both of the following options would produce the same result:

int currentAge = 32;
currentAge = currentAge * 2;

The second, alternative, way to do this is shown here:

int currentAge = 32;
currentAge *= 2;

The equals symbol is also considered an assignment operator in C#. The other assignment symbols follow the same syntax pattern as our preceding multiplication example: +=, -=, and /= for add and assign, subtract and assign, and divide and assign, respectively.

Strings are a special case when it comes to operators, as they can use the addition symbol to create patchwork text, as follows:

string fullName = "Joe" + "Smith";

This approach tends to produce clunky code, making string interpolation the preferred method for putting together different bits of text in most cases.

With this, we've learned that types have rules that govern what kind of operations and interactions they can have. However, we haven't seen that in practice, so we'll give it a shot in the next section.

Time for action – executing incorrect type operations

Let's do a little experiment: we'll try to multiply our string and float variables together, as we did earlier with our numbers:

If you look in the Console window, you'll see that we've got an error message letting us know that a string and a float can't be added. Whenever you see this type of error, go back and inspect your variable types for incompatibilities:

It's important that we clean up this example, as the compiler won't allow us to run our game at this point. Choose between a pair of backslashes (//) at the beginning of Debug.Log() on line 21, or delete it altogether.

That's as far as we need to go in terms of variables and types for the moment. Be sure to test yourself on this chapter's quiz before moving on!

Defining methods

In the previous chapter, we briefly touched on the role methods play in our programs; namely, that they store and execute instructions, just like variables store values. Now, we need to understand the syntax of method declarations and how they drive action and behavior in our classes.

Basic syntax

As with variables, method declarations have their basic requirements, which are as follows:

The type of data that will be returned by the method 

A unique name, starting with a capital letter

A pair of parentheses following the method name 

A pair of curly brackets marking the method body (where instructions are stored)

Putting all of these rules together, we get a simple method blueprint:

returnType UniqueName()
{
    method body
}

Let's break down the default Start() method in LearningCurve as a practical example:

In the preceding output, we can see the following:

The method starts with the void keyword, which is used as the method's return type if it doesn't return any data.

The method has a unique name.

The method has a pair of parentheses after its name to hold any potential parameters.

The method body is defined by a set of curly brackets.

In general, if you have a method that has an empty method body, it's good practice to delete it from the class. You always want to be pruning your scripts of unused code.

Like variables, methods can also have security levels. However, they can also have input parameters, both of which we'll be discussing next!

Modifiers and parameters

Methods can also have the same four access modifiers that are available to variables, as well as input parameters. Parameters are variable placeholders that can be passed into methods and accessed inside them. The number of input parameters you can use isn't limited, but each one needs to be separated by a comma, show its data type, and have a unique name.

Think of method parameters like variable placeholders whose values can be used inside the method body.

If we apply these options, our updated blueprint will look like this:

accessModifier returnType UniqueName(parameterType parameterName)
{
    method body
}

If there is no explicit access modifier, the method defaults to private. A private method, like a private variable, cannot be called from other scripts.

To call a method (meaning to run or execute its instructions), we simply use its name, followed by a pair of parentheses, with or without parameters, and cap it off with a semicolon:

// Without parameters
UniqueName();

// With parameters
UniqueName(parameterVariable);

Like variables, every method has a fingerprint that describes its access level, return type, and parameters. This is called its method signature. Essentially, a method's signature marks it as unique to the compiler so Visual Studio knows what to do with it.

Now that we understand how methods are structured, let's create one of our own.

Time for action – defining a simple method

One of the Time for action segments in the previous chapter had you blindly copy a method called AddNumbers into LearningCurve without you knowing what you were getting into. This time, let's purposefully create a method:

Declare a public method with a void return type called GenerateCharacter().

Add a simple Debug.Log() that prints out a character name from your favorite game or movie.

Call GenerateCharacter() inside the Start() method and hit Play:

When the game starts up, Unity automatically calls Start(), which, in turn, calls our GenerateCharacter() method and prints it to the Console window.

If you read enough documentation, you'll see different terminology related to methods. Throughout the rest of this book, when a method is created or declared, I'll refer to this as defining a method. Similarly, I'll refer to running or executing a method as calling that method.

The power of naming is integral to the entirety of the programming landscape, so it shouldn't be a surprise that we're going to revisit naming conventions for methods before moving on.

Naming conventions

Like variables, methods need unique, meaningful names to distinguish them in code. Methods drive actions, so it's a good practice to name them with that in mind. For example, GenerateCharacter() sounds like a command, which reads well when you call it in a script, whereas a name such as Summary() is bland and doesn't paint a very clear picture of what the method will accomplish. 

Methods always start with an uppercase letter, followed by capitalizing the first letter in any subsequent words. This is called PascalCase (a step-sibling of the CamelCase format we use with variables). 

Methods are logic detours

We've seen that lines of code execute sequentially in the order they're written, but bringing methods into the picture introduces a unique situation. Calling a method tells the program to take a detour into the method instructions, run them one by one, and then resume sequential execution where the method was called. 

Take a look at the following screenshot and see whether you can figure out in what order the debug logs will be printed out to the console:

These are the steps that occur:

Choose a character prints out first because it's the first line of code.

When GenerateCharacter() is called, the program jumps to line 23, prints out Character: Spike, then resumes execution at line 17.

A fine choice prints out last, after all the lines in GenerateCharacter() have finished running:

Now, methods in themselves wouldn't be very useful beyond simple examples like these if we couldn't add parameter values to them, which is what we'll do next.

Specifying parameters

Chances are your methods aren't always going to be as simple as GenerateCharacter(). To pass in additional information, we'll need to define parameters that our method can accept and work with. Every method parameter is an instruction and needs to have two things:

An explicit type

A unique name

Does this sound familiar? Method parameters are essentially stripped-down variable declarations and perform the same function. Each parameter acts like a local variable, only accessible inside their specific method.

You can have as many parameters as you need. Whether you're writing custom methods or using built-in ones, the parameters that are defined are what the method requires to perform its specified task.

If parameters are the blueprint for the types of values a method can accept, then arguments are the values themselves. To break this down further, consider the following:

The argument that's passed into a method needs to match the parameter type, just like a variable and its value.

Arguments can be literal values (for instance, the number 2) or variables declared elsewhere in the class.

Argument names and parameter names don't need to match to compile. 

Now, let's move on and add some method parameters to make GenerateCharacter() a bit more interesting.

Time for action – adding method parameters

Let's update GenerateCharacter() so that it can take in two parameters:

Add two method parameters: one for a character's name of the string type, and another for a character's level of the int type.

Update Debug.Log() so that it uses these new parameters.

Update the GenerateCharacter() method call in Start() with your arguments, which can be either literal values or declared variables:

Here, we defined two parameters, name (string) and level (int), and used them inside the GenerateCharacter() method, just like local variables. When we called the method inside Start(), we added argument values for each parameter with corresponding types. In the preceding screenshot, you can see that using the literal string value in quotations produced the same result as using characterLevel:

Going even further with methods, you might be wondering how we can pass values from inside the method and back out again. This brings us to our next section on return values.

Specifying return values

Aside from accepting parameters, methods can return values of any C# type. All of our previous examples have used the void type, which doesn't return anything, but being able to write instructions and pass back computed results is where methods shine. 

According to our blueprints, method return types are specified after the access modifier. In addition to the type, the method needs to contain the return keyword, followed by the return value. A return value can be a variable, a literal value, or even an expression, as long as it matches the declared return type.

Methods that have a return type of void can still use the return keyword with no value or expression assigned. Once the line with the return keyword is reached, the method will stop executing. This is useful in cases where you want to avoid certain behaviors or guard against program crashes.

Let's add a return type to GenerateCharacter() and learn how to capture it in a variable.

Time for action – adding a return type

 Let's update the GenerateCharacter method so that it returns an integer:

Change the return type in the method declaration from void to int.

Set the return value to level + 5 using the return keyword:

GenerateCharacter() will now return an integer. This is computed by adding 5 to the level argument. We haven't specified how, or if, we want to use this return value, which means that right now, the script won't do anything new. 

Now, the question becomes: how do we capture and use the newly added return value? Well, we'll discuss that very topic in the following section.

Using return values

When it comes to using return values, there are two approaches available:

Create a local variable to capture (store) the returned value.

Use the calling method itself as a stand-in for the returned value, using it just like a variable. The calling method is the actual line of code that fires the instructions, which in our example would be GenerateCharacter("Spike", characterLevel). You can even pass a calling method into another method as an argument if need be.

The first option is preferred in most programming circles for its readability. Throwing around method calls as variables can get messy fast, especially when we use them as arguments in other methods.

Let's give this a try in our code by capturing and debugging the return value that GenerateCharacter() returns.

Time for action – capturing return values 

We're going to use both ways of capturing and using return variables with two simple debug logs:

Create a new local variable of the int type, called nextSkillLevel, and assign it to the return value of the GenerateCharacter() method call we already have in place.

Add two debug logs, with the first printing out nextSkillLevel and the second printing out a new calling method with argument values of your choice.

Comment out the debug log inside GenerateCharacter() with two backslashes (//) to make the console output less cluttered.

Save the file and hit Play in Unity:

To the compiler, nextSkillLevel and the GenerateCharacter() method caller represent the same information, namely an integer, which is why both logs show the number 37:

That was a lot to take in, especially given the exponential possibilities of methods with parameters and return values. However, we'll ease off the throttle here for a minute and consider some of Unity's most common methods to catch a little breathing room.

Hero's trial – methods as arguments

If you're feeling brave, why not try creating a new method that takes in an int parameter and simply prints it out to the console? No return type necessary. When you've got that, call the method in Start, pass in a GenerateCharacter method call as its argument, and take a look at the output.

Dissecting common Unity methods

We're now at a point where we can realistically discuss the most common default methods that come with any new Unity C# script: Start() and Update(). Unlike the methods we define ourselves, methods belonging to the MonoBehaviour class are called automatically by the Unity engine according to their respective rules. In most cases, it's important to have at least one MonoBehaviour method in a script to kick off your code. 

You can find a complete list of all available MonoBehaviour methods and their descriptions at https://docs.unity3d.com/ScriptReference/MonoBehaviour.html.

Just like stories, it's always a good idea to start at the beginning. So, naturally, we should take a look at every Unity script's first default method – Start().

The Start method

Unity calls this method on the first frame where a script is enabled. Since MonoBehaviour scripts are almost always attached to GameObjects in a scene, their attached scripts are enabled at the same time they are loaded when you hit Play. In our project, LearningCurve is attached to the Main Camera GameObject, which means that its Start() method runs when the Main Camera is loaded into the scene. Start() is primarily used to set up variables or perform logic that needs to happen before Update() runs for the first time. 

The examples we've worked on so far have all used Start(), even though they weren't performing setup actions, which isn't normally the way it would be used. However, it only fires once, making it an excellent tool to use for displaying one-time-only information on the console.

Other than Start(), there's one other major Unity method that you'll run into by default: Update(). Let's familiarize ourselves with how it works in the following section before we finish off this chapter.

The Update method

If you spend enough time looking at the sample code in the Unity Scripting Reference, you'll notice that a vast majority of the code is executed using the Update() method. As your game runs, the Scene window is displayed many times per second, which is called the frame rate or frames per second (FPS). After each frame is displayed, the Update() method is called by Unity, making it one of the most executed methods in your game. This makes it ideal for detecting mouse and keyboard input or running gameplay logic.

If you're curious about the FPS rating on your machine, hit Play in Unity and click the Stats tab in the upper-right corner of the Game view:

You'll be using the Start() and Update() methods in the lion's share of your beginning C# scripts, so get acquainted with them. That being said, you've reached the end of this chapter with a pocketful of the most fundamental building blocks programming has to offer.

Summary

This chapter has been a fast descent from the basic theory of programming and its building blocks into the strata of real code and C# syntax. We've seen good and bad forms of code formatting, learned how to debug information in the Unity console, and created our first variables. C# types, access modifiers, and variable scope weren't far behind, as we worked with member variables in the Inspector window and started venturing into the realm of methods and actions. 

Methods helped us to understand written instructions in code, but more importantly, how to properly harness their power into useful behaviors. Input parameters, return types, and method signatures are all important topics, but the real gift they offer is the potential for new kinds of actions to be performed. You're now armed with the two fundamental building blocks of programming; almost everything you'll do from now on will be an extension or application of these two concepts. 

In the next chapter, we'll take a look at a special subset of C# types called collections, which can store groups of related data, and how to write decision-based code.

Pop quiz – variables and methods

What is the proper way to write a variable name in C#?

How do you make a variable appear in Unity's Inspector window?

What are the four access modifiers available in C#?

When are explicit conversions needed between types?

What are the minimum requirements for defining a method?

What is the purpose of the parentheses at the end of the method name?

What does a return type of void mean in a method definition?

How often is the Update() method called by Unity?

Control Flow and Collection Types

One of the central duties of a computer is to control what happens when predetermined conditions are met. When you click on a folder, you expect it to open; when you type on the keyboard, you expect the text to mirror your keystrokes. Writing code for applications or games is no different—they both need to behave in a certain way in one state, and in another when conditions change. In programming terms, this is called control flow, which is apt because it's controlling the flow of how code is executed in different scenarios. 

In addition to working with control statements, we'll be taking a hands-on look at collection data types. Collections are a category of types that allow multiple values, and groupings of values, to be stored in a single variable. These go hand in hand with many of the control-flow scenarios you'll commonly run into, so, naturally, we discuss them by focusing on the following topics:

Selection statements

Working with Array, Dictionary, and List collections

Iteration statements with for, foreach, while, and do-while loops

Execution control with the break, continue, and return keywords

Fixing infinite loops

Selection statements

The most complex programming problems can often be boiled down to sets of simple choices that a game or program evaluates and acts on. Since Visual Studio and Unity can't make those choices by themselves, writing out those decisions is up to us.

The if-else and switch selection statements allow you to specify branching paths, based on one or more conditions, and the actions you want to be taken in each case. Traditionally, these conditions include the following:

Detecting user input

Evaluating expressions and Boolean logic

Comparing variables or literal values

You're going to start with the simplest of these conditional statements, if-else, in the following section.

The if-else statement

if-else statements are the most common way of making decisions in code. When stripped of all its syntax, the basic idea is, If my condition is met, execute this block of code; if it's not, execute this other block of code. Think of these statements as gates, or doors, with the conditions as their keys. To pass through, the key needs to be valid. Otherwise, entry will be denied and the code will be sent to the next possible gate. Let's take a look at the syntax for declaring one of these gates.

Basic syntax

A valid if-else statement requires the following:

The if keyword at the beginning of the line 

A pair of parentheses to hold the condition

A statement body:

if(condition is true)
 Execute this line of code

However, if the statement body is more than a single line, it needs to have a pair of curly brackets to hold the bigger code block:

if(condition is true)
{
    Execute multiple lines
    of code
}

Optionally, an else statement can be added to store the action you want to take when the if statement condition fails. The same rules apply for the else statement:

else
    Execute single line of code

// OR

else
{
    Execute multiple lines
    of code
}

In blueprint form, the syntax almost reads like a sentence:

if(condition is true)
{
    Execute this code
    block
}
else
{
    Execute this code
    block
}

Since these are great introductions to logical thinking, at least in programming, we'll break down the three different if-else variations in more detail:

A single if statement can exist by itself in cases where you don't care about what happens if the condition isn't met. In the following example, if hasDungeonKey is set to true, then a debug log will print out; if set to false, no code will execute:

When referring to a condition as being met, I mean that it evaluates to true, which is often referred to as a passing condition.

Add an else statement in cases where an action needs to be taken whether the condition is true or false. If hasDungeonKey were false, the if statement would fail and the code execution would jump to the else statement: 

For cases where you need to have more than two possible outcomes, add an else-if statement with its parentheses, conditions, and curly brackets. This is best shown rather than explained, which we'll do next.

Keep in mind that if statements can be used by themselves, but the other statements cannot exist on their own.

You can also create more complex conditions with basic math operations, such as > (greater than), < (less than), >= (greater than or equal to), <= (less than or equal to), and == (equivalent). 

For example, a condition of (2 > 3) will return false and fail, while a condition of (2 < 3) will return true and pass. 

Don't worry too much about anything beyond this right now; you'll get your hands on this stuff soon enough.

Time for action – thieving prospects

Let's write out an if-else statement that checks the amount of money in a character's pocket, returning different debug logs for three different cases: greater than 50, less than 15, and anything else:

Open up LearningCurve and add a new int variable, named currentGold. Set its value to between 1 and 100.

Declare an if statement to check whether currentGold is greater than 50, and print a message to the console if this is true.

Add an else-if statement to check whether currentGold is less than 15 with a different debug log.

Add an else statement with no condition and a final default log.

Save the file and click on Play:

With currentGold set to 32 in my example, we can break down the code sequence as follows:

The if statement and debug log is skipped because currentGold is not greater than 50.

The else-if statement and debug log is also skipped because currentGold is not less than 15.

Since neither of the previous conditions was met, the else statement executes and the third debug log is displayed:

After trying out some other values for currentGold on your own, let's discuss what happens if we want to test a failing condition.

Using the NOT operator

Use cases won't always require checking for a positive, or true, condition, which is where the NOT operator comes in. Written with a single exclamation point, the NOT operator allows for negative, or false, conditions to be met by if or else-if statements. This means that the following conditions are the same:

if(variable == false)

// AND

if(!variable)

As you already know, you can check for Boolean values, literal values, or expressions in an if condition. So, naturally, the NOT operator has to be adaptable. Take a look at the following example of two different negative values, hasDungeonKey and weaponType, used in an if statement:

We can evaluate each statement as follows:

The first statement can be translated to, "If hasDungeonKey is false, the if statement evaluates to true and executes its code block."

If you're asking yourself how a false value can evaluate to true, think of it this way: the if statement is not checking whether the value is true, but that the expression itself is true. hasDungeonKey might be set to false, but that's what we're checking for, so it's true in the context of the if condition.

The second statement can be translated to, "If the string value of weaponType is not equal to Longsword, then execute this code block." 

You can see the debug results in the following screenshot. However, if you're still fuzzy, copy the preceding code into LearningCurve and play around with the variable values until it clicks:

So far, our branching conditions have been fairly simple, but C# also allows conditional statements to be nested inside each other for more complex situations.

Nesting statements

One of the most valuable functions of if-else statements is that they can be nested inside each other, creating complex logic routes through your code. In programming, we call them decision trees. Just like a real hallway, there can be doors behind other doors, creating a labyrinth of possibilities:

Let's break down the preceding example:

First, an if statement checks whether we have weaponEquipped. At this point, the code only cares whether it's true, not what type of weapon it is.

The second if statement checks the weaponType and prints out the associated debug log.

If the first if statement evaluates to false, the code would jump to the else statement and its debug log. If the second if statement evaluates to false, nothing is printed because there is no else statement.

The responsibility of handling logic outcomes is 100% on the programmer. It's up to you to determine the possible branches or outcomes your code can take.

What you've learned so far will get you through simple use cases with no problem. However, you'll quickly find yourself in need of more complex statements, which is where evaluating multiple conditions comes into play. 

Evaluating multiple conditions

In addition to nesting statements, it's also possible to combine multiple condition checks into a single if or else-if statement with AND and OR logic operators:

AND is written with two ampersand characters, &&. Any condition using the AND operator means that all conditions need to evaluate to true for the if statement to execute.

OR is written with two pipe characters, ||. An if statement using the OR operator will execute if one or more of its conditions is true.

In the following example, the if statement has been updated to check for both weaponEquipped and weaponType, both of which need to be true for the code block to execute:

The AND and OR operators can be combined to check multiple conditions in any order. There is also no limit on how many operators you can combine. Just be careful when using them together that you don't create logic conditions that will never execute.

It's time to put everything we've learned so far about if statements to the test. So, review this section if you need to, and then move on to the next section.

Time for action – reaching the treasure

Let's cement this topic with a little treasure chest experiment:

Declare three variables at the top of LearningCurve: pureOfHeart is a bool and should be true; hasSecretIncantation is also a bool and should be false, and rareItem is a string and its value is up to you.

Create a public method with no return value, called OpenTreasureChamber, and call it inside Start.

Inside OpenTreasureChamber, declare an if-else statement to check whether pureOfHeart is true and that rareItem matches the string value you assigned to it.

Create a nested if-else statement inside the first, checking whether hasSecretIncantation is false.

Add debug logs for each if-else case, save, and click on Play:

If you matched the variable values to the preceding screenshot, the nested if statement debug log will be printed out. This means that our code got past the first if statement checking for two conditions, but failed the third:

Now, you could stop here and use even bigger if-else statements for all your conditional needs, but that's not going to be efficient in the long run. Good programming is about using the right tool for the right job, which is where the switch statement comes in.

The switch statement

if-else statements are a great way to write decision logic. However, when you have more than three or four branching actions, they just aren't feasible. Before you know it, your code can end up looking like a tangled knot that's hard to follow, and a headache to update. switch statements take in expressions and let us write out actions for each possible outcome, but in a much more concise format than if-else.

Basic syntax

switch statements require the following elements:

The switch keyword followed by a pair of parentheses holding its condition

A pair of curly brackets

A case statement for each possible path ending with a colon:

Individual lines of code or methods, followed by the break keyword and a semicolon

A default case statement ending with a colon:

Individual lines of code or methods, followed by the break keyword and a semicolon

In blueprint form, it looks like this:

switch(matchExpression)
{
    case matchValue1:
        Executing code block
        break;
    case matchValue2:
        Executing code block
        break;
    default:
        Executing code block
        break;
}

The highlighted keywords in the preceding blueprint are the important bits. When a case statement is defined, anything between its colon and break keyword acts like the code block of an if-else statement. The break keyword just tells the program to exit the switch statement entirely after the selected case fires. Now, let's discuss how the statement determines which case gets executed, which is called pattern matching.

Pattern matching

In switch statements, pattern matching refers to how a match expression is validated against multiple case statements. A match expression can be of any type that isn't null or nothing; all case statement values need to match the type of the match expression.

For example, if we had a switch statement that was evaluating an integer variable, each case statement would need to specify an integer value for it to check against. The case statement with a value that matches the expression is the one that is executed. If no case is matched, the default case fires. Let's try this out for ourselves!

Time for action – choosing an action

That was a lot of new syntax and information, but it helps to see it in action. Let's create a simple switch statement for different actions a character could take:

Create a new string variable (member or local), named characterAction, and set it to Attack.

Declare a switch statement and use characterAction as the match expression.

Create two case statements for Heal and Attack with different debug logs. Don't forget to include the break keyword at the end of each.

Add a default case with a debug log and break.

Save the file and click on Play in Unity:

Since characterAction is set to Attack, the switch statement executes the second case and prints out its debug log. Change characterAction to either Heal or an undefined action to see the first and default cases in action:

There are going to be times where you need several, but not all, switch cases to perform the same action. These are called fall-through cases and are the subject of our next section.

Fall-through cases

Switch statements can execute the same action for multiple cases, similar to how we specified several conditions in a single if statement. The term for this is called fall-through or, sometimes, fall-through cases. If a case block is left empty or has code without a break keyword, it will fall through to the case directly beneath it. 

Cases can be written in any order, so creating fall-through cases greatly increases code readability and efficiency.

Let's add some code to SwitchingAround() so that we can see this in action.

Time for action – rolling the dice

Let's simulate a tabletop game scenario with a switch statement and fall-through case, where a dice roll determines the outcome of a specific action:

Create an int variable, named diceRoll, and assign it a value of 7.

Declare a switch statement with diceRoll as the match expression.

Add three cases for possible dice rolls: 7, 15, and 20. 

Case 15 and 20 should have their own debug logs and break statements, while case 7 should fall through to case 15.

Save the file and run it in Unity:

If you want to see the fall-through case in action, try adding a debug log to case 7, but without the break keyword.

With diceRoll set to 7, the switch will match with the first case, which will fall through and execute case 15 because it lacks a code block and a break statement. If you change diceRoll to 15 or 20, the console will show their respective messages, and any other value will fire off the default case at the end of the statement:

switch statements are extremely powerful and can simplify even the most complex decision logic. If you want to dig deeper into switch pattern matching, refer to https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch. 

That's all we need to know about conditional logic for the moment. So, review this section if you need to, and then test yourself on the following quiz before moving on to collections!

Pop quiz 1 – if, and, or but

Test your knowledge with the following questions:

What values are used to evaluate if statements?

Which operator can turn a true condition false or a false condition true?

If two conditions need to be true for an if statement's code to execute, what logical operator would you use to join the conditions?

If only one of two conditions needs to be true to execute an if statement's code, what logical operator would you use to join the two conditions?

With that done, you're ready to step into the world of collection data types. These types are going to open up a whole new subset of programming functionality for your games and C# programs!

Collections at a glance

So far, we've only needed variables to store a single value, but there are many conditions where a group of values will be required. Collection types in C# include arrays, dictionaries, and lists—each has its strengths and weaknesses, which we'll discuss in the following sections. 

Arrays

Arrays are the most basic collection that C# offers. Think of them as containers for a group of values, called elements in programming terminology, each of which can be accessed or modified individually:

Arrays can store any type of value; all the elements need to be of the same type.

The length, or the number of elements, an array can have is set when it's created, and this can't be modified afterward.

If no initial values are assigned when it's created, each element will be given a default value. Arrays storing number types default to zero, while any other type gets set to null or nothing.

Arrays are the least flexible collection type in C#. This is mainly because elements can't be added or removed after they have been created. However, they are particularly useful when storing information that isn't likely to change. That lack of flexibility makes them faster compared to other collection types.

Basic syntax

Declaring an array is similar to other variable types we've worked with, but has a few modifications:

Array variables require a specified element type, a pair of square brackets, and a unique name.

The new keyword is used to create the array in memory, followed by the value type and another pair of square brackets.

The number of elements the array will store goes inside the second pair of square brackets.

In blueprint form, it looks like this:

elementType[] name = new elementType[numberOfElements];

Let's take an example where we need to store the top three high scores in our game:

int[] topPlayerScores = new int[3];

Broken down, topPlayerScores is an array of integers that will store three integer elements. Since we didn't add any initial values, each of the three values in topPlayerScores is 0.

You can assign values directly to an array when it's created by adding them inside a pair of curly brackets at the end of the variable declaration. C# has a longhand and shorthand way of doing this, but both are equally valid:

// Longhand initializer
int[] topPlayerScores = new int[] {713, 549, 984};

// Shortcut initializer
int[] topPlayerScores = { 713, 549, 984 };

Initializing arrays with the shorthand syntax is very common, so I'll be using it for the rest of the book. However, if you want to remind yourself of the details, feel free to use the explicit wording.

Now that the declaration syntax is no longer a mystery, let's talk about how array elements are stored and accessed.

Indexing and subscripts

Each array element is stored in the order it's assigned, which is referred to as its index. Arrays are zero-indexed, meaning that the element order starts at zero instead of one. Think of an element's index as its reference, or location. In topPlayerScores, the first integer, 452, is located at index 0, 713 at index 1, and 984 at index 2:

Individual values are located by their index using the subscript operator, which is a pair of square brackets that contains the index of the elements. For example, to retrieve and store the second array element in topPlayerScores, we would use the array name followed by subscript brackets and index 1:

// The value of score is set to 713
int score = topPlayerScores[1];

The subscript operator can also be used to directly modify an array value just like any other variable, or even passed around as an expression by itself:

topPlayerScores[1] = 1001;
Debug.Log(topPlayerScores[1]);

The values in topPlayerScores are now 452, 1001, and 984.

Range exceptions

When arrays are created, the number of elements is set and unchangeable, which means we can't access an element that doesn't exist. In the topPlayerScores example, the array length is 3, so the range of valid indices is from 0 to 2. Any index of 3 or higher is out of the array's range and will generate an aptly-named IndexOutOfRangeException in the console:

Good programming habits dictate that we avoid range exceptions by checking whether the value we want is within an array's index range, which we'll cover in the Iteration statements section.

Arrays aren't the only collection types C# has to offer. In the next section, we'll deal with lists, which are more flexible and more common in the programming landscape.

Lists

Lists are closely related to arrays, collecting multiple values of the same type in a single variable. They're much easier to deal with when it comes to adding, removing, and updating elements, but their elements aren't stored sequentially. This can, sometimes, lead to a higher performance cost over arrays. 

Performance cost refers to how much of a computer's time and energy a given operation takes up. Nowadays, computers are fast, but they can still get overloaded with big games or applications.

Basic syntax

A list-type variable needs to meet the following requirements:

The List keyword, its element type inside left and right arrow characters, and a unique name

The new keyword to initialize the list in memory, with the List keyword and element type between arrow characters

A pair of parentheses capped off by a semicolon

In blueprint form, it reads as follows:

List<elementType> name = new List<elementType>();

List length can always be modified, so there is no need to specify how many elements it will eventually hold when created.

Like arrays, lists can be initialized in the variable declaration by adding element values inside a pair of curly brackets:

List<elementType> name = new List<elementType>() { value1, value2 };

Elements are stored in the order they are added, are zero-indexed, and can be accessed using the subscript operator. Let's start setting up a list of our own to test out the basic functionality this class has on offer.

Time for action – party members

Let's do a warm-up exercise by creating a list of party members in a fictional role-playing game:

Create a new List of the string type, called questPartyMembers, and initialize it with the names of three characters.

Add a debug log to print out the number of party members in the list using the Count method.

Save the file and Play it in Unity:

We initialized a new list, called questPartyMembers, which now holds three string values, and used the Count method from the List class to print out the number of elements:

Knowing how many elements are in a list is highly useful; however, in most cases, that information is not enough. We want to be able to modify our lists as needed, which we'll discuss next.

Common methods

List elements can be accessed and modified like arrays with a subscript operator and index, as long as the index is within the List class's range. However, the List class has a variety of methods that extend its functionality, such as adding, inserting, and removing elements. 

Sticking with the questPartyMembers list, let's add a new member to the team:

  questPartyMembers.Add("Craven the Necromancer");

The Add method appends the new element at the end of the list, which brings the questPartyMembers count to four and the element order to the following:

{ "Grim the Barbarian", "Merlin the Wise", "Sterling the Knight",
    "Craven the Necromancer"};

To add an element to a specific spot in a list, we can pass the Insert method the index and the value that we want to add:

 questPartyMembers.Insert(1, "Tanis the Thief");

When an element is inserted at a previously-occupied index, all the elements in the list have their indices increased by 1. In our example, "Tanis the Thief" is now at index 1, meaning that "Merlin the Wise" is now at index 2 instead of 1, and so on:

{ "Grim the Barbarian", "Tanis the Thief", "Merlin the Wise", "Sterling
    the Knight", "Craven the Necromancer"};

Removing an element is just as simple; all we need is the index or the literal value, and the List class does the work:

// Both of these methods would remove the required element
questPartyMembers.RemoveAt(0);
questPartyMembers.Remove("Grim the Barbarian");

At the end of our edits, questPartyMembers now contains the following elements indexed from 0 to 3:

{ "Tanis the Thief", "Merlin the Wise", "Sterling the Knight", "Craven
    the Necromancer"};

There are many more List class methods that allow for value checks, finding and sorting elements, and working with ranges. A full method list, with descriptions, can be found here: https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.7.2.

While lists are great for single-value elements, there are cases where you'll need to store information or data containing more than one value. This is where dictionaries come into play.

Dictionaries 

The Dictionary type steps away from arrays and lists by storing value pairs in each element, instead of single values. These elements are referred to as key-value pairs: the key acts as the index, or lookup value, for its corresponding value. Unlike arrays and lists, dictionaries are unordered. However, they can be sorted and ordered in various configurations after they are created.

Basic syntax

Declaring a dictionary is almost the same as declaring a list, but with one added detail—both the key and the value type need to be specified inside the arrow symbols:

Dictionary<keyType, valueType> name = new Dictionary<keyType,
  valueType>();

To initialize a dictionary with key-value pairs, do the following:

Use a pair of curly brackets at the end of the declaration.

Add each element within its pair of curly brackets, with the key and the value separated by a comma. 

Separate elements with a comma, except the last element where the comma is optional:

Dictionary<keyType, valueType> name = new Dictionary<keyType,
  valueType>()
{
    {key1, value1},
    {key2, value2}
};

An important note to consider when picking key values is that each key must be unique, and they cannot be changed. If you need to update a key, change its value in the variable declaration or remove the entire key-value pair and add another in code. 

Just like with arrays and lists, dictionaries can be initialized on a single line with no problems from Visual Studio. However, writing out each key-value pair on its line, as in the preceding example, is a good habit to get into—both for readability and your sanity.

Time for action – setting up an inventory

Let's create a dictionary to store items that a character might carry:

Declare a Dictionary with a key type of string and a value type of int, called itemInventory.

Initialize it to new Dictionary<string, int>(), and add three key-value pairs of your choice. Make sure each element is in its pair of curly brackets.

Add a debug log to print out the itemInventory.Count property so that we can see how items are stored.

Save the file and Play:

Here, a new dictionary, called itemInventory, was created and initialized with three key-value pairs. We specified the keys as strings, with corresponding values as integers, and printed out how many elements itemInventory currently holds:

Like lists, we need to be able to do more than just print out the number of key-value pairs in a given dictionary. We'll explore adding, removing, and updating these values in the following section.

Working with dictionary pairs

Key-value pairs can be added, removed, and accessed from dictionaries using both subscript and class methods. To retrieve an element's value, use the subscript operator with the element's key—in the following example, numberOfPotions would be assigned a value of 5:

int numberOfPotions = itemInventory["Potion"];

An element's value can be updated using the same method—the value associated with "Potion" would now be 10:

itemInventory["Potion"] = 10;

Elements can be added to dictionaries in two ways: with the Add method and with the subscript operator. The Add method takes in a key and a value and creates a new key-value element, as long as their types correspond to the dictionary declaration:

itemInventory.Add("Throwing Knife", 3);

If the subscript operator is used to assign a value to a key that doesn't exist in a dictionary, the compiler will automatically add it as a new key-value pair. For example, if we wanted to add a new element for "Bandage", we could do so with the following code:

itemInventory["Bandage"] = 5;

This brings up a crucial point about referencing key-value pairs: it's better to be certain that an element exists before trying to access it, to avoid mistakenly adding new key-value pairs. Pairing the ContainsKey method with an if statement is the simple solution since ContainsKey returns a Boolean value based on whether the key exists. In the following example, we're sure that the "Aspirin" key exists before modifying its value:

if(itemInventory.ContainsKey("Aspirin"))
{
    itemInventory["Aspirin"] = 3;
}

Finally, a key-value pair can be deleted from a dictionary using the Remove method, which takes in a key parameter:

itemInventory.Remove("Antidote");

Like lists, dictionaries offer a variety of methods and functionality to make development easier, but we can't cover them all here. If you're curious, the official documentation can be found at https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.7.2.

Collections are safely in our toolkit, so it's time for another quiz to make sure you're ready to move on to the next big topic: iteration statements.

Pop quiz 2 – all about collections

What is an element in an array or list?

What is the index number of the first element in an array or list?

Can a single array or list store different types of data?

How can you add more elements to an array to make room for more data?

Since collections are groups or lists of items, they need to be accessible in an efficient manner. Luckily, C# has several iteration statements, which we'll talk about in the following section.

Iteration statements

We've accessed individual collection elements through the subscript operator, along with collection type methods, but what do we do when we need to go through the entire collection element by element? In programming, this is called iteration, and C# provides several statement types that let us loop through (or iterate over, if you want to be technical) collection elements. Iteration statements are like methods, in that they store a block of code to be executed; unlike methods, they can repeatedly execute their code blocks as long as their conditions are met. 

For loops

The for loop is most commonly used when a block of code needs to be executed a certain number of times before the program continues. The statement itself takes in three expressions, each with a specific function to perform before the loop executes. Since for loops keep track of the current iteration, they are best suited to arrays and lists. 

Take a look at the following looping statement blueprint:

for (initializer; condition; iterator)
{
    code block;
}

Let's break this down:

The for keyword starts the statement, followed by a pair of parentheses.

Inside the parentheses are the gatekeepers: the initializer, condition, and iterator expressions. 

The loop starts with the initializer expression, which is a local variable created to keep track of how many times the loop has executed—this is usually set to 0 because collection types are zero-indexed.

Next, the condition expression is checked and, if true, proceeds to the iterator.

The iterator expression is used to either increase or decrease (increment or decrement) the initializer, meaning the next time the loop evaluates its condition, the initializer will be different.

That all sounds like a lot, so let's look at a practical example with the questPartyMembers list we created earlier:

List<string> questPartyMembers = new List<string>()
{ "Grim the Barbarian", "Merlin the Wise", "Sterling the Knight"};

for (int i = 0; i < questPartyMembers.Count; i++)
{
    Debug.LogFormat("Index: {0} - {1}", i, questPartyMembers[i]);
}

Let's go through the loop again and see how it's working:

First, the initializer in the for loop is set as a local int variable named i with a starting value of 0.

To ensure we never get an out-of-range exception, the for loop makes sure that the loop only runs another time if i is less than the number of elements in questPartyMembers.

Finally, i is increased by 1 each time the loop runs with the ++ operator. 

Inside the for loop, we've just printed out the index and the list element at that index using i. Notice that i is in step with the index of the collection elements, since both start at 0:

Traditionally, the letter i is typically used as the initializer variable name. If you happen to have nested for loops, the variable names used will be the letters j, k, l, and so on.

Let's try out our new iteration statements on one of our existing collections.

Time for action – finding an element

While we loop through questPartyMembers, let's see whether we can identify when a certain element is iterated over and add a special debug log just for that case:

Add an if statement below the debug log in the for loop.

In the if statement's condition, check whether the current questPartyMember list matches "Merlin the Wise".

If it does, add a debug log of your choice:

The console output should look almost the same, except that there is now an extra debug log—one that only printed once when it was Merlin's turn to go through the loop. More specifically, when i was equal to 1 on the second loop, the if statement fired and two logs were printed out instead of just one:

Using a standard for loop can be highly useful in the right situation, but there's seldom just one way to do things in programming, which is where the foreach statement comes into play.

foreach loops

foreach loops take each element in a collection and store it in a local variable, making it accessible inside the statement. The local variable type must match the collection element type to work properly. foreach loops can be used with arrays and lists, but they are especially useful with dictionaries, as they are not based on a numeric index.

In blueprint form, a foreach loop looks like this:

foreach(elementType localName in collectionVariable)
{
    code block;
}

Let's stick with the questPartyMembers example and do a roll call for each of its elements:

List<string> questPartyMembers = new List<string>()
{ "Grim the Barbarian", "Merlin the Wise", "Sterling the Knight"};

foreach(string partyMember in questPartyMembers)
{
    Debug.LogFormat("{0} - Here!", partyMember);
}

We can break this down as follows:

The element type is declared as a string, which matches the values in questPartyMembers.

A local variable, called partyMember, is created to hold each element as the loop repeats. 

The in keyword, followed by the collection we want to loop through, in this case, questPartyMembers, finishes things off:

This is a good deal simpler than the for loop. However, when dealing with dictionaries, there are a few important differences we need to mention—namely how to deal with key-value pairs as local variables.

Looping through key-value pairs

To capture a key-value pair in a local variable, we need to use the aptly-named KeyValuePair type, assigning both the key and value types to match the dictionary's corresponding types. Since KeyValuePair is its type, it acts just like any other element type, as a local variable.

For example, let's loop through the itemInventory dictionary we created earlier in the Dictionaries section and debug each key-value like a shop item description:

Dictionary<string, int> itemInventory = new Dictionary<string, int>()
{
    { "Potion", 5},
    { "Antidote", 7},
    { "Aspirin", 1}
};

foreach(KeyValuePair<string, int> kvp in itemInventory)
{
 Debug.LogFormat("Item: {0} - {1}g", kvp.Key, kvp.Value);
}

We've specified a local variable of KeyValuePair, called kvp, which is a common naming convention in programming like calling the for loop initializer i, and set the key and value types to string and int to match itemInventory.

To access the key and value of the local kvp variable, we use the KeyValuePair properties of Key and Value, respectively. 

In this example, the keys are strings and the values are integers, which we can print out as the item name and item price:

If you're feeling particularly adventurous, try out the following optional challenge to drive home what you've just learned.

Hero's trial – finding affordable items

Create a variable to store how much gold your fictional character has, and see whether you can add an if statement inside the foreach loop to check for items that you can afford. Hint: use kvp.Value to compare prices with what's in your wallet.

while loops

while loops are similar to if statements in that they run as long as a single expression or condition is true. Value comparisons and Boolean variables can be used as while conditions, and they can be modified with the NOT operator.

The while loop syntax says, While my condition is true, keep running my code block indefinitely:

initializer
while (condition)
{
    code block;
    iterator;
}

With while loops, it's common to declare an initializer variable, as in a for loop, and manually increment or decrement it at the end of the loop's code block. Depending on your situation, the initializer is usually part of the loop's condition.

Time for action – tracking player lives

Let's take a common use case where we need to execute code while the player is alive, and then debug when that's no longer the case:

Create an initializer variable, called playerLives, of the int type, and set it to 3.

Declare a while loop with the condition checking whether playerLives is greater than 0 (that is, the player is still alive).

Inside the while loop, debug something to let us know the character is still kicking, then decrement playerLives by 1 using the -- operator.

Increasing and decreasing a value by 1 is called incrementing and decrementing, respectively (-- will decrease a value by 1, and ++ will increase it by 1).

Add a debug log after the while loop curly brackets to print something when our lives run out:

With playerLives starting out at 3, the while loop will execute three times. During each loop, the debug log, "Still alive!", fires, and a life is subtracted from playerLives. When the while loop goes to run a fourth time, our condition fails because playerLives is 0, so the code block is skipped and the final debug log prints out:

The question now is what happens if a loop never stops executing? We'll discuss this issue in the following section.

To infinity and beyond

Before finishing this chapter, we need to understand one extremely vital concept when it comes to iteration statements: infinite loops. These are exactly what they sound like: when a loop's conditions make it impossible for it to stop running and move on in the program. Infinite loops usually happen in for and while loops when the iterator is not increased or decreased; if the playerLives line of code was left out of the while loop example, Unity would freeze and/or crash, recognizing that playerLives would always be 3 and execute the loop forever.

Iterators are not the only culprits to be aware of; setting conditions in a for loop that will never fail, or evaluate to false, can also cause infinite loops. In the party members example, from the Looping through key-value pairs section, if we had set the for loop condition to i < 0 instead of i < questPartyMembers.Count, i would always be less than 0, looping until Unity crashed.

Summary

As we bring the chapter to a close, we should reflect on how much we've accomplished and what we can build with that new knowledge. We know how to use simple if-else checks and more complex switch statements, allowing for decision making in code. We can create variables that hold collections of values with arrays and lists or key-value pairs with dictionaries. This allows for complex and grouped data to be stored efficiently. We can even choose the right looping statement for each collection type, while carefully avoiding infinite-loop crashes. If you're feeling overloaded, that's perfectly OK—logical, sequential thinking is all part of exercising your programming brain. 

The next chapter will complete the basics of C# programming with a look at classes, structs, and object-oriented programming (OOP). We'll be putting everything we've learned so far into these topics, preparing for our first real dive into understanding and controlling objects in the Unity engine.

Working with Classes, Structs, and OOP

For obvious reasons, the goal of this book isn't to give you a splitting headache from information overload. However, these next topics will take you out of the beginner's cubicle and into the open air of object-oriented programming (OOP). Up to this point, we've been relying exclusively on predefined variable types that are part of the C# language: under-the-hood strings, lists, and dictionaries that are classes, which is why we can create them and use their properties through dot notation. However, relying on built-in types has one glaring weakness—the inability to deviate from the blueprints that C# has already set.

Creating your classes gives you the freedom to define and configure blueprints of your design, capturing information, and driving action that is specific to your game or application. In essence, custom classes and OOP are the keys to the programming kingdom; without them, unique programs will be few and far between.

In this chapter, you'll get hands-on experience creating classes from scratch and discuss the inner workings of class variables, constructors, and methods. You'll also be introduced to the differences between reference and value type objects, and how these concepts can be applied inside Unity. The following topics will be discussed in more detail as you move along:

Defining classes

Declaring structs

Declaring and using structs

Understanding reference and value types

Exploring the basics of OOP

Applying OOP in Unity

Defining a class 

Back in Chapter 2, The Building Blocks of Programming, we briefly talked about how classes are blueprints for objects and mentioned that they can be treated as custom variable types. We also learned that the LearningCurve script is a class, but a special one that Unity can attach to objects in the scene. The main thing to remember with classes is that they are reference types—that is, when they are assigned or passed to another variable, the original object is referenced, not a new copy. We'll get into this after we discuss structs. However, before any of that, we need to understand the basics of creating classes.

Basic syntax

For now, we're going to set aside how classes and scripts work in Unity and focus on how they are created and used in C#. If you remember the blueprint we previously roughed out, classes are created using the class keyword, as follows:

accessModifier class UniqueName
{
    Variables
    Constructors
    Methods
}

Any variables or methods declared inside a class belong to that class and are accessed through its unique class name. 

To make the examples as cohesive as possible throughout this chapter, we'll be creating and modifying a simple Character class that a typical game would have. We'll also be moving away from code screenshots to get you accustomed to reading and interpreting code as you would see it in the programming wild. However, the first thing we need is a custom class of our own, so let's create one.

Time for action – creating a character class

We'll need a class to practice with before we can understand their inner workings, so let's create a new C# script and start from scratch:

Right-click on the Scripts folder, choose Create, and select C# Script.

Name it Character, open it up in Visual Studio, and delete all the generated code after using UnityEngine.

Declare a public class called Character followed by a set of curly braces, and then save the file. Your class code should exactly match the following code:

     using System.Collections;
     using System.Collections.Generic;
     using UnityEngine;

     public class Character
     {

     }

Character is now registered as a public class blueprint. This means that any class in the project can use it to create characters. However, these are just the instructions—to create a character takes an additional step. This creational step is called instantiation and is the subject of the next section.

Instantiating class objects

Instantiation is the act of creating an object from a specific set of instructions, which is called an instance. If classes are blueprints, instances are the houses built from their instructions; every new instance of Character is its object, just like two houses built from the same instructions are still two different physical structures. What happens to one doesn't have any repercussions on the other.

In the previous chapter, Control Flow and Collection Types, we created lists and dictionaries, which are classes, using their types and the new keyword. We can do the same thing for custom classes such as Character, which you'll do next.

Time for action – creating a new character

We declared the Character class as public, which means that a Character instance can be created in any other class. Since we have LearningCurve working already, let's declare a new character in the Start() method.

Declare a new Character type variable, called hero, in the Start() method of LearningCurve:

Character hero = new Character();

Let's break this down one step at a time:

The variable type is specified as Character, meaning that the variable is an instance of that class.

The variable is named hero, and it is created using the new keyword, followed by the Character class name and two parentheses. This is where the actual instance is created in the program's memory, even if the class is empty right now. 

We can use the hero variable just like any other object we've worked with so far. When the Character class gets variables and methods of its own, we can access them from hero using dot notation.

You could just as easily have used an inferred declaration when creating the hero variable, like so: 

var hero = new Character();

Now our character class can't do much without any class fields to work with. You'll be adding class fields, and more, in the next few sections.

Adding class fields

Adding variables, or fields, to a custom class is no different than what we've already been doing with LearningCurve. The same concepts apply, including access modifiers, variable scope, and value assignments. However, any variables belonging to a class are created with the class instance, meaning that if there are no values assigned, they will default to zero or null. In general, choosing to set initial values comes down to what information they will store:

If a variable needs to have the same starting value whenever a class instance is created, setting an initial value is a solid idea.

If a variable needs to be customized in every class instance, leave its value unassigned and use a class constructor (a topic that we'll get to later on).

Every character class is going to need a few basic fields; it's your job to add them in the following section.

Time for action – fleshing out character details

Let's incorporate two variables to hold the character's name and the number of starting experience points:

Add two public variables inside the Character class's curly braces—a string variable for the name, and an integer variable for the experience points.

Leave the name value empty, but assign the experience points to 0 so that every character starts from the bottom:

      public class Character
      {
         public string name;
         public int exp = 0;
      }

Add a debug log in LearningCurve right after the Character instance was initialized. Use it to print out the new character's name and exp variables using dot notation:

      Character hero = new Character();
      Debug.LogFormat("Hero: {0} - {1} EXP", hero.name, hero.exp);

When hero is initialized, name is assigned a null value that shows up as an empty space in the debug log, with exp printing out 0. Notice that we didn't have to attach the Character script to any GameObjects in the scene; we just referenced them in LearningCurve and Unity did the rest. The console will now debug out our character information, which is referenced as follows:

At this point, our class is working, but it's not very practical with these empty values. You'll need to fix that with what's called a class constructor.

Using constructors

Class constructors are special methods that fire automatically when a class instance is created, which is similar to how the Start method runs in LearningCurve. Constructors build the class according to its blueprint:

If a constructor is not specified, C# generates a default one. The default constructor sets any variables to their default type values—numeric values are set to zero, Booleans to false, and reference types (classes) to null.

Custom constructors can be defined with parameters, just like any other method, and are used to set class variable values at initialization.

A class can have multiple constructors.

Constructors are written like regular methods but with a few differences; for instance, they need to be public, have no return type, and the method name is always the class name. As an example, let's add a basic constructor with no parameters to the Character class and set the name field to something other than null.

Add this new code directly underneath the class variables, as follows:

public string name;
public int exp = 0;

public Character()
{
    name = "Not assigned";
}

Run the project in Unity and you'll see the hero instance using this new constructor. The debug log will show the hero's name as Not assigned instead of a null value: 

This is good progress, but we need the class constructor to be more flexible. This means that we need to be able to pass in values so that they can be used as starting values, which you'll do next.

Time for action – specifying starting properties

Now, the Character class is starting to behave more like a real object, but we can make this even better by adding a second constructor to take in a name at initialization and set it to the name field:

Add another constructor to Character that takes in a string parameter, called name.

Assign the parameter to the class's name variable using the this keyword. This is called constructor overloading:

      public Character(string name)
      {
          this.name = name;
      }

For convenience, constructors will often have parameters that share a name with a class variable. In these cases, use the this keyword to specify which variable belongs to the class. In the example here, this.name refers to the class's name variable, while name is the parameter; without the this keyword, the compiler will throw a warning because it won't be able to tell them apart. 

Create a new Character instance in LearningCurve, called heroine. Use the custom constructor to pass in a name when it's initialized and print out the details in the console:

      Character heroine = new Character("Agatha");
      Debug.LogFormat("Hero: {0} - {1} EXP", heroine.name,
        heroine.exp);

When a class has multiple constructors or a method has multiple variations, Visual Studio will show a set of arrows in the autocomplete popup that can be scrolled through using the arrow keys:

We can now choose between the basic and custom constructor when we initialize a new Character class. The Character class itself is now far more flexible when it comes to configuring different instances for different situations:

Now the real work starts; our class needs methods to be able to do anything useful besides acting as a storage facility for variables. Your next task is to put this into practice.

Declaring class methods

Adding methods to custom classes is no different from adding them to LearningCurve. However, this is a good opportunity to talk about a staple of good programming—Don't Repeat Yourself (DRY). DRY is a benchmark of all well-written code. Essentially, if you find yourself writing the same line, or lines, over and over, it's time to rethink and reorganize. This usually takes the form of a new method to hold the repeated code, making it easier to modify and call that functionality elsewhere.

In programming terms, you'll see this referred to as abstracting out a method or feature.

We have a fair bit of repeated code already, so let's take a look and see where we can increase the legibility and efficiency of our scripts.

Time for action – printing out character data

Our repeated debug logs are a perfect opportunity to abstract out some code directly into the Character class: 

Add a new public method with a void return type, called PrintStatsInfo, to the Character class.

Copy and paste the debug log from LearningCurve into the method body.

Change the variables to name and exp, since they can now be referenced from the class directly:

      public void PrintStatsInfo()
      {
          Debug.LogFormat("Hero: {0} - {1} EXP", name, exp);
      }

Replace the character debug log that we previously added to LearningCurve with method calls to PrintStatsInfo, and click on Play:

      Character hero = new Character();
      hero.PrintStatsInfo();

      Character heroine = new Character("Agatha");
      heroine.PrintStatsInfo();

Now that the Character class has a method, any instance can freely access it using dot notation. Since hero and heroine are both separate objects, PrintStatsInfo debugs their respective name and exp values to the Console.

This behavior is better than having the debug logs directly in LearningCurve. It's always a good idea to group functionality into a class and drive action through methods. This makes the code more readable—as our Character objects are giving a command when printing out the debug logs, instead of repeating code.

The entire Character class can be seen in the following screenshot for reference:

With classes covered, you're ready to tackle their lightweight cousin object: the struct!

Declaring structs

Structs are similar to classes in that they are also blueprints for objects you want to create in your programs. The main difference is that they are value types—meaning they are passed by value instead of reference, such as classes. We'll go into this in more detail in the next section. First, we need to understand how structs work and the specific rules that apply when creating them.

Basic syntax

Structs are declared in the same way as classes, and can hold fields, methods, and constructors:

accessModifier struct UniqueName
{
    Variables
    Constructors
    Methods
}

Like classes, any variables and methods belong exclusively to the struct and are accessed by its unique name.

However, structs have a few limitations:

Variables cannot be initialized with values inside the struct declaration unless they're marked with the static or const modifier—you can read more about this in Chapter 10, Revisiting Types, Methods, and Classes.

Constructors without parameters aren't permitted.

Structs come with a default constructor that will automatically set all variables to their default values according to their type. 

Every character requires a good weapon, and these weapons are the perfect fit for a struct object over a class. We'll discuss why that is in the Understanding reference and value types section of this chapter. However, first, you're going to create one to play around with.

Time for action – creating a weapon struct

Our characters are going to need good weapons to see them through quests, which are good candidates for a simple struct:

Create a public struct, called Weapon, in the Character script. Make sure it's outside the Character class's curly braces:

Add a field for name of type string.

Add another field for damage of type int:

You can have classes and structs nested within each other, but this is generally frowned upon because it clutters up the code.

      public struct Weapon
      {
          public string name;
          public int damage;
      }

Declare a constructor with the name and damage parameters, and set the struct fields using the this keyword:

     public Weapon(string name, int damage)
     {
          this.name = name;
          this.damage = damage;
     }

Add a debug method below the constructor to print out the weapon information:

     public void PrintWeaponStats()
     {
        Debug.LogFormat("Weapon: {0} - {1} DMB", name, damage);
     }

In LearningCurve, create a new Weapon struct using the custom constructor and the new keyword:

     Weapon huntingBow = new Weapon("Hunting Bow", 105);

Even though the Weapon struct was created in the Character script, since it is outside of the actual class declaration (curly braces), it is not part of the class itself. Our new huntingBow object uses the custom constructor and provides values for both fields on initialization.

It's a good idea to limit scripts to a single class. It's fairly common to see structs that are used exclusively by a class included in the file, such as in the Character script and Weapon struct example. 

Now that we have an example of both reference (class) and value (struct) objects, it's time to get acquainted with each of their finer points. More specifically, you'll need to understand how each of these objects is passed and stored in memory.

Understanding reference and value types

Other than keywords and initial field values, we haven't seen much difference between classes and structs so far. Classes are best suited for grouping together complex actions and data that will change throughout a program; structs are a better choice for simple objects and data that will remain constant for the most part. Besides their uses, they are fundamentally different in one key area—that is, how they are passed or assigned between variables. Classes are reference types, meaning that they are passed by reference; structs are value types, meaning that they are passed by value.

Reference types

When the instances of our Character class are initialized, the hero and heroine variables don't hold their class information—instead, they hold a reference to where the object is located in the program's memory. If we assigned hero or heroine to another variable, the memory reference is assigned, not the character data. This has several implications, the most important being that if we have multiple variables storing the same memory reference, a change to one affects them all.

Topics like this are better demonstrated than explained; it's up to you to try this out in a practical example next.

Time for action – creating a new hero

It's time to test that the Character class is a reference type:

Declare a new Character variable in LearningCurve, called hero2. Assign hero2 to hero, and use the PrintStatsInfo method to print out both sets of information.

Click on Play and take a look at the two debug logs that show up in the Console:

      Character hero = new Character();
      Character hero2 = hero;

      hero.PrintStatsInfo();
      hero2.PrintStatsInfo();

The two debug logs will be identical because hero2 was assigned to hero when it was created. At this point, both hero2 and hero point to where hero is located in memory:

Now, change the name of hero2 to something fun and click on Play again: 

      Character hero2 = hero;
      hero2.name = "Sir Krane the Brave";

You'll see that both hero and hero2 now have the same name, even though only one of our character's data was changed. The lesson here is that reference types need to be treated carefully and that they are not copied when assigned to new variables. Any change to one reference trickles through all other variables holding the same reference:

If you're trying to copy a class, either create a new, separate instance or reconsider whether a struct might be a better choice for your object blueprint. You'll get a better glimpse of value types in the following section.

Value types

When a struct object is created, all of its data is stored in its corresponding variable with no references or connections to its memory location. This makes structs useful for creating objects that need to be copied quickly and efficiently, while still retaining their separate identities. 

Try this out in with our Weapon struct in the Time for action – copying weapons section.

Time for action – copying weapons

Let's create a new weapon object by copying huntingBow into a new variable, and updating its data to see whether the changes affect both structs:

Declare a new Weapon struct in LearningCurve, and assign huntingBow as its initial value:

     Weapon huntingBow = new Weapon("Hunting Bow", 105);
     Weapon warBow = huntingBow;

Print out each weapon's data using the debug method:

     huntingBow.PrintWeaponStats();
     warBow.PrintWeaponStats();

The way they're set up now, both huntingBow and warBow will have the same debug logs, just like our two characters did before we changed any data:

Change the warBow.name and warBow.damage fields to values of your choice and click on Play again:

     Weapon warBow = huntingBow;

     warBow.name = "War Bow";
     warBow.damage = 155;

The console will show that only the data relating to warBow was changed, and that huntingBow retains its original data. The takeaway from this example is that structs are easily copied and modified as their separate objects, unlike classes that retain references to an original object. Now that we understand a little more about how structs and classes work under the hood, we're in a good place to start talking about OOP and how it fits into the programming landscape:

Now that we've confirmed how reference and value types behave in their natural habitat, you're ready to dive into one of the most important coding topics: OOP. This is the main programming paradigm, or architecture, that you'll use when coding in C#.

Integrating the object-oriented mindset

If class and struct instances are the blueprints of our programs, then OOP is the architecture that holds everything together. When we refer to OOP as a programming paradigm, we are saying that it has specific principles for how the overall program should work and communicate. Essentially, OOP focuses on objects rather than pure sequential logic—the data they hold, how they drive action, and, most importantly, how they communicate with each other. 

Things in the physical world operate on a similar level; when you want to buy a soft drink, you grab a can of soda, not the liquid itself. The can is an object, grouping related information and actions together in a self-contained package. However, there are rules when dealing with objects, both in programming and the grocery store. For instance, who can access them—different variations and generic actions all play into the objects all around us. In programming terms, these rules are the main tenants of OOP: encapsulation, inheritance, and polymorphism. 

Encapsulation

One of the best things about OOP is that it supports encapsulation—defining how accessible an object's variables and methods are to outside code (this is sometimes referred to as calling code). Take our soda can as an example—in a vending machine, the possible interactions are limited. Since the machine is locked, not just anyone can come up and grab one; if you happen to have the right change, you'll be allowed provisional access to it, but in a specified quantity. If the machine itself is locked inside a room, only someone with the door key will even know the soda can exists. 

The question you're asking yourself now is, how do we set these limitations? The simple answer is that we've been using encapsulation this entire time by specifying access modifiers for our object variables and methods. If you need a refresher, go back and visit the Access modifiers section in Chapter 3, Diving into Variables, Types, and Methods. 

Let's try out a simple encapsulation example in the following section to understand how this works in practice.

Time for action – adding a reset

Our Character class is public, as are its fields and method. However, what if we wanted a method that can reset a character's data back to its initial values? This could come in handy, but can prove disastrous if it was accidentally called, making it a perfect candidate for a private object member:

Create a private method, called Reset, with no return value inside the Character class:

Set the name and exp variables back to "Not assigned" and 0, respectively:

 private void Reset()
 {
    this.name = "Not assigned";
     this.exp = 0;
 }

Try and call Reset from LearningCurve after printing out the hero2 data: 

If you're wondering whether Visual Studio is broken, it's not. Marking a method or variable as private will make it inaccessible using dot notation; if you manually type it in and hover over Reset(), you'll see an Error message regarding the method being protected.

Encapsulation does allow for more complex accessibility setups with objects; however, for now, we're going to stick with public and private members. As we begin to flesh out our game prototype in the next chapter, we'll add in different modifiers as needed.

Now, let's talk about inheritance, which is going to be your best friend when creating class hierarchies in your future games.

Inheritance

Just as in life, a C# class can be created in the image of another class, sharing its member variables and methods, but able to define its unique data. In OOP, we refer to this as inheritance, and it's a powerful way of creating related classes without having to repeat code. Take the soda example again—there are generic sodas on the market that have all the same basic properties, and then there are special sodas. The special sodas share the same basic properties but have different branding, or packaging, that sets them apart. When you look at both side by side, it's obvious that they're both cans of soda—but they're also obviously not the same. 

The original class is usually called the base or parent class, while the inheriting class is called the derived or child class. Any base class members marked with the public, protected, or internal access modifiers are automatically part of the derived class—except for constructors. Class constructors always belong to their containing class, but they can be used from derived classes to keep repeated code to a minimum.

Most games have more than one type of character, so let's create a new class called Paladin that inherits from the Character class. You can add this new class into the Character script or create a new one:

public class Paladin: Character
{

}

Just as LearningCurve inherits from Monobehavior, all we need to do is add a colon and the base class we want to inherit from, and C# does the rest. Now any Paladin instances will have access to a name property and a exp property along with a PrintStatsInfo method.

It's generally considered best practice to create a new script for different classes instead of adding them to existing ones. This separates your scripts and avoids having too many lines of code in any single file (called a bloated file).

This is great, but how do inherited classes handle their construction? You can find out in the following section.

Base constructors

When a class inherits from another class, they form a pyramid of sorts with member variables flowing down from the parent class to any of its derived children. The parent class isn't aware of any of its children, but all children are aware of their parent. However, parent class constructors can be called directly from child constructors with a simple syntax modification:

public class ChildClass: ParentClass
{
    public ChildClass(): base()
    {

    }
}

The base keyword stands in for the parent constructor—in this case, the default constructor. However, since base is standing in for a constructor, and a constructor is a method, a child class can pass parameters up the pyramid to its parent constructor. 

Time for action – calling a base constructor

Since we want all Paladin objects to have a name, and Character already has a constructor that handles this, we can call the base constructor directly from the Paladin class and save ourselves the trouble of rewriting a constructor:

Add a constructor to the Paladin class that takes in a string parameter, called name:

Use a colon and the base keyword to call the parent constructor, passing in name:

      public class Paladin: Character
      {
          public Paladin(string name): base(name)
          {

          }
      }

Create a new Paladin instance, called knight, in LearningCurve: 

Use the base constructor to assign a value.

Call PrintStatsInfo from knight and take a look at the console:

      Paladin knight = new Paladin("Sir Arthur");
      knight.PrintStatsInfo();

The debug log will be the same as our other Character instances, but with the name that we assigned to the Paladin constructor. When the Paladin constructor fires, it passes the name parameter to the Character constructor, which sets the name value. Essentially, we used the Character constructor to do the initialization work for the Paladin class, making the Paladin constructor only responsible for initializing its unique properties, which it doesn't have at this point:

Aside from inheritance, there will be times when you want to make new objects out of a combination of other existing objects. Think of legos; you don't start building from nothing—you already have blocks of different colors and structures to work with. In programming terms, this is called composition, which we'll discuss in the following section.

Composition

Aside from inheritance, classes can be composed of other classes. Take our Weapon struct, for example. Paladin can easily contain a Weapon variable inside itself and have access to all its properties and methods. Let's do that by updating Paladin to take in a starting weapon and assign its value in the constructor:

public class Paladin: Character
{
    public Weapon weapon;

    public Paladin(string name, Weapon weapon): base(name)
    {
        this.weapon = weapon;
    }
}

Since weapon is unique to Paladin and not Character, we need to set its initial value in the constructor. We also need to update the knight instance to include a Weapon variable. So, let's use huntingBow:

Paladin knight = new Paladin("Sir Arthur", huntingBow);

If you run the game now, you won't see anything different because we're using the PrintStatsInfo method from the Character class, which doesn't know about the Paladin class weapon property. To tackle this problem, we need to talk about polymorphism.

Polymorphism

Polymorphism is the Greek word for many-shaped and applies to OOP in two distinct ways:

Derived class objects are treated the same as parent class objects. For example, an array of Character objects could also store Paladin objects, as they derive from Character.

Parent classes can mark methods as virtual, meaning that their instructions can be modified by derived classes using the override keyword. In the case of Character and Paladin, it will be useful if we could debug different messages from PrintStatsInfo for each one.

Polymorphism allows derived classes to keep the structure of their parent class while also having the freedom to tailor actions to fit their specific needs. Let's take this new knowledge and apply it to our character debug method.

Time for action – functional variations

Let's modify Character and Paladin to print out different debug logs using PrintStatsInfo:

Change PrintStatsInfo in the Character class by adding the virtual keyword between public and void: 

      public virtual void PrintStatsInfo()
      {
          Debug.LogFormat("Hero: {0} - {1} EXP", name, exp);
      }

Declare the PrintStatsInfo method in the Paladin class using the override keyword:

Add a debug log to print out the Paladin properties in whatever way you like:

      public override void PrintStatsInfo()
      {
         Debug.LogFormat("Hail {0} - take up your {1}!", name,
             weapon.name);
      }

This might look like repeated code, which we already said is bad form, but this is a special case. What we've done by marking PrintStatsInfo as virtual in the Character class is to tell the compiler that this method can have many shapes according to the calling class. When we declared the overridden version of PrintStatsInfo in Paladin, we added the custom behavior that only applies to that class. Thanks to polymorphism, we don't have to choose which version of PrintStatsInfo we want to call from a Character or Paladin object—the compiler already knows:

That was a lot to take in, so be sure to review the roundup topics in the following section before proceeding!

OOP roundup

This was a lot to take in, I know. So, let's review some of the main points of OOP as we approach the finish line:

OOP is all about grouping related data and actions into objects—objects that can communicate and act independently from each other.

Access to class members can be set using access modifiers, just like variables.

Classes can inherit from other classes, creating trickle-down hierarchies of parent/child relationships.

Classes can have members of other class or struct types.

Classes can override any parent methods marked as virtual, allowing them to perform custom actions while retaining the same blueprint.

OOP is not the only programming paradigm that can be used with C#—you can find practical explanations of the other main approaches here: http://cs.lmu.edu/~ray/notes/paradigms.

All the OOP you've learned in this chapter is directly applicable to the C# world. However, we still need to put this into perspective with Unity, which is what you'll spend the rest of the chapter focusing on.

Applying OOP in Unity

If you're around OOP languages enough, you'll eventually hear the phrase everything is an object whispered like a secret prayer between developers. Following OOP principles, everything in a program should be an object, but GameObjects in Unity can represent your classes and structs. However, that's not to say all objects in Unity have to be in the physical scene, so we can still use our newfound programmed classes behind the scenes.

Objects are a class act

Back in Chapter 2, The Building Blocks of Programming, we discussed how a script is transformed into a component when it's added to a GameObject in Unity. Think of this in terms of the OOP principle of composition—GameObjects are the parent containers, and they can be made up of multiple components. This might sound contradictory to the idea of one C# class per script, but, in truth, that's more of a guideline for better readability than an actual requirement. Classes can be nested inside one another—it just gets messy fast. However, having multiple script components attached to a single GameObject can be very useful, especially when dealing with manager classes or behaviors. 

Always try to boil down objects to their most basic elements, then use composition to build bigger, more complex objects out of those smaller classes. It's easier to modify a GameObject made out of small, interchangeable components than one large, clunky one.

Let's take a look at Main Camera to see this in action:

Each component in the preceding screenshot (Transform, Camera, Audio Listener, and the LearningCurve script) started as a class in Unity. Like instances of Character or Weapon, these components become objects in computer memory when we click on Play, complete with their member variables and methods.

If we were to attach LearningCurve (or any script or component) to 1,000 GameObjects and click on Play, 1,000 separate instances of LearningCurve would be created and stored in memory. 

We can even create our instances of these components using their component name as the data type. Like classes, Unity component classes are reference types and can be created like any other variable. However, finding and assigning these Unity components is slightly different than what you've seen so far. For that, you'll need to understand a little more about how GameObjects work in the following section.

Accessing components

Now that we know how components act on GameObjects, how do we go about accessing their specific instances? Lucky for us, all GameObjects in Unity inherit from the GameObject class, which means we can use their member methods to find anything we need in a scene. There are two ways to assign or retrieve GameObjects that are active in the current scene:

Through the GetComponent or Find methods in the GameObject class, which work with public and private variables.

By dragging and dropping the GameObjects themselves from the Project panel directly into variable slots in Inspector tab. This option only works with public variables in C# (or in Unity with private variables marked with the SerializeField attribute), since those are the only ones that will appear in Inspector.

You can learn more about attributes and SerializeField in the Unity documentation at https://docs.unity3d.com/ScriptReference/SerializeField.html.

Let's take a look at the syntax of the first option.

Basic syntax

Using GetComponent is fairly simple, but its method signature is slightly different from other methods that we've seen so far:

GameObject.GetComponent<ComponentType>();

All we need is the component type that we're looking for, and the GameObject class will return the component if it exists and null if it doesn't. There are other variations of the GetComponent method, but this one is the simplest because we don't need to know specifics about the GameObject class that we're looking for. This is called a generic method, which we'll discuss further in Chapter 11, Exploring Generics, Delegates, and Beyond. However, for now, let's just work with the camera's transform.

Time for action – accessing the current transform component

Since LearningCurve is already attached to the Main Camera, let's grab the Transform component from the Main Camera and store it in a public variable:

Add a new public Transform type variable, called camTransform, to LearningCurve:

       private Transform camTransform;

Initialize camTransform in Start using the GetComponent method from the GameObject class:

Use the this keyword, since LearningCurve is attached to the same GameObject component as the Transform component.

Access and debug the localPosition property of camTransform using dot notation:

 void Start()
 {
      camTransform = this.GetComponent<Transform>();
      Debug.Log(camTransform.localPosition);
}

We've added an uninitialized private Transform variable at the top of LearningCurve and initialized it using the GetComponent method inside Start. GetComponent finds the Transform component attached to this GameObject component and returns it to camTransform. With camTransform now storing a Transform object, we have access to all its class properties and methods—including localPosition in the following screenshot:

The GetComponent method is fantastic for quickly retrieving components, but it only has access to components on the GameObject that the calling script is attached to. For instance, if we use GetComponent from the LearningCurve script attached to the Main Camera, we'll only be able to access the Transform, Camera, and Audio Listener components.

If we want to reference a component on a separate GameObject, such as Directional Light, we would need to get a reference to the object first using the Find method. All it takes is a GameObject's name, and Unity will kick back the appropriate GameObject for us to store or manipulate.

For reference, the name of each GameObject can be found at the top of the Inspector tab with the object selected:

Finding objects in your game scenes is crucial in Unity, so you'll need to practice. Let's take the objects we have to work with and practice finding and assigning their components.

Time for action – finding components on different objects

Let's take the Find method out for a spin and retrieve the Directional Light object from LearningCurve:

Add two variables to LearningCurve underneath camTransform—one of type GameObject and one of type Transform: 

public GameObject directionLight;
private Transform lightTransform;

Find the Directional Light component by name, and use it to initialize directionLight inside the Start() method:

void Start()
{
    directionLight = GameObject.Find("Directional Light");
}

Set the value of lightTransform to the Transform component attached to directionLight, and debug its localPosition. Since directionLight is its GameObject now, GetComponent works perfectly:

void Start()
{
    directionLight = GameObject.Find("Directional Light");

    lightTransform = directionLight.GetComponent<Transform>();
    Debug.Log(lightTransform.localPosition);
}

Before running the game, it's important to understand that method calls can be chained together to cut down the number of code steps. For instance, we could initialize lightTransform in a single line by combining Find and GetComponent, without having to go through directionLight:

GameObject.Find("Directional Light").GetComponent<Transform>();

A word of warning—long lines of chained code can lead to poor readability and confusion when working on complex applications. It's a good rule of thumb to avoid lines longer than this example.

While finding objects in code always works, you can also simply drag and drop the objects themselves into the Inspector tab. Let's demonstrate how to do that in the following section.

Drag and drop

Now that we've covered the code-intensive way of doing things, let's take a quick look at Unity's drag and drop functionality. Although dragging and dropping is much faster than using the GameObject class in code, Unity sometimes loses the connections between objects and variables made this way when saving or exporting projects, or when Unity updates. When you need to assign a few variables quickly, then, by all means, take advantage of this feature. For most cases, I'd advise sticking with code.

Time for action – assigning variables in Unity

Let's change LearningCurve to show how to assign a GameObject component using drag and drop:

Comment out the following line of code, where we used GameObject.Find() to retrieve and assign the Directional Light object to the directionLight variable:

             //directionLight = GameObject.Find("Directional Light");

Select the Main Camera GameObject, drag Directional Light to the Direction Light field in the LearningCurve component, and click on Play:

The Directional Light GameObject is now assigned to the directionLight variable. No code was involved because Unity assigned the variable internally, with no change to the LearningCurve class.

It is important to understand a few things when deciding whether to assign variables using drag and drop or GameObject.Find(). First, the Find() method is marginally slower, leaving your game open to performance issues if you are calling the method multiple times in multiple scripts. Second, you need to be sure your GameObjects all have unique names in the scene hierarchy; if they don't, it may lead to some nasty bugs in situations where you have several objects of the same name or change the object names themselves.

Summary

Our journey into classes, structs, and OOP marks the end of the first section on the fundamentals of C#. You've learned how to declare your classes and structs, which is the scaffolding for every application or game you'll ever make. You've also identified the differences in how these two objects are passed and accessed and how they relate to OOP. Finally, you got hands-on with the tenants of OOP—creating classes using inheritance, composition, and polymorphism. 

Identifying related data and actions, creating blueprints to give them shape, and using instances to build interactions are a strong foundation for approaching any program or game. Add the ability to access components to the mix, and you've got the makings of a Unity developer.

The next chapter will segue into the basics of game development and scripting object behavior directly in Unity. We'll start by fleshing out the requirements of a simple open-world adventure game, work with GameObjects in the scene, and finish off with a white-boxed environment ready for our characters.

Pop quiz – all things OOP

What method handles the initialization logic inside a class?

Being value types, how are structs passed?

What are the main tenants of OOP?

Which GameObject class method would you use to find a component on the same object as the calling class?

Getting Your Hands Dirty with Unity

Creating a game involves much more than just simulating actions in code. Design, story, environment, lighting, and animation all play an important part in setting the stage for your players. A game is, first and foremost, an experience, which code alone can't deliver. 

Unity has placed itself at the forefront of game development over the past decade by bringing advanced tools to programmers and non-programmers alike. Animation and effects, audio, environment design, and much more are all available directly from the Unity Editor without a single line of code. We'll discuss these topics as we define the requirements, environment, and game mechanics of our game. However, first, we need a topical introduction to game design. 

Game design theory is a big area of study and learning all its secrets can consume an entire career. However, you'll only be getting hands-on with the basics; everything else is up to you to explore! This chapter will set us up for the rest of the book and will cover the following topics:

Game design primer 

Building a level

Understanding GameObjects and prefabs

Lighting basics

Animating in Unity

Integrating the particle system

A game design primer

Before jumping into any game project, it's important to have a blueprint of what you want to build. Sometimes, ideas will start crystal clear in your mind, but the minute you start creating character classes or environments, things seem to drift away from your original intention. This is where the game's design allows you to plan out the following touchpoints:

Concept: The big-picture idea and design of a game, including its genre and play style.

Core mechanics: The playable features or interactions that a character can take in-game. Common gameplay mechanics include jumping, shooting, puzzle-solving, or driving.

Control schemes: A map of the buttons and/or keys that give players control over their character, environment interactions, and other executable actions.

Story: The underlying narrative that fuels a game, creating empathy and a connection between players and the game world they play in.

Art style: The game's overarching look and feel, consistent across everything from characters and menu art to the levels and environment.

Win and lose conditions: The rules that govern how the game is won or lost, usually consisting of objectives or goals that carry the weight of potential failure.

These topics are by no means an exhaustive list of what goes into designing a game. However, they're a good place to start fleshing out something called a game design document, which is your next task!

Game design documents

Googling game design documents will result in a literal flood of templates, formatting rules, and content guidelines that can leave a new programmer ready to give it all up. The truth is, design documents are tailored to the team or company that creates them, making them much easier to draft than the internet would have you think. 

In general, there are three types of design documentation, as follows:

Game Design Document (GDD): The GDD houses everything from how the game is played to its atmosphere, story, and the experience it's trying to create. Depending on the game, this document can be a few pages long or several hundred.

Technical Design Document: This document focuses on all the technical aspects of the game, from the hardware it will run on to how the classes and program architecture need to be built out. Like a GDD, the length will vary based on the project.

One-Page: Usually used for marketing or promotional situations, a one-page is essentially a snapshot of your game. As the name suggests, it should only take up a single page.

There's no right or wrong way to format a GDD, so it's a good place to let your brand of creativity thrive. Throw in pictures of reference material that inspires you; get creative with the layout—this is your place to define your vision.

The game we'll be working on throughout the rest of this book is fairly simple and won't require anything as detailed as a GDD or TDD. Instead, we'll create our one-page to keep track of our project objectives and some background information.

The Hero Born one-page

To keep us on track going forward, I've put together a simple document that lays out the basics of the game prototype. Read through it before moving on, and try to start imagining some of the programming concepts that we've learned so far being put into practice:

Now that you have a high-level view of the bones of our game, you're ready to start building a prototype level to house the game experience.

Building a level

When building your game levels, it's always a good idea to try to see things from the perspective of your players. How do you want them to see the environment, interact with it, and feel while walking around in it? You're literally building the world your game exists in, so be consistent. 

With Unity, you have the option of creating outdoor environments using the Terrain tool, blocking out something set indoors with basic shapes and geometry, or a mixture of the two. You can even import 3D models from other programs, such as Blender, to use as objects in your scenes. 

Unity has a great introduction to the Terrain tool at https://docs.unity3d.com/Manual/script-Terrain.html. If you're going that route, there's also a wonderful free asset on the Unity Asset Store called Terrain Toolkit 2017, at https://assetstore.unity.com/packages/tools/terrain/terrain-toolkit-2017-83490.

For Hero Born, we'll stick with a simple indoor arena-like setting that's easy to get around, but with a few corners to hide in. You'll cobble all this together using primitives—base object shapes provided in Unity—because of how easy they are to create, scale, and position in a scene.

Creating primitives

Looking at games you might play regularly, you're probably wondering how you'll ever create models and objects that look so realistic you could reach through the screen and grab them. Fortunately, Unity has a set of primitive GameObjects that you can select from to prototype faster. These won't be super fancy or high-definition, but they are a lifesaver when you're learning the ropes or don't have a 3D artist on your development team. 

If you go into the Hierarchy panel and click on Create | 3D Object, you'll see all the available options, but only about half of these are primitives or common shapes:

Other 3D object options, such as Terrains, Wind Zones, and Trees, are a bit too advanced for what we need, but feel free to experiment with them if you're interested. Before we jump too far ahead, the arena needs a floor, so let's create one.

Time for action – creating a ground plane

It's usually easier to walk around when you've got a floor underneath you, so let's start by creating a ground plane using the following steps:

In the Hierarchy panel, click on Create | 3D Object | Plane.

Rename the GameObject to Ground in the Inspector tab:

Change the Scale to 3 in the X, Y, and Z axes:

If the lighting in your scene looks dimmer or different from the preceding screenshot, increase the Intensity value of the Directional Light component as follows:

We created a plane GameObject and increased its size to make more room for our future character to walk around. This plane will act like a 3D object bound by real-life physics, meaning other objects can't just fall through. We'll talk more about the Unity physics system and how it works in Chapter 7, Movement, Camera Controls, and Collisions. Right now, we need to start thinking in 3D.

Thinking in 3D

Now that we have our first object in the scene, we can talk about 3D space—specifically, how an object's position, rotation, and scale behave in three dimensions. If you think back to high school geometry, a graph with an x and y coordinate system should be familiar. To put a point on the graph, you had to have an x value and a y value.

Unity supports both 2D and 3D game development, and if we were making a 2D game, we could leave our explanation there. However, when dealing with 3D space in the Unity Editor, we have an extra axis, called the z-axis. The z-axis maps depth, or perspective, giving our space and the objects in it their 3D quality.

This might be confusing at first, but Unity has some nice visual aids to help you get your head on straight. In the top-right of the Scene panel, you'll see a geometric-looking icon with the x, y, and z axes marked in red, green, and blue, respectively. All GameObjects in the scene will show their axis arrows when they're selected:

This will always show the current orientation of the scene and the objects placed inside it. Clicking on any of these colored axes will switch the scene orientation to the selected axis. Give this a go by yourself to get comfortable with switching perspectives.

If you take a look at the Ground object's Transform component in the following screenshot, you'll see that the position, rotation, and scale are all determined by these three axes. The position determines where the object is placed in the scene, its rotation governs how it's angled, and its scale takes care of its size:

This leads to an interesting question: what are the origin points, of these positions, rotations, and scales that we're setting? The answer is that they depend on what relative space we're using, which, in Unity, is either World or Local:

World space uses a set origin point in the scene as a constant reference for all GameObjects. In Unity, this origin point is (0, 0, 0), or 0 on the x, y, and z-axis, which you can see in the preceding Ground object's Transform component.

Local space uses the objects parent transform as its origin, essentially changing the perspective of the scene to revolve around it.

Both of these orientations are useful in different situations, which we'll talk about later in this chapter when we start laying out the rest of the arena level. Right now, the ground is looking a little boring. Let's change that with a material.

Materials

Our ground plane isn't very interesting right now, but we can use Materials to breathe a little life into the level. Materials control how GameObjects are rendered in the scene, which is determined by the material's shader. Think of Shaders as being responsible for combining lighting and texture data into a representation of how the material looks.

Each GameObject starts with a default Material and Shader (pictured here from the Inspector), setting its color to a standard white:

To change an object's color, we need to create a material and drag it to the object that we want to modify. Remember, everything is an object in Unity—materials are no different. Materials can be reused on as many GameObjects as needed, but any change to a Material will also carry through to any objects the material is attached to. If we had several enemy objects in the scene with a material that set them all to red, and we changed that base material color to blue, all our enemies would then be blue.

Blue is eye-catching; let's change the color of the ground plane to match.

Time for action – changing the ground color

Let's create a new material to turn the ground plane from a dull white to a dark and vibrant blue:

Create a new folder in the Project panel and name it Materials.

Inside the Materials folder, click on Create | Material, and name it Ground_Mat.

Click on the color box next to the Albedo property and select your color from the Color Picker window that pops up, then close it.

Drag the Ground_Mat object and drop it onto the Ground GameObject in the Hierarchy panel:

The new material you created is now a project asset. Dragging and dropping Ground_Mat into the Ground GameObject changed the color of the plane, which means any changes to Ground_Mat will be reflected in the Ground:

The ground is our canvas; however, in 3D space, it can support other 3D objects on its surface. It'll be up to you to populate it with fun and interesting obstacles for your future players.

White-boxing

White-boxing is a design term for laying out ideas using placeholders, usually with the intent of replacing them with finished assets at a later date. In level design, the practice of white boxing is to block out an environment with primitive GameObjects to get a sense of how you want it to look. This is a great way to start things off, especially during the prototyping stages of your game.

Before diving into Unity, I'd like to start with a simple sketch of the basic layout and position of my level. This gives us a bit of direction and will help to get our environment laid out quicker. In the following drawing, you'll be able to see the arena I have in mind, with a raised platform in the middle that is accessible by ramps, complete with small turrets in each corner:

Don't worry if you're not an artist—neither am I. The important thing is to get your ideas down on paper to solidify them in your mind and work out any kinks before getting busy in Unity.

Before you go ahead full steam and put this sketch into production, you'll need to familiarize yourself with a few Unity Editor shortcuts to make white boxing easier.

Editor tools

When we discussed the Unity interface in Chapter 1, Getting to Know Your Environment, we skimmed over some of the Toolbar functionality, which we need to revisit so that we know how to efficiently manipulate GameObjects: 

Let's break down the different tools that are available to us from the Toolbar in the preceding screenshot:

Hand: This allows you to pan and change your position in the scene.

Move: This lets you move objects along the x, y, and z axes by dragging their respective arrows.

Rotate: This lets you adjust an object's rotation by turning or dragging their respective markers.

Scale: This lets you modify an object's scale by dragging to specific axes.

Rect Transform: This combines the move, rotate, and scale tool functionality into one package. 

Move, Rotate, and Scale: This gives you access to the position, rotation, and scale of an object all at once.

Custom Editor Tools: This allows you to access any custom tools you've built for the editor. Don't worry about this one, as it's way out of our scope. If you want to know more, please refer to the documentation at https://docs.unity3d.com/2020.1/Documentation/ScriptReference/EditorTools.EditorTool.html.

You can find more information about navigating and positioning GameObjects in the Scene panel at https://docs.unity3d.com/Manual/PositioningGameObjects.html.

Panning and navigating the Scene can be done with similar tools, although not from the Unity Editor itself:

To look around, hold down the right mouse button to pan the camera around.

To move around while using the camera, continue to hold the right mouse button and use the W, A, S, and D keys to move forward, back, left, and right, respectively. 

Hit the F key to zoom in and focus on a selected GameObject.

This kind of scene navigation is more commonly known as fly-through mode, so when I ask you to focus on or navigate to a particular object or viewpoint, use a combination of these features.

Getting around the Scene view can be a task in itself sometimes, but it all comes down to repeated practice. For a more detailed list of scene navigation features, visit https://docs.unity3d.com/Manual/SceneViewNavigation.html.

Even though the ground plane won't allow our character to fall through it, we could still walk off the edge at this point. Your job is to wall in the arena so that the player has a confined locomotion area.

Hero's trial – putting up drywall

Using primitive cubes and the Toolbar, position four walls around the level using the Move, Rotate, and Scale tools to section off the main arena. Use the following screenshot for reference:

From this chapter onwards, I haven't included any precise wall position, rotation, or scale values because I want you to experiment with the Unity Editor tools. The only way to get comfortable is to dive-in headfirst.

That was a bit of construction, but the arena is starting to take shape! Before we move on to adding in obstacles and platforms, you'll want to get into the habit of cleaning up your object hierarchy. We'll talk about how that works in the following section.

Keeping the hierarchy clean

Normally, I would put this sort of advice in a blurb at the end of a section, but making sure your project hierarchy is as organized as possible is so important that it needed its subsection. Ideally, you'll want all related GameObjects to be under a single parent object. Right now, it's not a risk because we only have a few objects in the scene; however, when that gets into the hundreds on a big project, you'll be struggling.

The easiest way to keep your hierarchy clean is to store related objects in a parent object, just as you would with files inside a folder on your desktop.

Time for action – using empty objects

Our level has a few objects that could use some organization, and Unity makes this easy by letting us create empty GameObjects. An empty object is a perfect container for holding related groups of objects because it doesn't come with any components attached—it's a shell. 

Let's take our ground plane and four walls and group them all under a common empty GameObject:

Select Create | Create Empty in the Hierarchy panel and name the new object Environment.

Drag and drop the ground plane and the four walls into Environment, making them child objects.

Select the Environment empty object and check that its X, Y, and Z positions are all set to 0:

The environment exists in the Hierarchy tab as a parent object, with the arena objects as its children. Now we're able to expand or close the Environment object drop-down list with the arrow icon, making the Hierarchy panel less cluttered.

It's important to set the Environment object's X, Y, and Z positions to 0 because the child object positions are now relative to the parent position. Resetting it at this point starts everyone on an even playing field. 

Working with prefabs

Prefabs are one of the most powerful tools you'll come across in Unity. They come in handy not only in level building but in scripting as well. Think of prefabs as GameObjects that can be saved and reused with every child object, component, C# script, and property setting intact. Once created, a prefab is like a class blueprint; each copy used in a scene is a separate instance of that prefab. Consequently, any change to the base prefab will also change all of the active instances in the scene.

The arena looks a little too simple and completely wide open, making it a perfect place to test out creating and editing prefabs.

Time for action – creating a turret

Since we want four identical turrets in each corner of the arena, they're a perfect case for a prefab, which we can create with the following steps:

Again, I haven't included any precise barrier position, rotation, or scale values because I want you to get up close and personal with the Unity editor tools. 

Going forward, when you see a task ahead of you that doesn't include specific position, rotation, or scale values, I'm expecting you to learn by doing.

Create an empty parent object inside Environment by selecting Create | Create Empty and naming it Barrier_01.

Create two Cube primitives from the Create menu, positioning and scaling them as a v-shaped base.

Create two more Cube primitives and place them on the ends of the turret base:

Create a new folder in the Project panel and name it Prefabs. Then, drag Barrier_01 into it:

Barrier_01, and all its child objects, is now a prefab, meaning that we can reuse it by dragging copies from the Prefabs folder or duplicating the one in the scene. Barrier_01 turned blue in the Hierarchy tab to signify its status change, and also added a row of prefab function buttons in the Inspector tab underneath its name:

Any edits to the original prefab object will now affect any copies in the scene. Since we need a third cube to complete the barrier, let's update and save the prefab to see this in action.

Time for action – updating the prefab

Now our turret has a huge gap in the middle, which isn't ideal for covering our character, so let's update the Barrier_01 prefab by adding another cube and applying the change:

Create a Cube primitive and place it at the intersection of the turret base. 

The new Cube primitive will be marked as gray with a little + icon next to its name in the Hierarchy tab. This means it's not officially part of the prefab yet:

Right-click on the new Cube primitive and select Added GameObject | Apply to Prefab 'Barrier_01':

The Barrier_01 prefab is now updated to include the new cube, and the entire prefab hierarchy should be blue again. You now have a turret prefab that looks like the preceding screenshot or, if you're feeling adventurous, something more creative. However, we want these to be in every corner of the arena. It's going to be your job to add them!

Time for action – finishing the level

Now that we've got a reusable barrier prefab, let's build out the rest of the level to match the rough sketch that we had at the beginning of the section:

Duplicate the Barrier_01 prefab three times and place each one in a different corner of the arena.

Create a new empty GameObject and name it Raised_Platform.

Create a Cube and scale it to form a platform.

Create a Plane and scale it into a ramp. Then, rotate and position it so that it connects the platform to the ground.

Duplicate the ramp object by using command + D on a Mac, or Ctrl + D on Windows. Then, repeat the rotation and positioning steps.

Repeat the previous step twice more, until you have four ramps in total leading to the platform:

You've now successfully white-boxed your first game level! Don't get too caught up in it yet, though—we're just getting started. All good games have items that players can pick up or interact with. In the following challenge, it's your job to create a health item and make it a prefab.

Hero's trial – creating a health pickup

Putting everything we've learned so far in this chapter might take you a few minutes, but it's well worth the time. Create the pickup item as follows:

Create, position, and scale a Capsule GameObject and name it Health_Pickup.

Create and attach a new yellow-colored Material to the Health_Pickup object.

Drag the Health_Pickup object into the Prefab folder.

Refer to the following screenshot for an example of what the finished product should look like:

That wraps up our work with level design and layout for now. Next up, you're going to get a crash course in lighting with Unity, and we'll learn about animating our item later on in the chapter.

Lighting basics

Lighting in Unity is a broad topic, but it can be boiled down into two categories: real-time and precomputed. Both types of lights take into account properties such as the color and intensity of the light, as well as the direction it is facing in the scene; the difference is how the Unity engine computes how the lights act.

Realtime lighting is computed every frame, meaning that any object that passes in its path will cast realistic shadows and generally behave like a real-world light source. However, this can significantly slow down your game and cost an exponential amount of computing power, depending on the number of lights in your scene. Precomputed lighting, on the other hand, stores the scene's lighting in a texture called a lightmap, which is then applied, or baked, into the scene. While this saves computing power, baked lighting is static. This means that it doesn't react realistically or change when objects move in the scene. 

There is also a mixed type of lighting called Precomputed Realtime Global Illumination, which bridges the gap between the realtime and precomputed processes. This is an advanced Unity-specific topic, so we won't cover it in this book, but feel free to view the documentation at https://docs.unity3d.com/Manual/GIIntro.html.

Let's take a look at how to create light objects in the Unity scene itself.

Creating lights

By default, every scene comes with a Directional Light component to act as a main source of illumination, but lights can be created in the hierarchy like any other GameObject. Even though the idea of controlling light sources might be new to you, they are objects in Unity, which means they can be positioned, scaled, and rotated to fit your needs:

Let's take a look at some examples of realtime light objects and their performance:

Directional lights are great for simulating natural light, such as sunshine. They don't have an actual position in the scene, but their light hits everything as if it's always pointed in the same direction. 

Point lights are essentially floating globes, sending light rays out from a central point in all directions. These have defined positions and intensities in the scene.

Spotlights send light out in a given direction, but they are locked in by their angle. Think of these as spotlights or floodlights in the real world.

Reflection Probes and Light Probe Groups are beyond what we need for Hero Born; however, if you're interested, you can find out more at https://docs.unity3d.com/Manual/ReflectionProbes.html and https://docs.unity3d.com/Manual/LightProbes.html.

Like all GameObjects in Unity, lights have properties that can be adjusted to give a scene a specific ambiance or theme.

Light component properties

The following screenshot shows the Light component on the directional light in our scene. All of these properties can be configured to create immersive environments, but the basic ones we need to be aware of are Color, Mode, and Intensity. These properties govern the light's tint, realtime or computed effects, and general strength:

Like other Unity components, these properties can be accessed through scripts and the Light class, which can be found at https://docs.unity3d.com/ScriptReference/Light.html.

Now that we know a little more about what goes into lighting up a game scene, let's turn our attention to adding some animations!

Animating in Unity

Animating objects in Unity can range from a simple rotation effect to complex character movements and actions, all of which are controlled through the Animation and Animator windows:

The Animation window is where animation segments, called clips, are created and managed using a timeline. Object properties are recorded along this timeline and are then played back to create an animated effect. 

The Animator window manages these clips and their transitions using objects called animation controllers.

You can find more information about the Animator window its controllers at https://docs.unity3d.com/Manual/AnimatorControllers.html.

Animations in Unity are referred to as clips. Creating and manipulating your target objects in these clips will have your game moving in no time.

Creating clips

Any GameObject that you want to apply an animation clip to needs to have an Animator component attached with an Animation Controller set. If there is no controller in the project when a new clip is created, Unity will create one and save it in the location of the clips, which you can then use to manage your clips. Your next challenge is to create a new animation clip for the pickup item.

Time for action – creating a new clip

We're going to start animating the Health_Pickup prefab by creating a new animation clip, which will spin the object around in an infinite loop. To create a new animation clip, we need to perform the following steps:

Navigate to Window | Animation | Animation to open up the Animation panel and pin it next to the Console.

Make sure the Health_Pickup prefab is selected in the Hierarchy, and then click on Create in the Animation panel:

Create a new folder from the following drop-down list, name it Animations, and then name the new clip Pickup_Spin:

Make sure the new clip shows up in the Animation panel:

Since we didn't have any Animator controllers, Unity created one for us in the Animation folder called Health_Pickup. With Health_Pickup selected, when we created the clip, an Animator component was also added to the prefab for us with the Health_Pickup controller set:

When you think of motion clips, like in movies, you may think of frames. As the clip moves through its frames, the animation advances, giving the effect of movement. It's no different in Unity; we need to record our target object in different positions throughout different frames so that Unity can play the clip.

Recording keyframes

Now that we have a clip to work with, you'll see a blank timeline in the Animation window. Essentially, when we modify our Health_Pickup prefab's z rotation, or any other property that can be animated, the timeline will record those changes as keyframes. Unity then assembles those keyframes into your complete animation, similar to how individual frames on analog film play together into a moving picture.

Take a look at the following screenshot and remember the locations of the record button and the timeline:

Now, let's get our item spinning.

Time for action – spinning animation

For the spinning animation, we want the Health_Pickup prefab to make a complete 360-degree rotation on its z axis every second, which can be done by setting three keyframes and letting Unity take care of the rest:

Select Add Property | Transform and click on the + sign next to Rotation:

Click on the Record button to start the animation.

Place your cursor at 0:00 on the timeline but leave the Health_Pickup prefab's z rotation at 0:

a. Place your cursor at 0:30 on the timeline, and set the Z rotation to 180.

b. Place your cursor at 1:00 on the timeline, and set the Z rotation to 360:

Click on the Record button to finish the animation.

Click on the Play button to the right of the record button to see the animation loop.

The Health_Pickup object now rotates on the z-axis between 0, 180, and 360 degrees every second, creating the looping spin animation. If you play the game now, the animation will run indefinitely until the game is stopped:

All animations have curves, which determine specific properties of how an animation executes. We won't be doing too much with these, but it's important to understand the basics. We'll get into them in the following section.

Curves and tangents

In addition to animating an object property, Unity lets us manage how the animation plays out over time with animation curves. So far, we've been in Dopesheet mode, which you can change at the bottom of the Animation window. If you click onto the Curves view (pictured in the following screenshot), you'll see a different graph with accent points in place of our recorded keyframes. We want the spinning animation to be smooth—what we call linear—so we'll leave everything as is. However, speeding up, slowing down, or altering the animation at any point in its run can be done by dragging or adjusting the points on the curve graph:

With animation curves handling how properties act over time, we still need a way to fix the stutter that occurs every time the Health_Pickup animation repeats. For that, we need to change the animation's Tangent, which manages how keyframes blend from one into another. These options can be accessed by right-clicking on any keyframe on the timeline in Dopesheet mode, which you can see here:

Both of these topics are intermediate advanced, so we won't be delving too deeply into them. If you're interested, you can take a look at the documentation on animation curves and tangent options at https://docs.unity3d.com/Manual/animeditor-AnimationCurves.html.

If you play the spinning animation as it is now, there's a slight pause between when the item completes its full rotation and starts a new one. Your job is to smooth that out, which is the subject of the next challenge.

Time for action – smoothing the spin

Let's adjust the tangents on the first and last frames of the animation so that the spinning animation blends seamlessly together when it repeats:

Right-click on the first and last keyframe's diamond icons on the animation timeline and select Auto:

Move the Main Camera so that you can see the Health_Pickup object and click on Play:

Changing the first and last keyframe tangents to Auto tells Unity to make their transitions smooth, which eliminates the jerky stop/start motion when the animation loops. 

Objects can also be animated using C# by manipulating specific properties, such as position or rotation. Even though we won't be going into that specific topic in this book, it's important to know that programmed animations are an option in Unity.

That's all the animation you'll need for this book, but I'd encourage you to check out the full toolbox that Unity offers in this area. Your games will be more engaging and your players will thank you! Next up, we'll take a brief look at the Unity particle system and how effects are added into a scene.

The particle system

When it comes to creating motion effects, such as explosions or the jet stream of an alien spaceship, Unity's particle effects are the way to go. Particle systems emit sprites or meshes, which we call particles, which then create a combined effect. Particle properties can be configured from their color and size to how long they stay onscreen and how fast they move in a given direction. These can be created as single objects or combined to create more realistic effects.

Particle system effects can be extremely complex, and they can be used to create almost anything you can imagine. However, getting good at creating realistic effects takes practice—take a look at these instructions for a place to start: https://docs.unity3d.com/Manual/ParticleSystemHowTo.html.

The pickup item is still looking a little bland, even though it's spinning. Let's add an effect to make it stand out.

Time for action – adding sparkle effects

We want to draw the player's eye to the collectible items that we'll place around the level, so let's add a simple particle effect to the Health_Pickup object:

Click on Create | Effects | Particle System in the Hierarchy panel:

Position the Particle System object in the middle of the Health_Pickup object.

Select the new particle system and update the following properties in the Inspector tab:

Start Lifetime to 2

Start Speed to 0.25

Start Size to 0.75

Start Color to orange or a color of your choice

Open the Emissions tab and change the Rate Over Time to 5.

Open the Shape tab and change the Shape to Sphere:

The Particle System object we created will now render and emit particles in every frame based on the properties we set in the Inspector tab.

Summary

We made it to the end of another chapter that had a lot of moving parts, especially for those of you who are new to Unity. Even though this book is focused on the C# language, we still need to take time to get an overview of game development, documentation, and the non-scripting features of the engine. While we didn't have time for in-depth coverage of the Lighting, Animation, and Particle System tools, it's worth getting to know them if you're thinking about continuing to create Unity projects.

In the next chapter, we'll be switching our focus back to programming Hero Born's core mechanics, starting with setting up a moveable player object, controlling the camera, and understanding how Unity's physics system governs the game world.

Pop quiz – basic Unity features

Cubes, capsules, and spheres are examples of what kind of GameObject?

What axis does Unity use to represent depth, which gives scenes their 3D appearance?

How do you turn a GameObject into a reusable prefab?

What unit of measurement does the Unity animation system use to record object animations?

Movement, Camera Controls, and Collisions

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

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

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

Transform movement and rotation

Managing player inputs

Scripting camera behavior

Unity physics and applied force

Basic colliders and collision detection

Moving the player

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

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

Option A: Use a GameObject's Transform component for movement and rotation. This is the easiest solution and the one we'll be working with first.

Option B: Attach a Rigidbody component to a GameObject and apply force in code. This solution relies on Unity's physics system to do the heavy lifting, delivering a far more realistic effect. We'll update our code to use this approach later on in this chapter to get a feel for both methods. 

Unity suggests sticking to a consistent approach when moving or rotating a GameObject; either manipulate an object's Transform or Rigidbody component, but never both at the same time.

Option C: Attach a ready-made Unity component or prefab, such as Character Controller or FirstPersonController. This cuts out the boilerplate code and still delivers a realistic effect while speeding up prototyping time.

You can find more information on the Character Controller component and its uses at https://docs.unity3d.com/ScriptReference/CharacterController.html.

The FirstPersonController prefab is available from the Standard Assets Package, which you can download from https://assetstore.unity.com/packages/essentials/asset-packs/standard-assets-32351.

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

Player setup

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

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

Time for action – creating the player capsule

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

Click on Create | 3D Object | Capsule from the Hierarchy panel and name it Player.

Select the Player GameObject and click on Add Component at the bottom of the Inspector tab. Search for Rigidbody and hit Enter to add it.

Expand the Constraints property at the bottom of the Rigidbody component:

Check the boxes for Freeze Rotation on the X and Y axes.

Select the Materials folder and click on Create | Material. Name it Player_Mat.

Change the Albedo property to a bright green and drag the material to the Player object in the Hierarchy panel:

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

Understanding vectors

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

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

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

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

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

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

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

Vector3 forwardDirection = Vector3.forward;

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

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

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

Getting player input

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

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

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

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

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

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

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

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

Time for action – player locomotion

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

Create a new C# script in the Scripts folder, name it PlayerBehavior, and drag it into the Player capsule.

Add the following code and save:

 public class PlayerBehavior : MonoBehaviour
 {
     // 1
     public float moveSpeed = 10f;
     public float rotateSpeed = 75f;

     // 2
     private float vInput;
     private float hInput;

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

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

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

         // 6
         this.transform.Rotate(Vector3.up * hInput * Time.deltaTime);
     }
 }

Using the this keyword is optional. Visual Studio 2019 may suggest that you remove it to simplify the code, but I prefer leaving it in for clarity.

When you have empty methods, such as Start, in this case, it's common to delete them for clarity. However, if you prefer to have them in your script, that's fine too. It's really up to your preference.

Here's a breakdown of the preceding code:

Declares two public variables to be used as multipliers:

movespeed for how fast we want the Player to go forward and backward

rotateSpeed for how fast we want the Player to rotate left and right

Declares two private variables to hold inputs from the player; initially set with no value:

vInput will store the vertical axis input.

hInput will store the horizontal axis input.

Input.GetAxis("Vertical") detects when the up arrow, down arrow, W, or S keys are pressed and multiplies that value by moveSpeed:

The up arrow and W keys return a value of 1, which will move the player in the forward (positive) direction.

The down arrow and S keys return -1, which moves the player backward in the negative direction.

Input.GetAxis("Horizontal") detects when the left arrow, right arrow, A, and D keys are pressed and multiplies that value by rotateSpeed:

The right arrow and D keys return a value of 1, which will rotate the capsule to the right.

The left arrow and A keys return a -1, rotating the capsule to the left.

If you're wondering whether it's possible to do all the movement calculations on one line, the simple answer is yes. However, it's better to have your code broken down, even if you're the only one reading it.

Uses the Translate method, which takes in a Vector3 parameter, to move the capsule's Transform component:

Remember that the this keyword specifies the GameObject the current script is attached to, which, in this case, is the player capsule. 

 Vector3.forward multiplied by vInput and Time.deltaTime supplies the direction and speed the capsule needs to move forward or back along the z axis at the speed we've calculated.

Time.deltaTime will always return the value in seconds since the last frame of the game was executed. It's commonly used to smooth values that are captured or run in the Update method instead of letting it be determined by a device's frame rate.

Uses the Rotate method to rotate the capsule's Transform component relative to the vector we pass in as a parameter:

Vector3.up multiplied by hInput and Time.deltaTime gives us the left/right rotation axis we want.

We use the this keyword and Time.deltaTime here for the same reasons.

As we discussed earlier, using direction vectors in the Translate and Rotate functions is only one way to go about this. We could have created new Vector3 variables from our axis inputs and used them as parameters just as easily.

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

Adding a following Camera

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

Time for action – scripting camera behavior

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

Create a new C# script in the Scripts folder, name it CameraBehavior, and drag it into the Main Camera.

Add the following code and save it:

 public class CameraBehavior : MonoBehaviour
 {
     // 1
     public Vector3 camOffset = new Vector3(0f, 1.2f, -2.6f);

     // 2
     private Transform target;

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

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

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

Here's a breakdown of the preceding code:

Declares a Vector3 variable to store the distance we want between the Main Camera and the Player capsule:

We'll be able to manually set the x, y, and z positions of the camera offset in the Inspector because it's public.

These default values are what I think looks best, but feel free to experiment.

Creates a variable to hold the player capsule's Transform information:

This will give us access to its position, rotation, and scale.

We don't want this data to be accessible outside of the CameraBehavior script, which is why it's private.

Uses GameObject.Find to locate the capsule by name and retrieve its Transform property from the scene:

This means the capsule's x, y, and z positions are updated and stored in the target variable every frame.

LateUpdate is a MonoBehavior method, like Start or Update, that executes after Update:

Since our PlayerBehavior script moves the capsule in its Update method, we want the code in CameraBehavior to run after the movement happens; this guarantees that target has the most up-to-date position to reference.

Sets the camera's position to target.TransformPoint(camOffset) for every frame, which creates the following effect:

The TransformPoint method calculates and returns a relative position in the world space.

In this case, it returns the position of the target (our capsule) offset by 0 in the x-axis, 1.2 in the y-axis (putting the camera above the capsule), and -2.6 in the z-axis (putting the camera slightly behind the capsule).

The LookAt method updates the capsule's rotation every frame, focusing on the Transform parameter we pass in, which, in this case, is the target:

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

We created an offset position for the camera.

We found and stored the player capsule's position.

We manually updated its position and rotation every frame so that's it's always following at a set distance and looking at the player.

When using class methods that deliver platform-specific functionality, always remember to break things down to their most basic steps. This will help you to stay above water in new programming environments.

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

Working with Unity physics

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

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

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

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

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

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

Kinematic movement happens when a Rigidbody component is attached to a GameObject, but it doesn't register to the physics system in the scene:

This is only used in certain cases and can be enabled by checking the Is Kinematic property of a Rigidbody component. Since we want our capsule to interact with the physics system, we won't be using this kind of motion.

Non-Kinematic movement is when a Rigidbody component is moved or rotated by applying force rather than manually changing a GameObject's Transform properties. Our goal for this section is to update the PlayerBehavior script to implement this type of motion.

The setup we have now, that is, manipulating the capsule's Transform component while using a Rigidbody component to interact with the physics system, was meant to get you thinking about movement and rotation in a 3D space. However, it's not meant for production and Unity suggests avoiding a mix of kinematic and non-kinematic movement in your code.

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

Rigidbody components in motion

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

You can do it directly by using Rigidbody class methods such as AddForce and AddTorque to move and rotate an object, respectively. This approach has its drawbacks and often requires additional code to compensate for unexpected physics behavior.

Alternatively, you can use other Rigidbody class methods such as MovePosition and MoveRotation, which still use applied force but take care of edge cases behind the scenes. 

We'll take the second route in the next section, but if you're curious about manually applying force and torque to your GameObjects, then start here: https://docs.unity3d.com/ScriptReference/Rigidbody.AddForce.html.

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

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

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

Time for action – accessing the Rigidbody component

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

Update PlayerBehavior with the following changes:

 public class PlayerBehavior : MonoBehaviour
 {
   public float moveSpeed = 10f;
   public float rotateSpeed = 75f;

   private float vInput;
   private float hInput;

    // 1
    private Rigidbody _rb;

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

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

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

Here's a breakdown of the preceding code:

Adds a private Rigidbody-type variable that will contain the capsule's Rigidbody component information.

The Start method fires when a script is initialized in a scene, which happens when you click on Play, and should be used any time variables need to be set at the beginning of a class.

The GetComponent method checks whether the component type we're looking for, in this case, Rigidbody, exists on the GameObject the script is attached to and returns it:

If the component isn't attached to the GameObject, the method will return null, but since we know there's one on the player, we won't worry about error checking right now.

Comments the Transform and Rotate method calls in the Update function so that we won't be running two different kinds of player controls:

We want to keep our code that captures player input so that we can still use it later on.

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

Time for action – moving the Rigidbody component

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

Add in the following code to PlayerBehavior underneath the Update method, and then save the file:

// 1
void FixedUpdate()
{
    // 2
    Vector3 rotation = Vector3.up * hInput;

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

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

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

Here's a breakdown of the preceding code:

Any physics- or Rigidbody-related code always goes inside FixedUpdate, rather than Update or the other MonoBehavior methods:

FixedUpdate is frame rate independent and is used for all physics code. 

Creates a new Vector3 variable to store our left and right rotation:

Vector3.up * hInput is the same rotation vector we used with the Rotate method in the previous example.

Quaternion.Euler takes a Vector3 parameter and returns a rotation value in Euler angles:

We need a Quaternion value instead of a Vector3 parameter to use the MoveRotation method. This is just a conversion to the rotation type that Unity prefers.

We multiply by Time.fixedDeltaTime for the same reason we used Time.deltaTime in Update. 

Calls MovePosition on our _rb component, which takes in a Vector3 parameter and applies force accordingly:

The vector that's used can be broken down as follows: the capsule's Transform position in the forward direction, multiplied by the vertical inputs and Time.fixedDeltaTime.

The Rigidbody component takes care of applying movement force to satisfy our vector parameter.

Calls the MoveRotate method on the _rb component, which also takes in a Vector3 parameter and applies the corresponding forces under the hood:

angleRot already has the horizontal inputs from the keyboard, so all we need to do is multiply the current Rigidbody rotation by angleRot to get the same left and right rotation.

Be aware that MovePosition and MoveRotation work differently for non-kinematic game objects. You can find more information in the Rigidbody scripting reference at https://docs.unity3d.com/ScriptReference/Rigidbody.html.

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

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

Colliders and collisions

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

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

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

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

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

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

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

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

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

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

Time for action – picking up an item

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

Create a new C# script in the Scripts folder, name it ItemBehavior, and then drag it into the Health_Pickup object:

Any script that uses collision detection MUST be attached to a GameObject with a Collider component, even if it's the child of a prefab.

Create an empty GameObject, named Item:

Make the Health_Pickup object and the Particle System object its children.

Drag Item into the Prefabs folder:

Replace the default code in ItemBehavior with the following, and then save it:

 public class ItemBehavior : MonoBehaviour
 {
       // 1
     void OnCollisionEnter(Collision collision)
     {
         // 2
         if(collision.gameObject.name == "Player")
         {
             // 3
             Destroy(this.transform.parent.gameObject);

             // 4
             Debug.Log("Item collected!");
         }
     }
 }

Click on Play and move the player over the capsule to pick it up!

Here's a breakdown of the preceding code:

When another object runs into the Item prefab with its isTrigger turned off, Unity automatically calls the OnCollisionEnter method:

OnCollisionEnter comes with a parameter that stores a reference to the Collider that ran into it.

Notice that the collision is of type Collision, not Collider.

The Collision class has a property, called gameObject, which holds a reference to the colliding GameObject's Collider:

We can use this property to get the GameObject's name and use an if statement to check whether the colliding object is the player.

If the colliding object is the player, we'll call the Destroy() method, which takes in a GameObject parameter:

We want the entire Item prefab object to be destroyed. 

Since ItemBehavior is attached to Health_Item, which is a child object of Pickup_Item, we use this.transform.parent.gameObject to set the Item prefab to be destroyed.

It then prints out a simple log to the console that we have collected an item:

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

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

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

Using Collider triggers

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

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

Time for action – creating an enemy

Use the following steps to create an enemy:

Create a new primitive using Create | 3D Object | Capsule in the Hierarchy panel and name it Enemy.

Inside the Materials folder, use Create | Material, name it Enemy_Mat, and set its Albedo property to a bright red:

Drag and drop Enemy_Mat into the Enemy GameObject.

With Enemy selected, click on Add Component and search for Sphere Collider. Then, hit Enter to add it:

Check the isTrigger property box and change the Radius to 8:

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

Time for action – capturing trigger events

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

Create a new C# script in the Scripts folder, name it EnemyBehavior, and then drag it into Enemy.

Add the following code and save the file:

 public class EnemyBehavior : MonoBehaviour
 {
     // 1
     void OnTriggerEnter(Collider other)
     {
         //2
         if(other.name == "Player")
         {
             Debug.Log("Player detected - attack!");
         }
     }

     // 3
     void OnTriggerExit(Collider other)
     {
         // 4
         if(other.name == "Player")
         {
             Debug.Log("Player out of range, resume patrol");
         }
     }
 }

Click on Play and walk over to the Enemy to set off the first notification:

Walk away from the Enemy to set off the second notification.

Here's a breakdown of the preceding code:

OnTriggerEnter() is fired whenever an object enters the Enemy Sphere Collider radius:

Similar to OnCollisionEnter(), OnTriggerEnter() stores a reference to the trespassing object's Collider component.

Note that other is of type Collider, not Collision.

We can use other to access the name of the colliding GameObject, and check whether it's the Player with an if statement:

If it is, the console prints out a log that the Player is in the danger zone.

OnTriggerExit() is fired when an object leaves the Enemy Sphere Collider radius:

This method also has a reference to the colliding object's Collider component:

We check the object leaving the Sphere Collider radius by name using another if statement:

If it's the Player, we print out another log to the console saying that they're safe:

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

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

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

Hero's trial – all the prefabs!

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

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

Physics roundup

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

Rigidbody components add simulated real-world physics to GameObjects they are attached to.

Collider components interact with each other, as well as objects, using Rigidbody components:

If a Collider component is not a trigger, it acts as a solid object.

If a Collider component is a trigger, it can be walked through.

An object is kinematic if it uses a Rigidbody component and has Is Kinematic checked, telling the physics system to ignore it.

An object is non-kinematic if it uses a Rigidbody component and applied force or torque to power its movement and rotation.

Colliders send out notifications based on their interactions:

These notifications depend on whether the Collider component is set to be triggered or not.

Notifications can be received from either colliding party, and they come with reference variables that hold an object's collision information.

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

Summary

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

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

Pop quiz – player controls and physics

What data type would you use to store 3D movement and rotation information?

What built-in Unity component allows you to track and modify player controls?

Which component adds real-world physics to a GameObject?

What method does Unity suggest using to execute physics-related code on GameObjects?

Scripting Game Mechanics

In the last chapter, we focused on using code to move the player and camera, with a trip into Unity physics on the side. However, controlling a playable character isn't enough to make a compelling game; in fact, it's probably the one area that remains fairly constant across different titles.

A game's unique spark comes from its core mechanics, and the feeling of power and agency those mechanics give to the players. Without fun and engrossing ways to affect the virtual environment you've created, your game doesn't stand a chance of repeat play, to say nothing of fun. As we venture into implementing the game's mechanics, we'll also be upgrading our knowledge of C# and its intermediate-level features.

This chapter will round out the Hero Born prototype by focusing on individually implemented game mechanics, as well as the basics of system design and user interfaces (UIs). You'll be diving into the following topics:

Adding jumps 

Understanding layer masks

Instantiating objects and prefabs

Understanding game manager classes

Getter and setter properties

Keeping score

Scripting UIs

Adding jumps

One great thing about using a Rigidbody component to control player movement is that we can easily add in different mechanics that rely on applied force, such as jumping. In this section, we'll get our player jumping and write our first utility function.

A utility function is a class method that performs some kind of grunt work so that we don't clutter up gameplay code—for instance, wanting to check whether the player capsule is touching the ground to jump.

Before that, you'll need to get acquainted with a new data type called enumerations, which you'll do in the following section.  

Introducing enumerations 

By definition, an enumeration type is a set, or collection, of named constants that belong to the same variable. These are useful when you want a collection of different values, but with the added benefit of them all being of the same parent type.

It's easier to show rather than tell with enumerations, so let's take a look at their syntax in the following code snippet:

enum PlayerAction { Attack, Defend, Flee };

Let's break down how this works, as follows:

The enum keyword declares the type followed by the variable name.

The different values an enum can have are written inside curly brackets, separated by a comma (except for the last item).

The enum has to end with a semicolon, just like all other data types we've worked with.

To declare an enumeration variable, we use the following syntax:

PlayerAction currentAction = PlayerAction.Defend;

Again, we can break this down, as follows:

The type is set as PlayerAction.

The variable is named and set equal to a PlayerAction value.

Each enum constant can be accessed using dot notation.

Enumerations may look simple at first glance, but they are extremely powerful in the right situations. One of their most useful features is the ability to store underlying types, which is the next subject you'll be jumping into.

Underlying types

Enums also come with an underlying type, meaning that each constant inside the curly brackets has an associated value. The default underlying type is int and starts at 0, just like arrays, with each sequential constant getting the next highest number. 

Not all types are created equal—underlying types for enumerations are limited to byte, sbyte, short, ushort, int, uint, long, and ulong. These are called integral types, which are used to specify the size of numeric values that a variable can store.

This is a bit advanced for this book, but you'll be using int in most cases. More information on these types can be found here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum.

For example, our enum PlayerAction values right now are listed as follows, even though they aren't explicitly written out:

enum PlayerAction { Attack = 0, Defend = 1, Flee = 2 };

There's no rule that says underlying values need to start at 0; in fact, all you have to do is specify the first value and then C# increments the rest of the values for us, as illustrated in the following code snippet:

enumPlayerAction { Attack = 5, Defend, Flee };

In the preceding example, Defend equals 6, and Flee equals 7 automatically. However, if we wanted the PlayerAction enum to hold non-sequential values, we could explicitly add them in, like this:

enum PlayerAction { Attack = 10, Defend = 5, Flee = 0};

We can even change the underlying type of PlayerAction to any of the approved types by adding a colon after the enum name, as follows:

enum PlayerAction :  byte { Attack, Defend, Flee };

Retrieving an enum's underlying type takes an explicit conversion, but we've already covered those, so the following syntax shouldn't be a surprise:

enum PlayerAction { Attack = 10, Defend = 5, Flee = 0};

PlayerAction currentAction = PlayerAction.Attack;
int actionCost = (int)currentAction;

Enumerations are extremely powerful tools in your programming arsenal. Your next challenge is to use your knowledge of enumerations to gather more specific user input from the keyboard.

Time for action – pressing the spacebar to jump!

Now that we have a basic grasp of enumeration types, we can capture keyboard input using the KeyCode enum.

Update PlayerBehavior with the following code, save, and hit Play:

public class PlayerBehavior : MonoBehaviour
 {
    public float moveSpeed = 10f;
    public float rotateSpeed = 75f;

     // 1
     public float jumpVelocity = 5f;

     private float vInput;
     private float hInput;

     private Rigidbody _rb;

     void Start()
     {
         _rb = GetComponentRigidbody>();
     }

     void Update()
     {
         // ... No changes needed ...
     }

     void FixedUpdate()
     {
         // 2
         if(Input.GetKeyDown(KeyCode.Space))
         {
             // 3
             _rb.AddForce(Vector3.up * jumpVelocity, ForceMode.Impulse);
         }

         // ... No other changes needed ...
     }
 }

Let's break down this code, as follows:

We create a new variable to hold the amount of applied jump force we want, which we can adjust in the Inspector.

The Input.GetKeyDown() method returns a bool value, depending on whether a specified key is pressed. 

The method accepts a key parameter as either a string or a KeyCode, which is an enumeration type. We specify that we want to check for KeyCode.Space.

We use an if statement to check the method's return value; if it's true, we execute the body of the statement.

Since we already have the Rigidbody component stored, we can pass the Vector3 and ForceMode parameters to RigidBody.AddForce() and make the player jump.

We specify that the vector (or applied force) should be in the up direction, multiplied by jumpVelocity.

The ForceMode parameter determines how the force is applied and is also an enumeration type. Impulse delivers an instant force to an object while taking its mass into account, which is perfect for a jump mechanic.

Other ForceMode choices can be useful in different situations, all of which are detailed here: https://docs.unity3d.com/ScriptReference/ForceMode.html.

If you play the game now, you'll be able to move around and jump when you hit the spacebar. However, the mechanic allows you to keep jumping indefinitely, which isn't what we want. We'll work on limiting our jump mechanic to one at a time in the next section, using something called a layer mask.

Working with layer masks

Think of layer masks as invisible groups that a GameObject can belong to, used by the physics system to determine anything from navigation to intersecting collider components. While more advanced uses of layer masks are outside the scope of this book, we'll create and use one to perform a simple check—whether the player capsule is touching the ground.

Time for action – setting object layers

Before we can check that the player capsule is touching the ground, we need to add all the objects in our level to a custom layer mask. This will let us perform the actual collision calculation with the Capsule Collider component that's already attached to the player. Proceed as follows:

Select any GameObject in the Hierarchy and click on Layer | Add Layer..., as illustrated in the following screenshot:

Add a new layer called Ground by typing the name into the first available slot, as illustrated in the following screenshot:

Select the Environment parent GameObject in the Hierarchy, click on the Layer dropdown, and select Ground. After you have selected the Ground option shown in the following screenshot, click Yes when a dialog appears, asking you if you want to change all child objects: 

By default, layers 0 - 7 are taken up by the Unity engine, which leaves us 24 slots for custom interactions. Here, you've defined a new layer called Ground and assigned every child object of Environment to that layer. Going forward, all the objects on the Ground layer can be checked to see if they intersect with a specific object. You'll use this in the following challenge to make sure the player can perform a jump if it's on the ground; no unlimited jump hacks here.

Time for action – one jump at a time

Since we don't want code cluttering up the Update() method, we'll do our layer mask calculations in a utility function and return a true or false value based on the outcome. To do so, proceed as follows:

Add the following code to PlayerBehavior and play the scene again:

 public class PlayerBehavior : MonoBehaviour
 {
     public float moveSpeed = 10f;
     public float rotateSpeed = 75f;
     public float jumpVelocity = 5f;

     // 1
            public float distanceToGround = 0.1f;

     // 2
            public LayerMask groundLayer;

     private float _vInput;
     private float _hInput;
     private Rigidbody _rb;

     // 3
            private CapsuleCollider _col;

     void Start()
     {
         _rb = GetComponent<Rigidbody>();

         // 4
                     _col = GetComponent<CapsuleCollider>();
     }

     void Update()
     {
         // ... No changes needed ...
     }

     void FixedUpdate()
     {
         // 5
         if(IsGrounded() && Input.GetKeyDown(KeyCode.Space))
         {
             _rb.AddForce(Vector3.up * jumpVelocity,
                 ForceMode.Impulse);
         }

         // ... No other changes needed ...
     }

     // 6
     private bool IsGrounded()
     {
         // 7
         Vector3 capsuleBottom = new Vector3(_col.bounds.center.x,
             _col.bounds.min.y, _col.bounds.center.z);

         // 8
         bool grounded = Physics.CheckCapsule(_col.bounds.center,
            capsuleBottom, distanceToGround, groundLayer,
               QueryTriggerInteraction.Ignore);

         // 9
         return grounded;
     }
}

Set Ground Layer in the Inspector to Ground from the Ground Layer dropdown, as illustrated in the following screenshot:

Let's break down this code, as follows:

We create a new variable for the distance we'll check between the player Capsule Collider and any Ground layer object.

We create a LayerMask variable that we can set in the Inspector and use for the collider detection.

We create a variable to store the player's Capsule Collider component.

We use GetComponent() to find and return the CapsuleCollider attached to the player.

We update the if statement to check whether IsGrounded returns true and the spacebar is pressed before executing the jump code.

We declare the IsGrounded() method with a bool return type.

We create a local Vector3 variable to store the position at the bottom of the player's Capsule Collider, which we'll use to check for collisions with any objects on the Ground layer.

All Collider components have a bounds property, which gives us access to the min, max, and center positions of its x, y, and z axes.

The bottom of the collider is the 3D point at center x, min y, and center z.

We create a local bool to store the result of the CheckCapsule() method that we call from the Physics class, which takes in the following five arguments:

The start of the capsule, which we set to the middle of the Capsule Collider since we only care about checking whether the bottom touches the ground.

The end of the capsule, which is the capsuleBottom position we've already calculated.

The radius of the capsule, which is the distanceToGround already set.

The layer mask we want to check collisions on, set to groundLayer in the Inspector.

The query trigger interaction, which determines whether the method should ignore colliders that are set as triggers. Since we want to ignore all triggers, we used the QueryTriggerInteraction.Ignore enum.

We return the value stored in grounded at the end of the calculation.

We could have done the collision calculation manually, but that would require more complex 3D math than we have time to cover here. However, it's always a good idea to use built-in methods when available.

That was an involved piece of code that we just added into PlayerBehavior, but when you break it down, the only new thing we did was use a method from the Physics class. In plain English, we supplied CheckCapsule() with a start and endpoint, a collision radius, and a layer mask. If the endpoint gets closer than the collision radius to an object on the layer mask, the method returns true—meaning the player is touching the ground. If the player is in a mid-jump position, CheckCapsule() returns false. Since we're checking IsGround in the if statement every frame in Update(), our player's jump skills are only allowed when touching the ground.

That's all you're going to do with the jump mechanic, but the player still needs a way to interact and defend themself against the hordes of enemies that will eventually populate the arena. In the following section, you'll fix that gap by implementing a simple shooting mechanic.

Shooting projectiles

Shooting mechanics are so common that it's hard to think of a first-person game without some variation present, and Hero Born is no different. In this section, we'll talk about how to instantiate GameObjects from prefabs while the game is running, and use the skills we've learned to propel them forward using Unity physics.

You've created objects in previous chapters using class constructors; instantiating objects in Unity is a little different, which you'll learn about in the following section.

Instantiating objects

The concept of instantiating a GameObject in the game is the same as instantiating an instance of a class—both require starting values so that C# knows what kind of object we want to create and where it needs to be created. However, when we instantiate a GameObject in the scene, we can streamline the process by using the Instantiate() method and providing a prefab object, a starting position, and a starting rotation.

Essentially, we can tell Unity to create a given object with all its components and scripts at this spot, looking in this direction, and then manipulate it as needed once it's born in 3D space. Before we instantiate an object, you'll need to create the object prefab itself, which is your next task.

Time for action – creating a projectile prefab

Before we can shoot any projectiles, we'll need a prefab reference, so let's create that now, as follows:

Select Create | 3D Object | Sphere in the Hierarchy panel and name it Bullet.

Change its Scale to 0.15 in the X, Y, and Z axes in the Transform component.

Use the Add Component button to search and add a Rigidbody component, leaving all default properties as they are.

Create a new material in the Materials folder using Create | Material, and name it Orb_Mat:

Change the Albedo property to a deep yellow.

Drag and drop the material onto the Bullet GameObject.

Drag the Bullet into the Prefabs folder and delete it from the Hierarchy, as follows:

You created and configured a bullet prefab GameObject that can be instantiated as many times as we need in the game and updated as needed. This means you're ready for the next challenge—shooting projectiles.

Time for action – adding the shooting mechanic

Now that we have a prefab object to work with, we can instantiate and move copies of the prefab whenever we hit the left mouse button to create a shooting mechanic, as follows:

Update PlayerBehavior with the following code:

 public class PlayerBehavior : MonoBehaviour
 {
     public float moveSpeed = 10f;
     public float rotateSpeed = 75f;
     public float jumpVelocity = 5f;
     public float distanceToGround = 0.1f;
     public LayerMask groundLayer;

     // 1
     public GameObject bullet;
     public float bulletSpeed = 100f;

     private float _vInput;
     private float _hInput;
     private Rigidbody _rb;
     private CapsuleCollider _col;

     void Start()
     {
         // ... No changes needed ...
     }

     void Update()
     {
         // ... No changes needed ...
     }

     void FixedUpdate()
     {
         // ... No other changes needed ...

         // 2
         if (Input.GetMouseButtonDown(0))
         {
             // 3
             GameObject newBullet = Instantiate(bullet,
                this.transform.position + new Vector3(1, 0, 0),
                   this.transform.rotation) as GameObject;

             // 4
             Rigidbody bulletRB =
                 newBullet.GetComponent<Rigidbody>();

             // 5
             bulletRB.velocity = this.transform.forward *
                                            bulletSpeed;
         }
     }

     private bool IsGrounded()
     {
         // ... No changes needed ...
     }
 }

Drag the Bullet prefab into the bullet property of PlayerBehavior in the Inspector, as illustrated in the following screenshot:

Play the game and use the left mouse button to fire projectiles in the direction the player is facing!

Let's break down the code, as follows:

We create two variables: one to store the Bullet prefab, the other to hold the bullet's speed.

We use an if statement to check that Input.GetMouseButtonDown() returns true, just like with Input.GetKeyDown().

GetMouseButtonDown() takes an int parameter to determine which mouse button we want to check for; 0 is the left button, 1 is the right button, and 2 is the middle button or scroll wheel.

Checking for inputs in FixedUpdate can sometimes lead to input loss or even double inputs because it doesn't run once per frame. We're going to leave the code as-is for simplicity, but another solution would be checking for inputs in Update and then applying force or setting the velocity in FixedUpdate. 

We create a local GameObject variable every time the left mouse button is pressed:

We use the Instantiate() method to assign a GameObject to newBullet by passing in the Bullet prefab. We also use the capsule's position to place the new bullet in front of the player to avoid any collisions.

We append as GameObject at the end to explicitly cast the returned object to the same type as newBullet. 

We call GetComponent() to return and store the Rigidbody component on newBullet.

We set the velocity property of the Rigidbody component to the player's transform.forward direction multiplied by bulletSpeed:

Changing the velocity instead of using AddForce() ensures that gravity doesn't pull our bullets down in an arc when fired.

Again, you've significantly upgraded the logic the player script is using. However, the problem now is that your game scene, and hierarchy, are flooded with spent bullet objects. Your next task is to clean those objects up once they've been fired, to avoid any performance issues.

Managing GameObject buildup

Whether you're writing a completely code-based application or a 3D game, it's important to make sure that unused objects are regularly deleted to avoid overloading the program. Our bullets don't exactly play an important role after they are shot; they sort of just keep existing on the floor near whatever wall or object they collided with.

With a mechanic such as shooting, this could result in hundreds, if not thousands, of bullets down the line, which is something we don't want. Your next challenge is to destroy each bullet after a set delay time.

Time for action – destroying bullets

For this task, we can take the skills we've already learned and make the bullets responsible for their self-destructive behavior, as follows:

Create a new C# script in the Scripts folder and name it BulletBehavior.

Drag and drop the BulletBehavior script onto the Bullet prefab in the Prefabs folder and add the following code:

public class BulletBehavior : MonoBehaviour
{
     // 1
    public float onscreenDelay = 3f;

    void Start ()
    {
        // 2
        Destroy(this.gameObject, onscreenDelay);
     }
 }

Let's break down this code, as follows:

We declare a float variable to store how long we want the Bullet prefabs to remain in the scene after they are instantiated.

We use the Destroy() method to delete the GameObject.

Destroy() always needs an object as a parameter. In this case, we use this keyword to specify the object that the script is attached to.

Destroy() can optionally take an additional float parameter as a delay, which we use to keep the bullets on screen for a short amount of time.

Play the game again, shoot some bullets, and watch as they are deleted from the Hierarchy by themselves in the scene after a specific delay. This means that the bullet executes its defined behavior, without another script having to tell it what to do, which is an ideal application of the Component design pattern. We'll talk more about that in Chapter 12, The Journey Continues.

Now that our housekeeping is done, you're going to learn about a key component of any well-designed and organized project—the manager class.

Creating a game manager

A common misconception when learning to program is that all variables should automatically be made public, but in general, this is not a good idea. In my experience, variables should be thought of as protected and private from the start, and only made public if necessary. One way you'll see experienced programmers protect their data is through manager classes, and since we want to build good habits, we'll be following suit. Think of manager classes as a funnel where important variables and methods can be accessed safely. 

When I say safely, I mean just that, which might seem unfamiliar in a programming context. However, when you have different classes communicating and updating data with each other, things can get messy. That's why having a single contact point, such as a manager class, can keep this to a minimum. We'll get into how to do that effectively in the following section.

Tracking player properties

Hero Born is a simple game, so the only two data points we need to keep track of are how many items the player has collected and how much health they have left. We want these variables to be private so that they can only be modified from the manager class, giving us control and safety. Your next challenge is to create a game manager for Hero Born and populate it with helpful functionality.

Time for action – creating a game manager

Game manager classes will be a constant facet of any project you develop in the future, so let's learn how to properly create one, as follows:

Create a new C# script in the Scripts folder and name it GameBehavior.

Usually, this script would be named GameManager, but Unity reserves that name for its uses. If you ever create a script and a cogwheel icon shows up next to its name instead of the C# file icon, that tells you it's restricted.

Create a new empty game object in the Hierarchy by using Create | Create Empty, and name it GameManager.

Attach GameBehavior.cs to the GameManager object, as illustrated in the following screenshot:

Manager scripts, and other non-game files, are set up on empty objects to put them in the scene, even though they don't interact with the actual 3D space. 

Add the following code to GameBehavior:

public class GameBehavior : MonoBehaviour
{
    private int _itemsCollected = 0;
    private int _playerHP = 10;
}

Let's break down this code.

We added two new private variables to hold the number of items picked up and how many lives the player has left; these are private because they should only be modifiable in this class. If they were made public, other classes could change them at will, which could lead to the variables storing incorrect or concurrent data. 

Having these variables declared as private means that you are responsible for how they are accessed. The following topic on get and set properties will introduce you to a standard, safe way to accomplish this task going forward.

The get and set properties

We've got our manager script and private variables set up, but how do we access them from other classes if they're private? While we could write separate public methods in GameBehavior to handle passing new values to the private variables, let's see whether there is a better way of doing things.

In this case, C# provides all variables with get and set properties, which are perfectly suited to our task. Think of these as methods that are automatically fired by the C# compiler whether we explicitly call them or not, similar to how Start() and Update() are executed by Unity when a scene starts. 

get and set properties can be added to any variable, with or without an initial value, as illustrated in the following code snippet: 

public string firstName { get; set; };

OR

public string lastName { get; set; } = "Smith";

However, using them like this doesn't add any additional benefits; for that, you need to include a code block for each property, as illustrated in the following code snippet:

public string FirstName
{
    get {
        // Code block executes when variable is accessed
    }

    set {
        // Code block executes when variable is updated
    }
}

Now, the get and set properties are set up to execute additional logic, depending on where it's needed. We're not done yet though, as we still need to handle the new logic.

Every get code block needs to return a value, while every set block needs to assign a value; this is where having a combination of a private variable, called a backing variable, and a public variable with get and set properties comes into play. The private variable remains protected, while the public variable allows controlled access from other classes, as shown in the following code snippet:

private string _firstName
public string FirstName {
    get {
        return _firstName;
    }

    set {
        _firstName = value;
    }
}

Let's break this down, as follows:

We can return the value stored in the private variable from the get property anytime another class needs it, without actually giving that outside class direct access.

We can update the private variable any time an outside class assigns a new value to the public variable, keeping them in sync. 

The value keyword is a stand-in for whatever new value is assigned.

This can seem a little esoteric without an actual application, so let's update GameBehavior with public variables with getter and setter properties to go along with our existing private variables.

Time for action – adding backing variables

Now that we understand the syntax of the get and set property accessors, we can implement them in our manager class for greater efficiency and code readability.

Update the code in GameBehavior, as follows:

public class GameBehavior : MonoBehaviour
 {
    private int _itemsCollected = 0;

    // 1
     public int Items
     {
         // 2
         get { return _itemsCollected; }

         // 3
         set {
                _itemsCollected = value;
                Debug.LogFormat("Items: {0}", _itemsCollected);
         }
      }

     private int _playerHP = 10;

     // 4
     public int HP
     {
         get { return _playerHP; }
         set {
                _playerHP = value;
                Debug.LogFormat("Lives: {0}", _playerHP);
          }
     }
 }

Let's break down the code, as follows:

We declare a new public variable called Items with get and set properties.

We use the get property to return the value stored in _itemsCollected whenever Items are accessed from an outside class.

We use the set property to assign _itemsCollected to the new value of Items whenever it's updated, with an added Debug.LogFormat() call to print out the modified value of _itemsCollected.

We set up a public variable called HP with get and set properties to complement the private _playerHP backing variable.

Both private variables are now readable, but only through their public counterparts; they can only be changed in GameBehavior. With this setup, we ensure that our private data can only be accessed and modified from specific contact points. This makes it easier to communicate with GameBehavior from our other mechanical scripts, as well as display the real-time data in the simple UI we'll create at the end of the chapter.

Let's test this out by updating the Items property when we successfully interact with an item pickup in the arena.

Time for action – updating item collection

Now that we have our variables set up in GameBehavior, we can update Items every time we collect an Item in the scene, as follows:

Add the following code to ItemBehavior:

public class ItemBehavior : MonoBehaviour
 {
     // 1
     public GameBehavior gameManager;

     void Start()
     {
           // 2
           gameManager = GameObject.Find("Game
              Manager").GetComponent<GameBehavior>();
     }

     void OnCollisionEnter(Collision collision)
     {
         if (collision.gameObject.name == "Player")
         {
             Destroy(this.transform.parent.gameObject);
             Debug.Log("Item collected!");

             // 3
             gameManager.Items += 1;
         }
     }
 }

Hit Play and collect the pickup item to see the new console log print out from the manager script, as illustrated in the following screenshot:

Let's break down the code, as follows:

We create a new variable of the GameBehavior type to store a reference to the attached script.

We use Start() to initialize gameManager by looking it up in the scene with Find() and adding a call to GetComponent(). 

You'll see this kind of code done in a single line quite often in Unity documentation and community projects. This is done for simplicity, but if you feel more comfortable writing out the Find() and GetComponent() calls separately, go right ahead; there's nothing wrong with clear, explicit formatting.

We increment the Items property in the gameManager class in OnCollisionEnter() after the Item prefab is destroyed.

Since we already set up ItemBehavior to take care of collision logic, it's easy to modify OnCollisionEnter() to communicate with our manager class when an item is picked up by the player. Keep in mind that separating functionality like this is what makes the code more flexible and less likely to break as you make changes during development. 

The last piece Hero Born is missing is some kind of interface that displays game data to the player. In programming and game development, this is called a UI. Your final task in this chapter is to familiarize yourself with how Unity creates and handles the UI code.

Adding player polish

At this point, we have several scripts working together to give players access to movement, jumping, collecting, and shooting mechanics. However, we're still missing any kind of display or visual cue that shows our player's stats, as well as a way to win and lose the game. We'll focus on these two topics as we close out this last section.

Graphical UI

UIs are the visual components of any computer system. The mouse cursor, folder icons, and programs on your laptop are all UI elements. For our game, we want a simple display to let our players know how many items they've collected, their current health, and a textbox to give them updates when certain events happen.

UI elements in Unity can be added in the following two ways:

Directly from the Create menu in the Hierarchy panel, just as with any other GameObject

Using the built-in GUI class in code

We want to stick to the code version for this project and add in our three UI elements in the GameBehavior class. This isn't to say that one approach is better than the other, but since we are learning to program, it's a good idea to stay consistent.

The GUI class provides several methods to create and position components; all GUI method calls go in a MonoBehaviour method called OnGUI(). Think of OnGUI() as the Update() method for all things UI; it runs anywhere from one to several times per frame and is the main contact point for programmatic interfaces.

We'll only be covering a small percentage of the GUI class in our next example, but as it has so much to offer, it would be a waste not to take a quick look at the documentation yourself: https://docs.unity3d.com/ScriptReference/GUI.html.                                                             
If you're curious about non-programmatic UI, take a tour of Unity's video tutorial series: https://unity3d.com/learn/tutorials/s/user-interface-ui.

Your next task is to add a simple UI to the game scene that displays the items collected and player health variables stored in GameBehavior.

Time for action – adding UI elements

We don't have that much information to display to the player at this point, but what we do have should be put on screen in a pleasing, noticeable way. Proceed as follows:

Update GameBehavior with the following code to collect an item:

 public class GameBehavior : MonoBehaviour
 {
     // 1
     public string labelText = "Collect all 4 items and win
     your freedom!";

     public int maxItems = 4;

     private int _itemsCollected = 0;
     public int Items
     {
         get { return _itemsCollected; }
         set {
             _itemsCollected = value;

             // 2
             if(_itemsCollected >= maxItems)
             {
                 labelText = "You've found all the items!";
             }
             else
             {
                 labelText = "Item found, only " + (maxItems -
                    _itemsCollected) + " more to go!";
             }
         }
     }

     private int _playerHP = 3;
     public int HP
     {
         get { return _playerHP; }
         set {
             _playerHP = value;
             Debug.LogFormat("Lives: {0}", _playerHP);
         }
     }

     // 3
     void OnGUI()
     {
         // 4
         GUI.Box(new Rect(20, 20, 150, 25), "Player Health:" +
             _playerHP);

         // 5
         GUI.Box(new Rect(20, 50, 150, 25), "Items Collected: " +
            _itemsCollected);

         // 6
         GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height -
            50, 300, 50), labelText);
     }
 }

Run the game and take a look at our new onscreen GUI boxes, shown in the following screenshot:

Let's break down the code, as follows:

We create two new public variables:

One for the text we want to show at the bottom of the screen

The other for the max number of items in the level

We declare an if statement in the set property of _itemsCollected.

If the player has gathered more than or equal to maxItems, they've won, and labelText is updated. 

Otherwise, labelText shows how many items are still left to collect.

We declare the OnGUI() method to house the UI code.

We create a GUI.Box() with specified dimensions and a string message:

The Rect class constructor takes in x, y, width, and height values.

A Rect position always starts on the upper-left side of the screen.

new Rect(20, 20, 150, 25) translates to a 2D box in the upper-left corner, 20 units from the left edge, 20 units from the top, with a width of 150 and a height of 25.

We create another GUI.Box() underneath the health box to display the current item count.

We create a GUI.Label() centered at the bottom of the screen to display labelText:

Because OnGUI() fires at least once per frame, whenever the value of labelText changes, it will be updated immediately on the screen.

Instead of manually calculating the middle of the screen, we use the Screen class width and height properties to give us absolute values.

When we play the game now, our three UI elements show up with the correct values; when an Item is collected, labelText and _itemsCollected counts update, as illustrated in the following screenshot:

Every game can either be won or lost. In the last section of this chapter, your task is to implement those conditions and the UI that goes along with it.

Win and loss conditions

We've implemented our core game mechanics and a simple UI, but Hero Born is still missing an important game design element: its win and loss conditions. These conditions will manage how the player wins or loses the game and execute different code depending on the situation. 

Back in the game document from Chapter 6, Getting Your Hands Dirty with Unity, we set out our win and loss conditions as follows:

Collecting all items in the level with at least 1 health point remaining to win

Taking damage from enemies until health points are at 0 to lose

These conditions are going to affect both our UI and game mechanics, but we've already set up GameBehavior to handle this efficiently. Our get and set properties will handle any game-related logic, while OnGUI() will handle changes to the UI when a player wins or loses. 

We're going to implement the win condition logic in this section because we have the pickup system already in place. When we get to the enemy AI behavior in the next chapter, we'll add in the loss condition logic. Your next task is to determine when the game is won in code.

Time for action – winning the game 

We always want to give players clear and immediate feedback, so we'll start by adding in the logic for a win condition, as follows:

Update GameBehavior to match the following code:

      public class GameBehavior : MonoBehaviour
      {
        // 1
        public bool showWinScreen = false;

        private int _itemsCollected = 0;
        public int Items
       {
          get { return _itemsCollected; }
          set {
              _itemsCollected = value;

             if (_itemsCollected >= maxItems)
             {
                 labelText = "You've found all the items!";

                 // 2
                 showWinScreen = true;
             }
             else
             {
                 labelText = "Item found, only " + (maxItems -
                    _itemsCollected) + " more to go!";
             }
         }
      }

     // ... No changes needed ...

        void OnGUI()
        {
          // ... No changes to GUI layout needed ...

           // 3
           if (showWinScreen)
           {
             // 4
             if (GUI.Button(new Rect(Screen.width/2 - 100,
                Screen.height/2 - 50, 200, 100), "YOU WON!"))
             {

             }
         }
     }
 }

Change Max Items to 1 in the Inspector to test out the new screen, as illustrated in the following screenshot:

Let's break down the code, as follows:

We create a new bool variable to track when the win screen should appear.

We set showWinScreen to true in the set property of Items when the player has collected all pickups.

We use an if statement inside OnGUI() to check whether the win screen should be displayed.

We create a clickable GUI.Button() in the middle of the screen with a message for the player:

The GUI.Button() method returns a bool value—true when the button is clicked, false when it's not.

Embedding the GUI.Button() call inside an if statement executes the body of the if statement when the button is clicked.

With Max Items set to 1, the win button will show up on collecting the only Pickup_Item in the scene. Clicking the button doesn't do anything right now, but we'll address that in the following section. 

Using directives and namespaces

Right now, our win condition works as expected, but the player still has control over the capsule and doesn't have a way of restarting the game once it's over. Unity provides a property in the Time class called timeScale, which when set to 0 freezes the game scene. However, to restart the game, we need access to a namespace called SceneManagement that isn't accessible from our classes by default.

A namespace collects and groups a set of classes under a specific name to organize large projects and avoid conflicts between scripts that may share the same names. A using directive needs to be added to a class to access a namespace's classes. 

All C# scripts created from Unity come with three default using directives, shown in the following code snippet:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

These allow access to common namespaces, but Unity and C# offer plenty more that can be added with the using keyword followed by the name of the namespace. 

Time for action – pausing and restarting 

Since our game will need to be paused and restarted when a player wins or loses, this is a good time to use a namespace that isn't included in new C# scripts by default.

Add the following code to GameBehavior and play:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 // 1
 using UnityEngine.SceneManagement;

 public class GameBehavior : MonoBehaviour
 {
     // ... No changes needed ...

     private int _itemsCollected = 0;
     public int Items
     {
         get { return _itemsCollected; }
         set {
             _itemsCollected = value;

             if (_itemsCollected >= maxItems)
             {
                 labelText = "You've found all the items!";
                 showWinScreen = true;

                 // 2
                 Time.timeScale = 0f;
             }
             else
             {
                 labelText = "Item found, only " + (maxItems -
                   _itemsCollected) + " more to go!";
             }
         }
     }

     // ... No other changes needed ...

     void OnGUI()
     {
         // ... No changes to GUI layout needed ...

         if (showWinScreen)
         {
             if (GUI.Button(new Rect(Screen.width/2 - 100,
                 Screen.height/2 - 50, 200, 100), "YOU WON!"))
             {
                 // 3
                 SceneManager.LoadScene(0);

                 // 4
                 Time.timeScale = 1.0f;
             }
         }
     }
 }

Let's break down the code, as follows:

We add the SceneManagement namespace with the using keyword, which handles all scene-related logic that Unity offers.

We set Time.timeScale to 0 to pause the game when the win screen is displayed, which disables any input or movement.

We call LoadScene() when the win screen button is clicked:

LoadScene() takes in a scene index as an int parameter.

Because there is only one scene in our project, we use index 0 to restart the game from the beginning.

We reset the Time.timeScale to the default value of 1 so that when the scene restarts, all controls and behaviors will be able to execute again.

Now, when you collect an item and click on the win screen button, the level restarts, with all scripts and components restored to their original values and set up for another round!

Summary

Congratulations! Hero Born is now in a playable state from a gamer's perspective. We implemented jumping and shooting mechanics, managed physics collisions and spawning objects, and added in a few basic UI elements to display feedback. We even got as far as resetting the level when the player wins.

A lot of new topics were introduced in this chapter, and it's important to go back and make sure you understand what went into the code we wrote. Pay special attention to our discussions on enumerations, get and set properties, and namespaces. From here on in, the code is only going to get more complex as we dive further into the possibilities of the C# language. 

In the next chapter, we'll start working on getting our enemy GameObjects to take notice of our player when we get too close, resulting in a follow-and-shoot protocol that will up the stakes for our player. 

Pop quiz – working with mechanics

What type of data do enumerations store?

How would you create a copy of a Prefab GameObject in an active scene?

Which variable properties allow you to add functionality when their value is referenced or modified?

Which Unity method displays all UI objects in the scene?

Basic AI and Enemy Behavior

Virtual scenarios need conflicts, consequences, and potential rewards to feel real. Without these three things, there's no incentive for the player to care about what happens to their in-game character, much less continue to play the game. And while there are plenty of game mechanics that deliver on one or more of these conditions, nothing beats an enemy that will seek you out and try to end your session.

Programming an intelligent enemy is no easy task, and often goes hand in hand with long working hours and frustration. However, Unity has built-in features, components, and classes we can use to design and implement AI systems in a more user-friendly way. These tools will push the first playable iteration of Hero Born over the finish line and provide a springboard for more advanced C# topics.

In this chapter, we'll focus on the following topics:

The Unity navigation system

Static objects and navigation meshes

Navigation agents 

Procedural programming and logic

Taking and dealing damage

Adding a loss condition

Refactoring techniques 

Let's get started!

Navigating in Unity

When we talk about navigation in real life, it's usually a conversation about how to get from point A to point B. Navigating around virtual 3D space is largely the same, but how do we account for the experiential knowledge we humans have accumulated since the day we first started crawling? Everything from walking on a flat surface to climbing stairs and jumping off of curbs is a skill we learned by doing; how can we possibly program all that into a game without going insane?

Before you can answer any of these questions, you'll need to know what navigation components Unity has to offer.

Navigation components

The short answer is that Unity has spent a lot of time perfecting their navigation system and delivering components that we can use to govern how playable and non-playable characters can get around. Each of the following components comes standard with Unity and has complex features already built-in:

A NavMesh is essentially a map of the walkable surfaces in a given level; the NavMesh component itself is created from the level geometry in a process called baking. Baking a NavMesh into your level creates a unique project asset that holds the navigation data.

If NavMesh is the level map, then a NavMeshAgent is the moving piece on the board. Any object with a NavMeshAgent component attached will automatically avoid other agents or obstacles it comes into contact with. 

The navigation system needs to be aware of any moving or stationary objects in the level that could cause a NavMeshAgent to alter their route. Adding NavMeshObstacle components to those objects lets the system know that they need to be avoided.

While this description of the Unity navigation system is far from complete, it's enough for us to move forward with our enemy behavior. For this chapter, we'll be focusing on adding a NavMesh to our level, setting up the Enemy prefab as a NavMeshAgent, and getting the Enemy prefab to move along a predefined route in a seemingly intelligent way.

We'll only be using the NavMesh and NavMeshAgent components in this chapter, but if you want to spice up your level, take a look at how to create obstacles here: https://docs.unity3d.com/Manual/nav-CreateNavMeshObstacle.html.

Your first task in setting up an "intelligent" enemy is to create a NavMesh over the arena's walkable areas.

Time for action – setting up the NavMesh

Let's set up and configure our level's NavMesh:

Select the Environment GameObject, click on the arrow icon next to Static in the Inspector window, and choose Navigation Static:

Click Yes, change children when the dialogue window pops up to set all the Environment child objects to Navigation Static.

Go to Window | AI | Navigation and select the Bake tab. Leave everything set to their default values and click Bake:

Every object in our level is now marked as Navigation Static, which means that our newly-baked NavMesh has evaluated their accessibility based on its default NavMeshAgent settings. Everywhere you can see a light blue overlay in the preceding screenshot is a perfectly walkable surface for any object with a NavMeshAgent component attached, which is your next task.

Time for action – setting up enemy agents

Let's register the Enemy prefab as a NavMeshAgent:

Select the Enemy prefab, click Add Component in the Inspector window and search for NavMesh Agent. Make sure the Enemy prefab is in the scene is updated:

Click Create | Create Empty from the Hierarchy window and name the GameObject Patrol Route. 

Select Patrol Route, click Create | Create Empty to add a child GameObject, and name it Location 1. Position Location 1 in one of the corners of the level:

Create three more empty child objects in Patrol Route, name them Location 2, Location 3, and Location 4, respectively, and position them in the remaining corners of the level to form a square:

Adding a NavMeshAgent component to the Enemy tells the NavMesh component to take notice and register it as an object that has access to its autonomous navigation features. Creating the four empty game objects in each corner of the level lays out the simple route we want our enemies to eventually patrol; grouping them in an empty parent object makes it easier to reference them in code and makes for a more organized Hierarchy window. All that's left is the code to make the enemy walk the patrol route, which you'll add in the next section. 

Moving enemy agents

Our patrol locations are set and the Enemy prefab has a NavMeshAgent component, but now we need to figure out how to reference those locations and get the enemy moving on its own. To do that, we'll first need to talk about an important concept in the world of software development: procedural programming.

Procedural programming

Even though it's in the name, the idea behind procedural programming can be elusive until you get your head around it; once you do, you'll never see a code challenge the same way.

Any task that executes the same logic on one or more sequential objects is the perfect candidate for procedural programming. You already did a little procedural programming when you debugged arrays, lists, and dictionaries with for and foreach loops. Each time those looping statements executed, you performed the same call to Debug.Log(), iterating over each item sequentially. The idea now is to use that skill to get a more useful outcome.

One of the most common uses of procedural programming is adding items from one collection to another, often modifying them along the way. This works great for our purposes since we want to reference each child object in the empty patrolRoute parent and store them in a list.

Time for action – referencing the patrol locations 

Now that we understand the basics of procedural programming, it's time to get a reference to our patrol locations and assign them to a usable list:

Add the following code to EnemyBehavior:

public class EnemyBehavior : MonoBehaviour
 {
     // 1
     public Transform patrolRoute;

     // 2
     public List<Transform> locations;

     void Start()
     {
         // 3
         InitializePatrolRoute();
     }

           // 4
     void InitializePatrolRoute()
     {
         // 5
         foreach(Transform child in patrolRoute)
         {
             // 6
             locations.Add(child);
         }
     }

     void OnTriggerEnter(Collider other)
     {
         // ... No changes needed ...
     }

     void OnTriggerExit(Collider other)
     {
         // ... No changes needed ...
     }
 }

Select Enemy and drag the Patrol Route object from the Hierarchy window onto the Patrol Route variable in EnemyBehavior:

Hit the arrow icon next to the Locations variable in the Inspector window and run the game to see the list populate:

Let's break down the code:

First, it declares a variable for storing the patrolRoute empty parent GameObject.

Then, it declares a List variable to hold all the child Transform components in patrolRoute.

After that, it uses Start() to call the InitializePatrolRoute() method when the game begins.

Next, it creates InitializePatrolRoute() as a private utility method to procedurally fill locations with Transform values:

Remember that not including an access modifier makes variables and methods private by default.

Then, we use a foreach statement to loop through each child GameObject in patrolRoute and reference its Transform component:

Each Transform component is captured in the local child variable declared in the foreach loop.

Finally, we add each sequential child Transform to the list of locations using the Add() method as we loop through the child objects in patrolRoute:

This way, no matter what changes we make in the Hierarchy window, locations will always be filled in with all the child objects under the patrolRoute parent.

While we could have assigned each location GameObject to locations by dragging and dropping them directly from the Hierarchy window into the Inspector window, it's easy to lose or break these connections; making changes to the location object names, object additions or deletions, or project updates can all throw a wrench into a classes' initialization. It's much safer, and more readable, to procedurally fill GameObject lists or arrays in the Start() method.

Due to that reasoning, I also tend to use GetComponent() in the Start() method to find and store component references attached to a given class instead of assigning them in the Inspector window.

Now, we need the enemy object to follow the patrol route we laid out, which is your next task.

Time for action – moving the enemy

With a list of patrol locations initialized on Start(), we can grab the enemy NavMeshAgent component and set its first destination.

Update EnemyBehavior with the following code and hit Play:

 // 1
 using UnityEngine.AI;

 public class EnemyBehavior : MonoBehaviour
 {
     public Transform patrolRoute;
     public List<Transform> locations;

     // 2
     private int locationIndex = 0;

     // 3
     private NavMeshAgent agent;

     void Start()
     {
         // 4
         agent = GetComponent<NavMeshAgent>();

         InitializePatrolRoute();

         // 5
         MoveToNextPatrolLocation();
     }

     void InitializePatrolRoute()
     {
          // ... No changes needed ...
     }

     void MoveToNextPatrolLocation()
     {
         // 6
         agent.destination = locations[locationIndex].position;
     }

     void OnTriggerEnter(Collider other)
     {
         // ... No changes needed ...
     }

     void OnTriggerExit(Collider other)
     {
         // ... No changes needed ...
     }
 }

Let's break down the code:

First, it adds the UnityEngine.AI using directive so that EnemyBehavior has access to Unity's navigation classes; in this case, NavMeshAgent.

Then, it declares a variable to keep track of which patrol location the enemy is currently walking toward. Since List items are zero-indexed, we can have the Enemy prefab move between patrol points in the order they are stored in locations.

Next, it declares a variable to store the NavMeshAgent component attached to the Enemy GameObject. This is private because no other classes should be able to access or modify it.

After that, it uses GetComponent() to find and return the attached NavMeshAgent component to the agent.

Then, it calls the MoveToNextPatrolLocation() method on Start().

Finally, it declares MoveToNextPatrolLocation() as a private method and sets agent.destination:

destination is a Vector3 position in 3D space.

locations[locationIndex] grabs the Transform item in locations at a given index.

Adding .position references the Transform component's Vector3 position.

Now when our scene starts, locations are filled with patrol points and MoveToNextPatrolLocation() is called to set the destination position of the NavMeshAgent component to the first item at locationIndex 0 in the list of locations. The next step is to have the enemy object move from the first patrol location to all the other locations in sequence.

Time for action – patrolling continuously between locations

Our enemy moves to the first patrol point just fine, but then it stops. What we want is for it to continually move between each sequential location, which will require additional logic in Update() and MoveToNextPatrolLocation(). Let's create this behavior.

Add the following code to EnemyBehavior and hit Play:

 public class EnemyBehavior : MonoBehaviour
 {
     // ... No changes needed ...

     void Start()
     {
         // ... No changes needed ...
     }

     void Update()
     {
         // 1
         if(agent.remainingDistance < 0.2f && !agent.pathPending)
         {
             // 2
             MoveToNextPatrolLocation();
         }
     }

     void MoveToNextPatrolLocation()
     {
         // 3
         if (locations.Count == 0)
             return;

         agent.destination = locations[locationIndex].position;

         // 4
         locationIndex = (locationIndex + 1) % locations.Count;
     }

     // ... No other changes needed ...
 }

Let's break down the code:

First, it declares the Update() method and adds an if statement to check whether two different conditions are true:

remainingDistance returns how far the NavMeshAgent component currently is from its set destination.

pathPending returns a true or false Boolean, depending on whether Unity is computing a path for the NavMeshAgent component.

If the agent is very close to its destination, and no other path is being computed, the if statement returns true and calls MoveToNextPatrolLocation().

Here, we added an if statement to make sure that locations isn't empty before the rest of the code in MoveToNextPatrolLocation() is executed:

If locations is empty, we use the return keyword to exit the method without continuing.

This is referred to as defensive programming, and, coupled with refactoring, it is an essential skill to have in your arsenal as you move toward more intermediate C# topics.

Then, we set locationIndex to its current value, +1, followed by the modulo (%) of locations.Count:

This will increment the index from 0 to 4, then restart it at 0 so that our Enemy prefab moves in a continuous path.

The modulo operator returns the remainder of two values being divided – 2 divided by 4 has a remainder of 2, so 2 % 4 = 2. Likewise, 4 divided by 4 has no remainder, so 4 % 4 = 0.

Dividing an index by the maximum number of items in a collection is a quick way to always find the next item. If you're rusty on the modulo operator, revisit Chapter 2, The Building Blocks of Programming.

We now need to check that the Enemy is moving toward its set patrol location every frame in Update(); when it gets close, MoveToNextPatrolLocation() is fired, which increments locationIndex and sets the next patrol point as the destination. If you drag the Scene view down next to the Console window, as shown in the following screenshot, and hit Play, you can watch the Enemy prefab walk around the corners of the level in a continuous loop:

The enemy now follows the patrol route around the outside the level, but it doesn't seek out the player and attack when it's within a preset range. You'll use the NavAgent component to do just that in the next section.

Enemy game mechanics

Now that our Enemy is on a continuous patrol circuit, it's time to give it some interaction mechanics of its own; there wouldn't be much risk or reward if we left it walking around with no way to act against us.

Seek and destroy

In this section, we'll be focusing on switching the target of the enemies' NavMeshAgent component when the player gets too close and dealing damage if a collision occurs. When the enemy successfully lowers the player's health, it will return to its patrol route until its next run-in with the player. However, we're not going to leave our player helpless; we'll also add in code to track enemy health, detect when an enemy is successfully hit with one of the player's bullets, and when an enemy needs to be destroyed. 

Time for action – changing the agent's destination

Now that the Enemy prefab is moving around on patrol, we need to get a reference to the player's position and change the destination of NavMeshAgent.

Add the following code to EnemyBehavior and hit Play:

 public class EnemyBehavior : MonoBehaviour
 {
     // 1
     public Transform player;

     public Transform patrolRoute;
     public List<Transform> locations;

     private int locationIndex = 0;
     private NavMeshAgent agent;

     void Start()
     {
         agent = GetComponent<NavMeshAgent>();

         // 2
         player = GameObject.Find("Player").transform;

         // ... No other changes needed ...
     }

     /* ... No changes to Update,
            InitializePatrolRoute, or
            MoveToNextPatrolLocation ... */

     void OnTriggerEnter(Collider other)
     {
         if(other.name == "Player")
         {
             // 3
             agent.destination = player.position;

             Debug.Log("Enemy detected!");
         }
     }

     void OnTriggerExit(Collider other)
     {
         // .... No changes needed ...
     }
 }

Let's break down the code:

First, it declares a public variable to hold the Player capsule's Transform value.

Then, we use GameObject.Find("Player") to return a reference to the player object in the scene:

Adding .transform directly references the object's Transform value in the same line.

Finally, we set agent.destination to the player's Vector3 position in OnTriggerEnter() whenever the player enters the enemies' attack zone.

If you play the game now and get too close to the patrolling Enemy, you'll see that it breaks from its path and comes straight for you. Once it reaches the player, the code in the Update() method takes over again and the Enemy prefab resumes its patrol. 

We still need the enemy to be able to hurt the player in some way, which we'll learn how to do in the next section.

Time for action – lowering player health

While our enemy mechanic has come a long way, it's still anti-climactic to have nothing happen when the Enemy prefab collides with the Player prefab. To fix this, we'll tie in the new enemy mechanics with the game manager.

Update PlayerBehavior with the following code and hit Play:

 public class PlayerBehavior : MonoBehaviour
 {
     // ... No changes to public variables needed ...

     private float _vInput;
     private float _hInput;
     private Rigidbody _rb;
     private CapsuleCollider _col;

     // 1
     private GameBehavior _gameManager;

     void Start()
     {
         _rb = GetComponent<Rigidbody>();
         _col = GetComponent<CapsuleCollider>();

         // 2
         _gameManager = GameObject.Find("Game
             Manager").GetComponent<GameBehavior>();
     }

     /* ... No changes to Update,
            FixedUpdate, or
            IsGrounded ... */

     // 3
     void OnCollisionEnter(Collision collision)
     {
         // 4
         if(collision.gameObject.name == "Enemy")
         {
             // 5
             _gameManager.HP -= 1;
         }
     }
 }

Let's break down the code:

First, it declares a private variable to hold the reference to the instance of GameBehavior we have in the scene.

Then, it finds and returns the GameBehavior script that's attached to the Game Manager object in the scene:

Using GetComponent() on the same line as GameObject.Find() is a common way to cut down on unnecessary lines of code.

Since our Player is the object being collided with, it makes sense to declare OnCollisionEnter() in PlayerBehavior.

Next, we check for the name of the colliding object; if it's the Enemy prefab, we execute the body of the if statement.

Finally, we subtract 1 from the public HP variable using the _gameManager instance.

Whenever the Enemy tracks and collides with the player now, the game manager will fire the set property on HP. The UI will update with a new value for player health, which means we have an opportunity to put in some additional logic for the loss condition later on.

Time for action – detecting bullet collisions

Now that we have our loss condition, it's time to add in a way for our player to fight back and survive enemy attacks.

Open up EnemyBehavior and modify it with the following code:

 public class EnemyBehavior : MonoBehaviour
 {
     public Transform player;
     public Transform patrolRoute;
     public List<Transform> locations;

     private int locationIndex = 0;
     private NavMeshAgent agent;

     // 1
     private int _lives = 3;
     public int EnemyLives
     {
         // 2
         get { return _lives; }

         // 3
         private set
         {
             _lives = value;

             // 4
             if (_lives <= 0)
             {
                 Destroy(this.gameObject);
                 Debug.Log("Enemy down.");
             }
         }
     }

     /* ... No changes to Start,
            Update,
            InitializePatrolRoute,
            MoveToNextPatrolLocation,
            OnTriggerEnter, or
            OnTriggerExit ... */

     void OnCollisionEnter(Collision collision)
     {
         // 5
         if(collision.gameObject.name == "Bullet(Clone)")
         {
             // 6
             EnemyLives -= 1;
             Debug.Log("Critical hit!");
         }
     }
 }

Let's break down the code:

First, it declares a private int variable called _lives with a public backing variable called EnemyLives. This will let us control how EnemyLives is referenced and set, just like in GameBehavior.

Then, we set the get property to always return _lives.

Next, we use a private set to assign the new value of EnemyLives to _lives to keep them both in sync.

We haven't seen a private get or set before, but they can have their access modifiers, just like any other executable code. Declaring a get or set as private means that only the parent class has access to their functionality.

Then, we add an if statement to check whether _lives is less than or equal to 0, meaning that the Enemy should be dead:

When that's the case, we destroy the Enemy GameObject and print out a message to the console.

Because Enemy is the object getting hit with bullets, it's sensible to put a check for those collisions in EnemyBehavior with OnCollisionEnter().

Finally, if the name of the colliding object matches a bullet clone object, we decrement EnemyLives by 1 and print out another message.

Notice that the name we're checking for is "Bullet(Clone)", even though our bullet prefab is named Bullet. This is because Unity adds the (Clone) suffix to any object created with the Instantiate() method, which is how we made them in our shooting logic.

You can also check for the GameObjects' tag, but since that's a Unity-specific feature, we're going to leave the code as-is and do things with pure C#. 

Now, the player can fight back when the enemy tries to take one of its lives by shooting it three times and destroying it. Again, our use of the get and set properties to handle additional logic proves to be a flexible and scalable solution. With that done, your last task is to update the game manager with a loss condition.

Time for action – updating the game manager

To fully implement the loss condition, we need to update the manager class.

Open up GameBehavior and add the following code. Then, get the Enemy prefab to collide with you three times:

 public class GameBehavior : MonoBehaviour
 {
     public string labelText = "Collect all 4 items and win your
     freedom!";
     public int maxItems = 4;
     public bool showWinScreen = false;

     // 1
     public bool showLossScreen = false;

     private int _itemsCollected = 0;
     public int Items
     {
         // ... No changes needed ...
     }

     private int _playerHP = 3;
     public int HP
     {
         get { return _playerHP; }
         set {
             _playerHP = value;

             // 2
             if(_playerHP <= 0)
             {
                 labelText = "You want another life with that?";
                 showLossScreen = true;
                 Time.timeScale = 0;
             }
             else
             {
                 labelText = "Ouch... that's got hurt.";
             }
         }
     }

     void OnGUI()
     {
         // ... No changes needed ...

         // 3
         if(showLossScreen)
         {
            if (GUI.Button(new Rect(Screen.width / 2 - 100,
              Screen.height / 2 - 50, 200, 100), "You lose..."))
             {
                 SceneManager.LoadScene(0);
                 Time.timeScale = 1.0f;
             }
         }
     }
 }

Let's break down the code:

First, it declares a public bool to keep track of when the GUI needs to display the loss screen button.

Then, it adds in an if statement to check when _playerLives drops below 0:

If it's true, labelText, showLossScreen, and Time.timeScale are all updated.

If the player is still alive after an Enemy collision, labelText shows a different message.

Finally, we continually check whether showLossScreen is true, at which point we create and display a button that matches the dimensions of the win condition button but with different text:

When the user clicks the loss button, the level is restarted and timeScale is reset to 1 so that input and motion are re-enabled.

That's a wrap! You've successfully added a "smart" enemy that can damage the player and be damaged right back, as well as a loss screen through the game manager. Before we finish this chapter, there's one more important topic that we need to discuss, and that's how to avoid repeating code. Repeated code is the bane of all programmers, so it makes sense that you learn how to keep it out of your projects early on!

Refactoring and keeping it DRY

The Don't Repeat Yourself (DRY) acronym is the software developer's conscience: it tells you when you're in danger of making a bad or questionable decision, and gives you a feeling of satisfaction after a job well done.

In practice, repeated code is part of programming life. Trying to avoid it by constantly thinking ahead will put up so many roadblocks in your project that it won't seem worth it to continue. A more efficient – and sane – approach to dealing with repeating code is to quickly identify it when and where it occurs and then look for the best way to remove it. This task is called refactoring, and our GameBehavior class could use a little of its magic right now.

Time for action – creating a restart method

To refactor the existing level's restart code, you'll need to update GameBehavior, as follows:

  public class GameBehavior : MonoBehaviour
 {
     // ... No changes needed ...

     // 1
     void RestartLevel()
     {
         SceneManager.LoadScene(0);
         Time.timeScale = 1.0f;
     }

     void OnGUI()
     {
         GUI.Box(new Rect(20, 20, 150, 25), "Player Health: " +
           _playerLives);
         GUI.Box(new Rect(20, 50, 150, 25), "Items Collected: " +
           _itemsCollected);
         GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height -
            50, 300, 50), labelText);

         if (showWinScreen)
         {
             if (GUI.Button(new Rect(Screen.width/2 - 100,
             Screen.height/2 - 50, 200, 100), "YOU WON!"))
             {
                 // 2
                 RestartLevel();
             }
         }

         if(showLossScreen)
         {
             if (GUI.Button(new Rect(Screen.width / 2 - 100,
             Screen.height / 2 - 50, 200, 100), "You lose..."))
             {
                 RestartLevel();
             }
         }
     }
 }

Let's break down the code:

First, it declares a private method named RestartLevel() that executes the same code as the win/loss buttons in OnGUI().

Then, it replaces both instances of the repeated restart code in OnGUI() with a call to RestartLevel().

There's always more to refactor if you look in the right places. Your final optional task is to refactor the win/lost logic in the game manager, which we'll do in the next section.

Hero's trial – refactoring win/lose logic

While you've got your refactoring brain in gear, you might have noticed that the code that updates labelText, showWinScreen/showLossScreen, and Time.timeScale is repeated in the set block of both Lives and Items. Your mission is to write a private utility function that takes parameters for each of the updated variables mentioned and assign them accordingly. Then, replace the repeated code in Lives and Items with a call to the new method. Happy coding!

Summary

With that, our Enemy and Player interactions are complete. We can deal damage as well as take it, lose lives, and fight back, all while updating the on-screen GUI. Our enemies use Unity's navigation system to walk around the arena and change to attack mode when within a specified range of the player. Each GameObject is responsible for its behavior, internal logic, and object collisions, while the game manager keeps track of the variables that govern the game's state. Lastly, we learned about simple procedural programming and how much cleaner code can be when repeated instructions are abstracted out into their methods.

You should feel a sense of accomplishment at this point, especially if you started this book as a total beginner. Getting up to speed with a new programming language while building a working game is no easy trick. In the next chapter, you'll be introduced to some intermediate topics in C#, including new type modifiers, method overloading, interfaces, and class extensions.

Pop quiz – AI and navigation

How is a NavMesh component created in a Unity scene?

What component identifies a GameObject to a NavMesh?

Executing the same logic on one or more sequential objects is an example of which programming technique?

What does the DRY acronym stand for?

Revisiting Types, Methods, and Classes

Now that you've programmed the game mechanics and interactions with Unity's built-in classes, it's time to expand our core C# knowledge and focus on the intermediate applications of the foundation we've laid. We'll revisit old friends – variables, types, methods, and classes – but we'll target their deeper applications and relevant use cases. Many of the topics we'll be covering don't apply to Hero Born in its current state, so some examples will be standalone rather than be applied directly to the game prototype. 

I'll be throwing a lot of new information your way, so if you feel overwhelmed at any point, don't hesitate to revisit the first few chapters to solidify those building blocks. We'll also be using this chapter to break away from gameplay mechanics and features specific to Unity by focusing on the following topics:

Intermediate modifiers

Method overloading 

Using the out and ref parameters

Working with interfaces

Abstract classes and overriding

Extending class functionality

Namespace conflicts 

Type aliasing

Let's get started!

Access Modifier redux

While we've gotten into the habit of pairing the public and private access modifiers with our variable declarations, there remains a laundry list of modifier keywords that we haven't seen. We can't go into detail about every one of them in this chapter, but the five that we'll focus on will further your understanding of the C# language and give your programming skills a boost.

This section will cover the first three modifiers in the following list, while the remaining two will be discussed later on in the OOP redux section:

const

readonly

static

abstract

override

You can find a full list of available modifiers at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/modifiers.

Let's start with the first three access modifiers provided in the preceding list.

Constant and read-only properties

There will be times when you need to create variables that store constant, unchanging values. Adding the const keyword after a variable's access modifier will do just that, but only for built-in C# types. A good candidate for a constant value is our maxItems in the GameBehavior class:

public const int maxItems = 4;

The problem you'll run into with constant variables is that they can only be assigned a value in their declaration, meaning we can't leave maxItems without an initial value:

public readonly int maxItems;

Using the readonly keyword to declare a variable will give us the same unmodifiable value as a constant, while still letting us assign its initial value at any time.

Using the static keyword 

We've already gone over how objects, or instances, are created from a class blueprint, and that all properties and methods belong to that particular instance. While this is great for object-oriented functionality, not all classes need to be instantiated, and not all properties need to belong to a specific instance. However, static classes are sealed, meaning they cannot be used in class inheritance.

Utility methods are a good case for this situation, where we don't necessarily care about instantiating a particular Utility class instance since all its methods wouldn't be dependent on a particular object. Your task is to create just such a utility method in a new script.

Time for action – creating a static class

Let's create a new class to hold some of our future methods that deal with raw computations or repeated logic that doesn't depend on the gameplay:

Create a new C# script in the Scripts folder and name it Utilities.

Open it up and add the following code:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 // 1
 using UnityEngine.SceneManagement;

 // 2
 public static class Utilities
 {
     // 3
     public static int playerDeaths = 0;

     // 4
     public static void RestartLevel()
     {
         SceneManager.LoadScene(0);
         Time.timeScale = 1.0f;
     }
 }

Delete RestartLevel() from GameBehavior and modify the OnGUI() method with the following code:

  void OnGUI()
  {
      // ... No other changes needed ...

      if (showWinScreen)
      {
          if (GUI.Button(new Rect(Screen.width/2 - 100,
          Screen.height/2 - 50, 200, 100), "YOU WON!"))
          {
              // 5
              Utilities.RestartLevel();
          }
      }

      if(showLossScreen)
      {
          if (GUI.Button(new Rect(Screen.width / 2 - 100,
          Screen.height / 2 - 50, 200, 100), "You lose..."))
          {
              Utilities.RestartLevel();
          }
      }
  }

Let's break down the code:

First, it adds the SceneManagement using directive so that we can access the LoadScene() method.

Then, it declares Utilities as a public static class that does not inherit from MonoBehavior because we won't need it to be in the game scene.

Next, it creates a public static variable to hold the number of times our player has died and restarted the game.

After, it declares a public static method to hold our level restart logic, which is currently hardcoded in GameBehavior.

Finally, it calls RestartLevel() from the static Utilities class when either the win or lose GUI button is pressed. Notice that we didn't need an instance of the Utilities class to call the method because it's static – it's just dot notation.

We've now extracted the restart logic out of GameBehavior and put it into its static class, which makes it easier to reuse across our codebase. Marking it as static will also ensure that we never have to create or manage instances of the Utilities class before we use its class members.

Non-static classes can have properties and methods that are static and non-static. However, if an entire class is marked as static, all properties and methods must follow suit.

That wraps up our second visit of variables and types, which means it's time to move on to methods and their intermediate capabilities, which includes method overloading and ref and out parameters.

Methods redux

Methods have been a big part of our code since we learned how to use them in Chapter 3, Diving into Variables, Types, and Methods, but there are two intermediate use cases we haven't covered yet: method overloading and using the ref and out parameter keywords.

Overloading methods

The term method overloading refers to creating multiple methods with the same name but with different signatures. A method's signature is made up of its name and parameters, which is how the C# compiler recognizes it. Take the following method as an example:

public bool AttackEnemy(int damage) {}

The method signature of AttackEnemy is written as follows:

AttackEnemy(int)

Now that we know the signature of AttackEnemy, it can be overloaded by changing the number of parameters or the parameter types themselves, while still keeping its name. This offers added flexibility when you need more than one option for a given operation. 

The RestartLevel() method in GameBehavior is a great example of a situation where method overloading comes in handy. Right now, RestartLevel() only restarts the current level, but what happens if we expanded the game so that it includes multiple scenes? We could refactor RestartLevel() to accept parameters, but that often leads to bloated and confusing code. 

The RestartLevel() method is, once again, a good candidate for testing out our new knowledge. Your task is to overload it to take in different parameters.

Time for action – overloading the level restart

Let's add an overloaded version of RestartLevel():

Open up Utilities and add the following code:

 public static class Utilities
 {
     public static int playerDeaths = 0;

     public static void RestartLevel()
     {
         SceneManager.LoadScene(0);
         Time.timeScale = 1.0f;
     }

     // 1
     public static bool RestartLevel(int sceneIndex)
      {
         // 2
         SceneManager.LoadScene(sceneIndex);
         Time.timeScale = 1.0f;

         // 3
         return true;
     }
 }

Open GameBehavior and update one of the calls to Utilities.RestartLevel() in the OnGUI() method to the following:

if (showWinScreen)
 {
   if (GUI.Button(new Rect(Screen.width/2 - 100,
      Screen.height/2 - 50, 200, 100), "YOU WON!"))
   {
     // 4
     Utilities.RestartLevel(0);
   }
 }

Let's break down the code:

First, it declares an overloaded version of the RestartLevel() method that takes in an int parameter and returns a bool. 

Then, it calls LoadScene() and passes in the sceneIndex parameter instead of manually hardcoding that value. 

Next, it returns true after the new scene is loaded and the timeScale property has been reset.

Finally, it calls the overloaded RestartLevel() method and passes in a sceneIndex of 0 when the win button is pressed. Overloaded methods are automatically detected by Visual Studio and are displayed by number, as shown here:

The functionality in the RestartLevel() method is now much more customizable and can account for additional situations you may need later.

Method overloading is not limited to static methods – this was just in line with the previous example. Any method can be overloaded as long as its signature differs from the original.

Ref parameters

When we talked about classes and structs back in Chapter 5, Working with Classes, Structs, and OOP, we discovered that not all objects are passed the same way: value types are passed by copy, while reference types are passed by reference. However, we didn't go over how objects, or values, are used when they're passed into methods as parameter arguments. 

By default, all arguments are passed by value, meaning that a variable passed into a method will not be affected by any changes that are made to its value inside the method body. While this works for most cases, there are situations where you'll want to pass in a method argument by reference; prefixing a parameter declaration with either the ref or out keyword will mark the argument as a reference. 

Here are a few key points to keep in mind about using the ref keyword:

Arguments have to be initialized before being passed into a method.

You don't need to initialize or assign the reference parameter value before ending the method.

Properties with get or set accessors can't be used as ref or out arguments.

Let's try this out by adding some logic to keep track of how many times a player has restarted the game.

Time for action – tracking player restarts

Let's create a method to update playerDeaths to see the method arguments that are being passed by reference in action.

Open up Utilities and add the following code:

 public static class Utilities
 {
     public static int playerDeaths = 0;

     // 1
     public static string UpdateDeathCount(ref int countReference)
     {
         // 2
         countReference += 1;
         return "Next time you'll be at number " + countReference;
     }

     public static void RestartLevel()
     {
         SceneManager.LoadScene(0);
         Time.timeScale = 1.0f;

         // 3
         Debug.Log("Player deaths: " + playerDeaths);
         string message = UpdateDeathCount(ref playerDeaths);
         Debug.Log("Player deaths: " + playerDeaths);
     }

     public static bool RestartLevel(int sceneIndex)
     {
         // ... No changes needed ...
     }
 }

Let's break down the code:

First, it declares a new static method that returns a string and takes in an int passed by reference.

Then, it updates the reference parameter directly, incrementing its value by 1 and returning a string that contains the new value.

Finally, it debugs the playerDeaths variable in RestartLevel() before and after it is passed by reference to UpdateDeathCount().

If you play the game and lose, the debug log will show that playerDeaths has increased by 1 inside UpdateDeathCount() because it was passed by reference and not by value:

We're using the ref keyword in this situation for the sake of our example, but we could have also updated playerDeaths directly inside UpdateDeathCount() or added logic inside RestartLevel() to only fire UpdateDeathCount() when the restart was due to a loss. 

Now that we know how to use a ref parameter in our project, let's take a look at the out parameter and how it serves a slightly different purpose.

Out parameters

The out keyword does the same job as ref but with different rules:

Arguments do not need to be initialized before being passed into a method.

The referenced parameter value does need to be initialized or assigned in the calling method before it's returned.

For instance, we could replace ref with out in UpdateDeathCount() as long as we initialized or assigned the countReference parameter before returning from the method:

public static string UpdateDeathCount(out int countReference)
{
     countReference = 1;
     return "Next time you'll be at number " + countReference;
}

Methods that use the out keyword are better suited to situations where you need to return multiple values from a single function, while the ref keyword works best when a reference value only needs to be modified. 

With these new method features under our belts, it's time to revisit the big one: object-oriented programming (OOP). There's so much to this topic that it's impossible to cover everything in a chapter or two, but there are a few key tools that will come in handy early on in your development career. OOP is one of those topics that you're encouraged to follow up on after finishing this book. 

OOP redux

An object-oriented mindset is crucial to creating meaningful applications and understanding how the C# language works behind the scenes. The tricky part is that classes and structs by themselves aren't the end of the line when it comes to OOP and designing your objects. They'll always be the building blocks of your code, but classes are limited to single inheritance, meaning they can only ever have one parent or superclass, and structs can't inherit at all. So, the question you should be asking yourself right about now is simple: "How can I create objects from the same blueprint and have them perform different actions based on a specific scenario?"

Interfaces

One of the ways to gather groups of functionality together is through interfaces. Like classes, interfaces are blueprints for data and behaviors, but with one important difference: they can't have any actual implementation logic or stored values. Instead, it's up to the adopting class or struct to fill in the values and methods outlined in the interface. The great part about interfaces is that both classes and structs can use them, and there's no upper limit regarding how many can be adopted by a single object. 

For example, what if we wanted our enemies to be able to shoot back at our player when they're in close range? We could create a parent class that both the player and enemy could derive from, which would base them both on the same blueprint. The problem with that approach, however, is that enemies and players won't necessarily share the same behaviors and data. The more efficient way to handle this would be to define an interface with a blueprint for what shootable objects need to do, and then have both the enemy and player adopt it. That way, they still have the freedom to be separate, but share common functionality.

Time for action – creating a manager interface

Refactoring the shooting mechanic into an interface is a challenge I'll leave to you, but we still need to know how to create and adopt interfaces in code. For this example, we'll create an interface that all manager scripts would hypothetically need to implement to share a common structure.

Create a new C# script in the Scripts folder, name it IManager, and update its code, as follows:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 // 1
 public interface IManager
 {
     // 2
     string State { get; set; }

     // 3
     void Initialize();
 }

Let's break down the code:

First, it declares a public interface called IManager using the interface keyword.

Then, it adds a string variable to IManager named State with get and set accessors to hold the current state of the adopting class.

All interface properties need at least a get accessor to compile but can have both get and set accessors if necessary.

Finally, it defines a method named Initialize() with no return type for the adopting class to implement.

Your next task is to use the IManager interface, which means it needs to be adopted by another class.

Time for action – adopting an interface

To keep things simple, let's have the game manager adopt our new interface and implement its blueprint.

Update GameBehavior with the following code:

 // 1
 public class GameBehavior : MonoBehaviour, IManager
 {
     // 2
     private string _state;

     // 3
     public string State
     {
         get { return _state; }
         set { _state = value; }
     }

     // ... No other changes needed ...

     // 4
     void Start()
     {
         Initialize();
     }

     // 5
     public void Initialize()
     {
         _state = "Manager initialized..";
         Debug.Log(_state);
     }

     void OnGUI()
     {
         // ... No changes needed ...
     }
 }

Let's break down the code:

First, it declares that GameBehavior adopts the IManager interface using a comma and its name, just like with subclassing.

Then, it adds a private variable that we'll use to back the public State value we have to implement from IManager.

Next, it adds the public State variable declared in IManager and uses _state as its private backing variable.

After that, it declares the Start() method and calls the Initialize() method.

Finally, it declares the Initialize() method declared in IManager with an implementation that sets and prints out the public State variable.

With this, we specified that GameBehavior adopts the IManager interface and implemented its State and Initialize() members, as shown here:

The great part of this is that the implementation is specific to GameBehavior; if we had another manager class, we could do the same thing but with different logic. This opens up a whole world of possibilities for building classes, one of which is abstract classes. 

Abstract classes

Another approach to separating common blueprints and sharing them between objects is the abstract class. Like interfaces, abstract classes cannot include any implementation logic for their methods; they can, however, store variable values. Any class that subclasses from an abstract class must fully implement all variables and methods marked with the abstract keyword. They can be particularly useful in situations where you want to use class inheritance without having to write out a base class' default implementation. 

For example, let's take the IManager interface functionality we just wrote and turn it into an abstract base class instead:

 // 1
 public abstract class BaseManager
 {
     // 2
     protected string _state;
     public abstract string state { get; set; }

     // 3
     public abstract void Initialize();
 }

Let's break down the code:

First, it declares a new class named BaseManager using the abstract keyword.

Then, it creates two variables:

A protected string named _state that can only be accessed by classes that inherit from BaseManager

An abstract string named state with get and set accessors to be implemented by the subclass

Finally, it adds Initialize() as an abstract method, also to be implemented in the subclass.

In this setup, BaseManager has the same blueprint as IManager, allowing any subclasses to define their implementations of state and Initialize() using the override keyword:

 // 1
 public class CombatManager: BaseManager
 {
     // 2
     public override string state
     {
         get { return _state; }
         set { _state = value; }
     }

     // 3
     public override void Initialize()
     {
         _state = "Manager initialized..";
         Debug.Log(_state);
     }
 }

By breaking down the preceding code, we can see the following:

First, it declares a new class called CombatManager that inherits from the BaseManager abstract class.

Then, it adds the state variable implementation from BaseManager using the override keyword.

Finally, it adds the Initialize() method implementation from BaseManager using the override keyword again and sets the protected _state variable.

Even though this is only the tip of the iceberg of interfaces and abstract classes, their possibilities should be jumping around in your programming brain. Interfaces will allow you to spread and share pieces of functionality between unrelated objects, leading to a Lego-like assembly when it comes to your code.

Abstract classes, on the other hand, will let you keep the single-inheritance structure of OOP while separating a class's implementation from its blueprint. These approaches can even be mixed and matched, as abstract classes can adopt interfaces just like non-abstract ones.

As always with complicated topics, your first stop should be the documentation. Check it out at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract and https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface.

You won't always need to build a new class from scratch. Sometimes, it's enough to add the feature or logic you want to an existing class, which is called a class extension.

Class extensions

Let's step away from custom objects and talk about how we can extend existing classes so that they fit our own needs. The idea behind class extensions is simple: take an existing built-in C# class and add on any functionality that you need it to have. Since we don't have access to the underlying code that C# is built on, this is the only way to get custom behavior out of objects the language already has.

Classes can only be modified with methods – no variables or other entities are allowed. However limiting this might be, it makes the syntax consistent:

public static returnType MethodName(this ExtendingClass localVal) {}

Extension methods are declared using the same syntax as normal methods, but with a few caveats:

All extension methods need to be marked as static.

The first parameter needs to be the this keyword, followed the name of the class we want to extend and a local variable name:

This special parameter lets the compiler identify the method as an extension, and gives us a local reference for the existing class.

Any class methods and properties can then be accessed through the local variable.

It's common to store extension methods inside a static class, which, in turn, is stored inside its namespace. This allows you to control what other scripts have access to your custom functionality.

Your next task is to put class extensions into practice by adding a new method to the built-in C# String class.

Time for action – extending the string class

Let's take a look at extensions in practice by adding a custom method to the String class.

Create a new C# script in the Scripts folder, name it CustomExtensions, and add the following code:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 // 1
 namespace CustomExtensions
 {
     // 2
     public static class StringExtensions
     {
         // 3
         public static void FancyDebug(this string str)
         {
             // 4
             Debug.LogFormat("This string contains {0} characters.",
               str.Length);
         }
     }
 }

Let's break down the code:

First, it declares a namespace named CustomExtensions to hold all the extension classes and methods.

Then, it declares a static class named StringExtensions for organizational purposes; each group of class extensions should follow this setup.

Next, it adds a static method named FancyDebug to the StringExtensions class:

The first parameter, this string str, marks the method as an extension.

The str parameter will hold a reference to the actual text value that FancyDebug() is called from; we can operate on str inside the method body as a stand-in for all string literals.

Finally, it prints out a debug message whenever FancyDebug is executed, using str.Length to reference the string variable that the method is called on.

Now that the extension is part of the String class, let's test it out.

Time for action – using an extension method

To use our new custom string method, we'll need to include it in whatever class we want to have access to it.

Open up GameBehavior and update the class with the following code:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 // 1
 using CustomExtensions;

 public class GameBehavior : MonoBehaviour, IManager
 {
      // ... No changes needed ...

      void Start()
      {
          // ... No changes needed ...
      }

      public void Initialize()
      {
          _state = "Manager initialized..";

          // 2
          _state.FancyDebug();

          Debug.Log(_state);
      }

      void OnGUI()
      {
          // ... No changes needed ...
      }
 }

Let's break down the code:

First, it adds the CustomExtensions namespace with a using directive at the top of the file.

Then, it calls FancyDebug on the _state string variable with dot notation inside Initialize() to print out the number of individual characters its value has.

Extending the entire string class with FancyDebug() means that any string variable has access to it. Since the first extension method parameter has a reference to whatever string value that FancyDebug() is called on, its length will be printed out properly, as shown here:

A custom class can also be extended using the same syntax, but it's more common to just add extra functionality directly into the class if it's one you control.

The last topic we'll explore in this chapter is namespaces, which we briefly learned about earlier in this book. In the next section, you'll learn the larger role that namespaces play in C# and how to create your type alias.

Namespace redux

As your applications get more complicated, you'll start to section off your code into namespaces, ensuring that you have control over where and when it's accessed. You'll also use third-party software tools and plugins to save on time implementing a feature from the ground up that someone else has already made available. Both of these scenarios show that you're progressing with your programming knowledge, but they can also cause namespace conflicts. 

Namespace conflicts happen when there are two or more classes or types with the same name, which happens more than you'd think. Good naming habits tend to produce similar results, and before you know it, you're dealing with multiple classes named Error or Extension, and Visual Studio is throwing out errors. Luckily, C# has a simple solution to these situations: type aliasing.

Type aliasing

Defining a type alias lets you explicitly choose which conflicting type you want to use in a given class, or create a more user-friendly name for a long-winded existing one. Type aliases are added at the top of the class file with a using directive, followed by the alias name and the assigned type:

using aliasName = type;

For instance, if we wanted to create a type alias to refer to the existing Int64 type, we could say the following:

using CustomInt = System.Int64;

Now that CustomInt is a type alias for the System.Int64 type, the compiler will treat it as an Int64, letting us use it like any other type:

public CustomInt playerHealth = 100;

You can use type aliasing with your custom types, or existing ones with the same syntax, as long as they're declared at the top of script files with the other using directives.

Summary

With new modifiers, method overloading, class extensions, and object-oriented skills under our belts, we are only one step away from the end of our C# journey. Remember, these intermediate topics are intended to get you thinking about more complex applications of the knowledge you've been gathering throughout this book; don't think of what you've learned in this chapter as all that there is to know on these concepts. Take them as a starting point and continue from there.

In the next chapter, we'll discuss the basics of generic programming, get a little hands-on experience with delegates and events, and wrap up with an overview of exception handling.

Pop quiz – leveling up

Which keyword would mark a variable as unmodifiable?

How would you create an overloaded version of a base method?

What is the main difference between classes and interfaces?

How would you solve a namespace conflict in one of your classes?

Introducing Stacks, Queues, and HashSets

In the last chapter, we revisited variables, types, and classes to see what they had to offer beyond the basic features introduced at the beginning of the book. In this chapter, we'll take a closer look at new collection types and learn about their intermediate-level capabilities. 

Each of the new collection types in this chapter has a specific purpose. For most scenarios where you need a collection of data, a list or array works just fine. When you need temporary storage or control over the order of collection elements, or more specifically, the order they are accessed, look to stacks and queues. When you need to perform operations that depend on every element in a collection to be unique, meaning not duplicated, look to HashSets. Remember, being a good programmer isn't about memorizing code: it's about choosing the right tool for the right job. 

Before you start on the code in the following section, let's lay out the topics you'll be learning about:

Introducing stacks

Peeking and popping elements

Common methods

Working with queues

Adding, removing, and peeking elements

Using HashSets

Performing operations

Introducing stacks

At its most basic level, a stack is a collection of elements of the same specified type. The stack length is variable, meaning it can change depending on how many elements it's holding. The important difference between a stack and a list or array is how the elements are stored. Stacks follow the last-in-first-out (LIFO) model, meaning the last element in the stack is the first accessible element. This is useful when you want to access elements in reverse order. You should note that they can store null and duplicate values.

All the collection types in this chapter are a part of the System.Collections.Generic namespace, meaning you need to add the following code to the top of any file that you want to use them in:

using System.Collections.Generic;

Now that you know what you're about to work with, let's take a look at the basic syntax for declaring stacks.

Basic syntax

A stack variable declaration needs to meet the following requirements:

The Stack keyword, its element type between left and right arrow characters, and a unique name

The new keyword to initialize the stack in memory, followed by the Stack keyword and element type between arrow characters

A pair of parentheses capped off by a semicolon

In blueprint form, it looks like this:

Stack<elementType> name = new Stack<elementType>();

Unlike the other collection types you've worked with, stacks can't be initialized with elements when they're created. 

C# supports a non-generic version of the Stack type that doesn't require you to define the type of element in the stack:

Stack myStack = new Stack();

However, this is less safe and more costly than using the preceding generic version. You can read more about Microsoft's recommendation at https://github.com/dotnet/platform-compat/blob/master/docs/DE0006.md.

Your next task is to create a stack of your own and get hands-on experience with working with its class methods.

Time for action – storing collected items

To test this out, you're going to modify the existing item collection logic in Hero Born by using a stack to store possible loot that can be collected:

Open GameBehavior.cs and add in a new stack variable named lootStack:

// 1
public Stack<string> lootStack = new Stack<string>();

Update the Initialize method with the following code to add new items to the stack:

public void Initialize()
{
    _state = "Manager initialized..";
    _state.FanceyDebug();
    Debug.Log(_state);

    // 2
     lootStack.Push("Sword of Doom");
     lootStack.Push("HP+");
     lootStack.Push("Golden Key");
     lootStack.Push("Winged Boot");
     lootStack.Push("Mythril Bracers");
}

Add a new method to the bottom of the script to print out the stack information:

// 3
public void PrintLootReport()
{
    Debug.LogFormat("There are {0} random loot items waiting for
       you!", lootStack.Count);
}

Open ItemBehavior.cs and call PrintLootReport from the gameManager instance:

void OnCollisionEnter(Collision collision)
{
    if(collision.gameObject.name == "Player")
    {
        Destroy(this.transform.parent.gameObject);
        Debug.Log("Item collected!");
    }

    gameManager.Items += 1;

    // 4
    gameManager.PrintLootReport();
}

Breaking this down, it does the following:

Creates an empty stack with elements of the string type

Uses the Push method to add string elements to the stack, increasing its size each time

Prints out the stack count whenever the method is called

Calls PrintLootReport every time an item is collected by the player:

Now that you have a working stack, you're ready to experiment with how items are accessed using the stack class' Pop and Peek methods.

Popping and peeking

We've already talked about how stacks store elements using the LIFO method. Now, we need to look at how elements are accessed in a familiar but different collection type – by peeking and popping:

The Peek method returns the next item on the stack without removing it, letting you "peek" at it without changing anything.

The Pop method returns and removes the next item on the stack, essentially "popping" it off and handing it to you.

Both of these methods can be used by themselves or together depending on what you need. You'll get hands-on experience with both methods in the following section.

Time for action – the last item collected

Your next task is to grab the last item added to lootStack. In our example, the last element is determined programmatically in the Initialize method, but you can imagine how the elements could be randomized or added based on some parameter. Either way, update PrintLootReport with the following code:

public void PrintLootReport()
{
    // 1
    var currentItem = lootStack.Pop()

    // 2
    var nextItem = lootStack.Peek()

    // 3
    Debug.LogFormat("You got a {0}! You've got a good chance of finding
       a {1} next!", currentItem, nextItem);

    Debug.LogFormat("There are {0} random loot items waiting for you!",
       lootStack.Count);
}

Here's what's going on:

Calls Pop on lootStack, removes the next item on the stack, and stores it.

Calls Peek on lootStack and stores the next item on the stack without removing it.

Adds a new debug log to print out the item that was popped off and the next item on the stack.

You can see from the console that Mythril Bracers, the last item added to the stack, was popped off first, followed by Winged Boots, which was peeked at but not removed. You can also see that lootStack has four remaining elements that can be accessed:

Now that you know how to create, add, and query elements from a stack, we can move on to some common methods that you have access to through the stack class.

Common methods

First, you can use the Clear method to empty out or delete the entire contents of a stack:

// Empty the stack and reverting the count to 0
lootStack.Clear()

If you want to know whether an element exists in your stack, use the Contains method and specify the element you're looking for:

// Returns true for "Golden Key" item
var itemFound = lootStack.Contains("Golden Key");

If you need to copy the elements of a stack to an array, the CopyTo method will let you specify the destination and the starting index for the copy operation:

// Copies loot stack items to an existing array starting at the 0 index
string[] copiedLoot = new string[lootStack.Count];
numbers.CopyTo(copiedLoot, 0);

If you need to convert a stack into an array, simply use the ToArray method. This conversion creates a new array out of your stack, which is different than the CopyTo method, which copies the stack elements to an existing array.

You can also convert a stack into a string if needed by using the ToString method:

// Copies an existing stack to a new array
lootStack.ToArray();

// Returns a string representing the stack object
lootStack.ToString();

If you're paying attention to good coding practices, you'll want to check whether there's the next item on your stack before popping or peeking at it. Luckily, the stack class has two methods for that exact scenario – TryPeek and TryPop:

// The item will NOT be removed from the stack.
bool itemPresent = lootStack.TryPeek(out lootItem);
if(itemPresent)
    Debug.Log(lootItem);
else
    Debug.Log("Stack is empty.");

// The item WILL be removed from the stack
bool itemPresent = lootStack.TryPop(out lootItem);
if(itemPresent)
    Debug.Log(lootItem);
else
    Debug.LogFormat("Stack is empty.");

Both methods return a true or false value based on the presence of an object at the top of the stack. If there is an object present, it will be copied to the out result parameter and the method will return true. If the stack is empty, the out result will default to its initial value and the method will return false.

You can find the entire list of stack methods in the C# documentation at https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.stack-1?view=netcore-3.1.

That wraps up our introduction to stacks, but we're going to talk about its cousin, the queue, in the following section. 

Working with queues

Like stacks, queues are collections of elements or objects of the same type. The length of any queue is variable just like a stack, meaning its size changes as elements are added or removed. However, queues follow the first-in-first-out (FIFO) model, meaning the first element in the queue is the first accessible element. You should note that queues can store null and duplicate values but can't be initialized with elements when they're created. 

Basic syntax

A queue variable declaration needs to meet the following requirements:

The Queue keyword, its element type between left and right arrow characters, and a unique name

The new keyword to initialize the queue in memory, followed by the Queue keyword and element type between arrow characters

A pair of parentheses capped off by a semicolon

In blueprint form, a queue looks as follows:

Queue<elementType> name = new Queue<elementType>();

C# supports a non-generic version of the Queue type that doesn't require you to define the type of element it stores:

Queue myQueue = new Queue();

However, this is less safe and more costly than using the preceding generic version. You can read more about Microsoft's recommendation at https://github.com/dotnet/platform-compat/blob/master/docs/DE0006.md.

An empty queue all by itself isn't all that useful; you want to be able to add, remove, and peek at its elements whenever you need, which is the topic of the following section.

Adding, removing, and peeking

Since the lootStack variable in the previous sections could easily be a queue, we'll keep the following code out of our game scripts for efficiency. However, feel free to explore the differences, or similarities, of these classes in your own code.

To create a queue of string elements, use the following:

// Creates a new Queue of string values.
Queue<string> activePlayers = new Queue<string>();

To add elements to the queue, call the Enqueue method with the element you want to add:

// Adds string values to the end of the Queue.
activePlayers.Enqueue("Harrison");
activePlayers.Enqueue("Alex");
activePlayers.Enqueue("Haley");

To see the first element in the queue without removing it, use the Peek method:

// Returns the first element in the Queue without removing it.
var firstPlayer = activePlayers.Peek();

To return and remove the first element in the queue, use the Dequeue method:

// Returns and removes the first element in the Queue.
var firstPlayer = activePlayers.Dequeue();

Now that you know how to work with the basic features of a queue, feel free to explore the more intermediate and advanced methods that the queue class offers.

Common methods

Queues and stacks share almost the exact same features, so we won't go over them a second time. You can find a complete list of methods and properties in the C# documentation at https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1?view=netcore-3.1. 

Before closing out the chapter, let's take a look at the HashSet collection type and the mathematical operations it's uniquely suited for.

Using HashSets

The last collection type we'll get our hands on in this chapter is the HashSet. This collection is very different from any other collection type that we've come across: it cannot store duplicate values and is not sorted, meaning its elements are not ordered in any way. Think of HashSets as dictionaries with just keys, instead of key-value pairs. They can perform set operations and element lookups extremely fast, which we'll explore at the end of this section, and are best suited to situations where the element order and uniqueness are a top priority. 

Basic syntax

A HashSet variable declaration needs to meet the following requirements:

The HashSet keyword, its element type between left and right arrow characters, and a unique name

The new keyword to initialize the HashSet in memory, followed by the HashSet keyword and element type between arrow characters.

A pair of parentheses capped off by a semicolon.

In blueprint form, it looks as follows:

HashSet<elementType> name = new HashSet<elementType>();

Unlike stacks and queues, you can initialize a HashSet with default values when declaring the variable:

HashSet<string> people = new HashSet<string>();

// OR

HashSet<string> people = new HashSet<string>() { "Joe", "Joan", "Hank"};

To add elements, use the Add method and specify the new element:

people.Add("Walter");
people.Add("Evelyn");

To remove an element, call Remove and specify the element you want to delete from the HashSet:

people.Remove("Joe");

That's it for the easy stuff, and this should start to feel pretty familiar at this point in your programming journey. The set operations are where the HashSet collection really shines, which is the topic of the following section.

Performing operations

Set operations are performed on the calling collection object and the passed-in collection object. More specifically, set operations are used to modify the calling HashSet elements based on which operation is used. We'll get into this in more detail in the following code, but first, let's go over the three main set operations that crop up in programming scenarios the most often:

In the following definitions, currentSet refers to the HashSet calling an operation method and specifiedSet refers to the passed-in HashSet method parameter. The modified HashSet is always the current set:

currentSet.Operation(specifiedSet);

There are three main operations that we'll be working with in the rest of this section:

UnionWith adds the elements of the current and specified sets together.

IntersectWith stores only the elements that are in both the current and specified sets.

ExceptWith subtracts the elements of the specified set from the current set.

There are two more groups of set operations that deal with subset and superset computations, but these are targeted at specific use cases that are beyond the scope of this chapter. You can find all the relevant information for these methods at https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1?view=netcore-3.1.

Let's say we have two sets of player names – one for active players and one for inactive players:

HashSet<string> activePlayers = new HashSet<string>() { "Harrison",
  "Alex", "Haley"};
HashSet<string> inactivePlayers = new HashSet<string>() { "Kelsey",
  "Basel"};

We would use the UnionWith operation to modify a set to include all the elements in both sets:

activePlayers.UnionWith(inactivePlayers);
// activePlayers now stores "Harrison", "Alex", "Haley", "Kelsey",
     "Basel"

Now, let's say we have two different sets – one for active players and one for premium players:

HashSet<string> activePlayers = new HashSet<string>() { "Harrison",
  "Alex", "Haley"};
HashSet<string> premiumPlayers = new HashSet<string>() { "Haley",
  "Basel" };

We would use the IntersectWith operation to find any active players that are also premium members:

activePlayers.IntersectWith(premiumPlayers);
// activePlayers now stores only "Haley"

What if we wanted to find all active players that are not premium members? We would do the opposite of what we did with the IntersectWith operation by calling ExceptWith:

HashSet<string> activePlayers = new HashSet<string>() { "Harrison",
  "Alex", "Haley"};
HashSet<string> premiumPlayers = new HashSet<string>() { "Haley",
  "Basel" };

activePlayers.ExceptWith(premiumPlayers);
// activePlayers now stores "Harrison" and "Alex" but removed "Haley"

Notice that I'm using brand-new instances of the two example sets for each operation because the current set is modified after each operation is executed. If you keep using the same sets throughout, you will get different results.

Now that you've learned how to perform fast mathematical operations with HashSets, it's time to close our chapter and drive home what we have learned.

Summary

Congratulations, you're almost at the finish line! In this chapter, you learned about three new collection types, and how they can be used in different situations. Stacks are great if you want to access your collection elements in the reverse order that they were added, queues are your ticket if you want to access your elements in sequential order, and both are ideal for temporary storage. The important difference between these collection types and lists or arrays is how they can be accessed with popping and peeking operations. Lastly, you learned about the almighty HashSet and its performance-based mathematical set operations. In situations where you need to work with unique values and perform additions, comparisons, or subtractions on large collections, these are key.

In the next chapter, you'll be taken a little deeper into the intermediate world of C# with delegates, generics, and more as you approach the end of this book. Even after all you've learned, the last page is still just the beginning of another journey.

Pop quiz – intermediate collections

Which collection type stores its elements using the LIFO model?

Which method lets you query the next element in a stack without removing it?

Can stacks and queues store null values?

How would you subtract one HashSet from another?

Exploring Generics, Delegates, and Beyond

The more time you spend programming, the more you start thinking about systems. Structuring how classes and objects interact, communicate, and exchange data are all examples of systems we've worked with so far; the question now is how to make them safer and more efficient.

Since this will be the last practical chapter of the book, we'll be going over examples of generic programming concepts, delegation, event creation, and error handling. Each of these topics is a large area of study in its own right, so take what you learn here and expand on it in your projects. After we complete our practical coding, we'll finish up with a brief overview of design patterns and how they'll play a part in your programming journey going forward.

We'll cover the following topics in this chapter:

Generic programming

Using delegates

Creating events and subscriptions

Throwing and handling errors

Understanding design patterns

Introducing generics

All of our code so far has been very specific in terms of defining and using types. However, there will be cases where you need a class or method to treat its entities in the same way, regardless of its type, while still being type-safe. Generic programming allows us to create reusable classes, methods, and variables using a placeholder, rather than a concrete type.

When a generic class instance is created or a method is used, a concrete type will be assigned, but the code itself treats it as a generic type. You'll see generic programming most often in custom collection types that need to be able to perform the same operations on elements regardless of type. While this might not conceptually make sense yet, it will once when we look at a concrete example in the next section.

We've already seen this in action with the List type, which is a generic type itself. We can access all its addition, removal, and modification functions regardless of whether it's storing integers, strings, or individual characters.

Generic objects

Creating a generic class works the same as creating a non-generic class but with one important difference: its generic type parameter. Let's take a look at an example of a generic collection class we might want to create to get a clearer picture of how this works:

public class SomeGenericCollection<T> {}

We've declared a generic collection class named SomeGenericCollection and specified that its type parameter will be named T. Now, T will stand in for the element type that the generic list will store and can be used inside the generic class just like any other type.

Whenever we create an instance of SomeGenericCollection, we need to specify the type of values it can store:

SomeGenericCollection<int> highScores = new SomeGenericCollection<int>
  ();

In this case, highScores stores integer values and T stands in for the int type, but the SomeGenericCollection class will treat any element type the same. 

You have complete control over naming a generic type parameter, but the industry standard in many programming languages is a capital T. If you are going to name your type parameters differently, consider starting the name with a capital T for consistency and readability.

Time for action – creating a generic collection

Let's create a more complete generic list class to store some fictional inventory items with the following steps:

Create a new C# script in the Scripts folder, name it InventoryList, and update its code to the following:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 // 1
 public class InventoryList<T>
 {
     // 2
     public InventoryList()
     {
         Debug.Log("Generic list initalized...");
     }
 }

Create a new instance of InventoryList in GameBehavior:

 public class GameBehavior : MonoBehaviour, IManager
 {
     // ... No changes needed ...

     void Start()
     {
         Initialize();

         // 3
         InventoryList<string> inventoryList = new
            InventoryList<string>();
     }

     // ... No changes to Initialize or OnGUI ...
 }

Let's break down the code:

Declares a new generic class named InventoryList with a T type parameter

Adds a default constructor with a simple debug log to print out whenever a new InventoryList instance is created

Creates a new instance of InventoryList in GameBehavior to hold string values:

Nothing new has happened here yet in terms of functionality, but Visual Studio recognizes InventoryList as a generic class because of its generic type parameter, T. This sets us up to include additional generic operations in the InventoryList class itself.

Generic methods

A standalone generic method needs to have a placeholder type parameter, just like a generic class, which allows it to be included inside either a generic or non-generic class as needed:

public void GenericMethod<T>(T genericParameter) {}

The T type can be used inside the method body and defined when the method is called:

GenericMethod<string>("Hello World!");

However, if you want to declare a generic method inside a generic class, you don't need to specify a new T type:

public class SomeGenericCollection<T>
{
    public void NonGenericMethod(T genericParameter) {}
}

When you call a non-generic method that uses a generic type parameter, there's no issue because the generic class has already taken care of assigning a concrete type:

SomeGenericCollection<int> highScores = new SomeGenericCollection
                                                         <int> ();
highScores.NonGenericMethod(35);

Generic methods can be overloaded and marked as static, just like non-generic methods. If you want the specific syntax for those situations, check out https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generic-methods.

Your next task is to create a new generic item and use it in the InventoryList script.

Time for action – adding a generic item

Since we already have a generic class with a defined type parameter, let's add a non-generic method to see them working together:

Open up InventoryList and update the code as follows:

public class InventoryList<T>
 {
     // 1
     private T _item;
     public T item
     {
         get { return _item; }
     }

     public InventoryList()
     {
         Debug.Log("Generic list initialized...");
     }

     // 2
     public void SetItem(T newItem)
     {
         // 3
         _item = newItem;
         Debug.Log("New item added...");
     }
 }

Go into GameBehavior and add an item to inventoryList:

 public class GameBehavior : MonoBehaviour, IManager
 {
     // ... No changes needed ...

     void Start()
     {
         Initialize();
         InventoryList<string> inventoryList = new
            InventoryList<string>();

         // 4
         inventoryList.SetItem("Potion");
         Debug.Log(inventoryList.item);
     }

     public void Initialize()
     {
         // ... No changes needed ...
     }

     void OnGUI()
     {
         // ... No changes needed ...
     }
 }

Let's break down the code:

Adds a public item property of the T type with a private backing variable of the same type

Declares a new method inside InventoryList, named SetItem, that takes in a T type parameter

Sets the value of _item to the generic parameter passed into SetItem() and debugs out a success message

Assigns a string value to the item property on inventoryList using SetItem() and prints out a debug log:

We wrote SetItem() to take in a parameter of whatever type our generic InventoryList instance is created with and assign it to a new class property using a public and private backing approach. Since inventoryList was created to hold string values, we assigned the "Potion" string to the item property without any issues. However, this method works equally well with any type that an InventoryList instance might hold.

Constraining type parameters

One of the great things about generics is that their type parameters can be limited. This might seem counterintuitive to what we've learned about generics so far, but just because a class can contain any type, doesn't mean it should necessarily be allowed to. 

To constrain a generic type parameter, we need a new keyword and a syntax we haven't seen before:

public class SomeGenericCollection<T> where T: ConstraintType {}

The where keyword defines the rules that T must pass before it can be used as a generic type parameter. It essentially says SomeGenericClass can take in any T type as long as it conforms to the constraining type. The constraining rules aren't anything mystical or scary; they're concepts we've already covered:

Adding the class keyword would constrain T to types that are classes.

Adding the struct keyword would constrain T to types that are structs.

Adding an interface, such as IManager, as the type would limit T to types that adopt the interface.

Adding a custom class, such as Character, would constrain T to only that class type.

If you need a more flexible approach to account for classes that have subclasses, you can use where T : U, which specifies that the generic T type must be of, or derive from, the U type. This is a little advanced for our needs, but you can find more details at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters. 

Time for action – limiting generic elements

Just for fun, let's constrain InventoryList to only be able to accept types that are classes:

Open up InventoryList and add in the following code:

 // 1
 public class InventoryList<T> where T: class
 {
     // ... No changes needed ...
 }

Since our inventoryList instance, the example uses strings, and strings are classes, so there's no problem with our code. However, if we were to change the type constraint to a struct or an interface name, our generic class would throw an error. This kind of situation is especially useful when you need to safeguard your generic classes or methods against types that you don't want to support.

Delegating actions

There will be times when you need to pass off, or delegate, the actual execution of a method. In C#, this can be accomplished through delegate types, which store references to methods and can be treated like any other variable. The only caveat is that the delegate itself and any assigned method need to have the same signature—just like integer variables can only hold whole numbers and strings can only hold text. 

Basic syntax

Creating a delegate is a mix between writing a function and declaring a variable:

public delegate returnType DelegateName(int param1, string param2);

You start with an access modifier followed by the delegate keyword, which identifies it to the compiler as a delegate type. A delegate type can have a return type and name as a regular function, as well as parameters if needed. However, this syntax only declares the delegate type itself; to use it, you need to create an instance as we do with classes:

public DelegateName someDelegate;

With a delegate type variable declared, it's easy to assign a method that matches the delegate signature:

public DelegateName someDelegate = MatchingMethod;

public void MatchingMethod(int param1, string param2)
{
    // ... Executing code here ...
}

Notice that you don't include the parentheses when assigning MatchingMethod to the someDelegate variable, as it's not calling the method at this point. What it's doing is delegating the calling responsibility of MatchingMethod to someDelegate, which means we can call the function as follows:

someDelegate();

This might seem cumbersome at this point in your C# skills, but I promise you that being able to store and execute methods as variables will come in handy down the road.

Time for action – creating a debug delegate

Let's create a simple delegate type to define a method that takes in a string and eventually prints it out using an assigned method:

Open up GameBehavior and add in the following code:

public class GameBehavior : MonoBehaviour, IManager
 {
     // ... No other changes needed ...

     // 1
     public delegate void DebugDelegate(string newText);

     // 2
     public DebugDelegate debug = Print;

     // ... No other changes needed ...

     void Start()
     {
         // ... No changes needed ...
     }

     public void Initialize()
     {
         _state = "Manager initialized..";
         _state.FancyDebug();

         // 3
         debug(_state);
     }

     // 4
     public static void Print(string newText)
     {
         Debug.Log(newText);
     }

     void OnGUI()
     {
         // ... No changes needed ...
     }
 }

Let's break down the code:

Declares a public delegate type named DebugDelegate to hold a method that takes in a string parameter and returns void

Creates a new DebugDelegate instance named debug and assigns it a method with a matching signature named Print()

Replaces the Debug.Log(_state) code inside Initialize() with a call to the debug delegate instance instead

Declares Print() as a static method that takes in a string parameter and logs it to the console:

Nothing in the console has changed, but instead of directly calling Debug.Log() inside Initialize(), that operation has been delegated to the debug delegate instance. While this is a simplistic example, delegation is a powerful tool when you need to store, pass, and execute methods as their types. We've already worked with a real-world example of delegation with the OnCollisionEnter() and OnCollisionExit() methods, which are Unity methods that are called through delegation.

Delegates as parameter types

Since we've seen how to create delegate types for storing methods, it makes sense that a delegate type could also be used as a method parameter itself. This isn't that far removed from what we've already done, but it's a good idea to cover our bases.

Time for action – using a delegate argument

Let's see how a delegate type can be used as a method parameter:

Update GameBehavior with the following code:

public class GameBehavior : MonoBehaviour, IManager
 {
     // ... No changes needed ...

     void Start()
     {
         // ... No changes needed ...
     }

     public void Initialize()
     {
         _state = "Manager initialized..";
         _state.FancyDebug();

         debug(_state);

         // 1
         LogWithDelegate(debug);
     }

     public static void Print(string newText)
     {
         // ... No changes needed ...
     }

     // 2
     public void LogWithDelegate(DebugDelegate del)
     {
         // 3
         del("Delegating the debug task...");
     }

     void OnGUI()
     {
         // ... No changes needed ...
     }
 }

Let's break down the code:

Calls LogWithDelegate() and passes in our debug variable as its type parameter

Declares a new method that takes in a parameter of the DebugDelegate type

Calls the delegate parameter's function and passes in a string literal to be printed out:

We've created a method that takes in a parameter of the DebugDelegate type, which means that the actual argument passed in will represent a method and can be treated as one. Think of this example as a delegation chain, where LogWithDelegate() is two steps removed from the actual method doing the debugging, which is Print().

It's easy to get lost with delegation if you miss an important mental connection, so go back and review the code from the beginning of the section and check the docs at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/.

Now that you know how to work with basic delegates, it's time we talked about events and how they can be used to efficiently communicate information between multiple scripts.

Firing events

C# events allow you to essentially create a subscription system based on actions in your games or apps. For instance, if you wanted to send out an event whenever an item is collected, or when a player presses the spacebar, you could do that. However, when an event fires, it doesn't automatically have a subscriber, or receiver, to handle any code that needs to execute after the event action.

Any class can subscribe or unsubscribe to an event through the calling class the event is fired from; just like signing up to receive notifications on your phone when a new post is shared on Facebook, events form a kind of distributed-information superhighway for sharing actions and data across your application.

Basic syntax

Declaring events is similar to declaring delegates in that an event has a specific method signature. We'll use a delegate to specify the method signature we want the event to have, then create the event using the delegate type and the event keyword:

public delegate void EventDelegate(int param1, string param2);
public event EventDelegate eventInstance;

This setup allows us to treat eventInstance as a method because it's a delegate type, which means we can send it out at any time by calling it:

eventInstance(35, "John Doe");

Your next task is to create an event of your own and fire it off in the appropriate place inside PlayerBehavior.

Time for action – creating an event

Let's create an event to fire off any time our player jumps:

Open up PlayerBehavior and add in the following changes:

public class PlayerBehavior : MonoBehaviour
 {
     // ... No other variable changes needed ...

     // 1
     public delegate void JumpingEvent();

     // 2
     public event JumpingEvent playerJump;

     void Start()
     {
         // ... No changes needed ...
     }

     void Update()
     {
         _vInput = Input.GetAxis("Vertical") * moveSpeed;
         _hInput = Input.GetAxis("Horizontal") * rotateSpeed;
     }

     void FixedUpdate()
     {
         if(IsGrounded() && Input.GetKeyDown(KeyCode.Space))
         {
             _rb.AddForce(Vector3.up * jumpVelocity,
                ForceMode.Impulse);

             // 3
             playerJump();
         }
     }

     // ... No changes needed in IsGrounded or OnCollisionEnter
 }

Let's break down the code:

Declares a new delegate type that returns void and takes in no parameters

Creates an event of the JumpingEvent type, named playerJump, that can be treated as a method that matches the delegate's void return and no parameter signature

Calls playerJump after the force is applied in Update()

We have successfully created a simple delegate type that takes in no parameters and returns nothing, as well as an event of that type to execute whenever the player jumps. Each time the player jumps, the playerJump event is sent out to all of its subscribers to notify them of the action.

After the event fires, it's up to its subscribers to process it and do any additional operations, which we'll see in the Handling event subscriptions section next.

Handling event subscriptions

Right now, our playerJump event has no subscribers, but changing that is simple and very similar to how we assigned method references to delegate types in the last section:

someClass.eventInstance += EventHandler;

Since events are variables that belong to the class they're declared in, and subscribers will be other classes, a reference to the event-containing class is necessary for subscriptions. The += operator is used to assign a method that will fire when an event executes, just like setting up an out-of-office email. Like assigning delegates, the method signature of the event handler method must match the event's type. In our previous syntax example, that means EventHandler needs to be the following:

public void EventHandler(int param1, string param2) {}

In cases where you need to unsubscribe from an event, you simply do the reverse of the assignment by using the -= operator:

someClass.eventInstance -= EventHandler;

Event subscriptions are generally handled when a class is initialized or destroyed, making it easy to manage multiple events without messy code implementations.

Now that you know the syntax for subscribing and unsubscribing to events, it's your turn to put this into practice in the GameBehavior script.

Time for action – subscribing to an event

Now that our event is firing every time the player jumps, we need a way to capture that action:

Go back to GameBehavior and update the following code:

public class GameBehavior : MonoBehaviour, IManager
 {
     // ... No changes needed ...

     void Start()
     {
         // ... No changes needed ...
     }

     public void Initialize()
     {
         _state = "Manager initialized..";
         _state.FancyDebug();

         debug(_state);
         LogWithDelegate(debug);

         // 1
         GameObject player = GameObject.Find("Player");

         // 2
         PlayerBehavior playerBehavior =
             player.GetComponent<PlayerBehavior>();

         // 3
         playerBehavior.playerJump += HandlePlayerJump;
     }

     // 4
     public void HandlePlayerJump()
     {
          debug("Player has jumped...");
     }

     // ... No changes in Print,
            LogWithDelegate, or
            OnGUI ...
 }

Let's break down the code:

Finds the Player object in the scene and stores its GameObject in a local variable

Uses GetComponent() to retrieve a reference to the PlayerBehavior class attached to the Player and stores it in a local variable

Subscribes to the playerJump event declared in PlayerBehavior with a method named HandlePlayerJump

Declares the HandlePlayerJump() method with a signature that matches the event's type and logs a success message using the debug delegate each time the event is received:

To correctly subscribe and receive events in GameBehavior, we had to grab a reference to the PlayerBehavior class attached to the player. We could have done this all in one line, but it's much more readable when it's split up. We then assigned a method to the playerJump event that will execute whenever the event is received, and complete the subscription process. Since the player is never destroyed in our prototype, there's no need to unsubscribe to playerJump, but don't forget to take that step if the situation calls for it.

That wraps up our discussion on events, but we still have to discuss a very important topic that no program can succeed without, and that's error handling. 

Handling exceptions

Efficiently incorporating errors and exceptions into your code is both a professional and personal benchmark in your programming journey. Before you start yelling "Why would I add errors when I've spent all this time trying to avoid them?!", you should know that I don't mean adding errors to break your existing code. It's quite the opposite—including errors or exceptions and handling them appropriately when pieces of functionality are used incorrectly makes your code base stronger and less prone to crashes, not weaker. 

Throwing exceptions

When we talk about adding errors, we refer to the process as exception throwing, which is an apt visual analogy. Throwing exceptions is part of something called defensive programming, which essentially means that you actively and consciously guard against improper or unplanned operations in your code. To mark those situations, you throw out an exception from a method that is then handled by the calling code. 

Let's take an example: say we have an if statement that checks whether a player's email address is valid before letting them sign up. If the email entered is not valid, we want our code to throw an exception:

public void ValidateEmail(string email)
{
    if(!email.Contains("@"))
    {
        throw new System.ArgumentException("Email is invalid");
    }
}

We use the throw keyword to send out the exception, which is created with the new keyword followed by the exception we specify. System.ArgumentException() will log the information about where and when the exception was executed by default, but can also accept a custom string if you want to be more specific.

ArgumentException is a subclass of the Exception class and is accessed through the preceding System class shown. C# comes with many built-in exception types, but we're not going to dig too deeply into those as that's something you can do once you understand the basics of the overall system.

A full list of C# exceptions can be found under the Choosing Standard Exceptions heading at https://docs.microsoft.com/en-us/dotnet/api/system.exception?view=netframework-4.7.2#Standard.

Time for action – checking negative scene indexes

Let's keep things simple on our first foray into exceptions and make sure that our level only restarts if we provide a positive scene index number:

Open up Utilities and add in the following code to the overloaded version of RestartLevel():

public static class Utilities
 {
     public static int playerDeaths = 0;

     public static string UpdateDeathCount(out int countReference)
     {
         // ... No changes needed ...
     }

     public static void RestartLevel()
     {
         // ... No changes needed ...
     }

     public static bool RestartLevel(int sceneIndex)
     {
         // 1
         if(sceneIndex < 0)
         {
             // 2
             throw new System.ArgumentException("Scene index cannot
                 be negative");
         }

         SceneManager.LoadScene(sceneIndex);
         Time.timeScale = 1.0f;

         return true;
     }
 }

Change RestartLevel() in the OnGUI() method of GameBehavior to take in a negative scene index and lose the game:

if(showLossScreen)
{
      if (GUI.Button(new Rect(Screen.width / 2 - 100,
        Screen.height / 2 - 50, 200, 100), "You lose..."))
      {
           // 3
           Utilities.RestartLevel(-1);
       }
 }

Let's break down the code:

Declares an if statement to check that sceneIndex is not less than 0 or a negative number

Throws an ArgumentException with a custom message if a negative scene index is passed in as an argument

Calls RestartLevel() with a scene index of -1:

When we lose the game now, RestartLevel() is called, but since we're using -1 as the scene index argument, our exception is fired before any of the scene manager logic is executed. This will put a stop to our game as we don't have any other button options at this point, but the safeguard works as expected and doesn't let us take an action that might crash the game (Unity doesn't support negative indexes when loading scenes).

Now that you've successfully thrown an error, you need to know how to handle the fallout from the error, which leads us to our next section and the try-catch statement.

Using try-catch

Now that we've thrown an error, it's our job to safely handle the possible outcomes that calling RestartLevel() might have because at this point, this is not addressed properly. The way to do this is with a new kind of statement, called try-catch:

try
{
    // Call a method that might throw an exception
}
catch (ExceptionType localVariable)
{
    // Catch all exception cases individually
}

The try-catch statement is made up of consecutive code blocks that are executed on different conditions; it's like a specialized if/else statement. We call any methods that potentially throw exceptions in the try block—if no exceptions are thrown, the code keeps executing without interruption. If an exception is thrown, the code jumps to the catch statement that matches the thrown exception, just like switch statements do with their cases. catch statements need to define what exception they are accounting for and specify a local variable name that will represent it inside the catch block.

You can chain as many catch statements after the try block as you need to handle multiple exceptions that might be thrown from a single method.

There's also an optional finally block that can be declared after any catch statements that will execute at the very end of the try-catch statement, regardless of whether an exception was thrown:

finally
{
    // Executes at the end of the try-catch no matter what
}

Your next task is to use a try-catch statement to handle any errors thrown from restarting the level unsuccessfully.

Time for action – catching restart errors

Now that we have an exception that is thrown when we lose the game, let's handle it safely:

Update GameBehavior with the following code and lose the game again:

public class GameBehavior : MonoBehaviour, IManager
 {
     // ... No variable changes needed ...

     // ... No changes needed in Start
            Initialize,
            Print,
            or LogWithDelegate ...

     void OnGUI()
     {
         // ... No other changes to OnGUI needed ...

         if(showLossScreen)
         {
             if (GUI.Button(new Rect(Screen.width / 2 - 100,
              Screen.height / 2 - 50, 200, 100), "You lose..."))
             {
                 // 1
                 try
                 {
                     Utilities.RestartLevel(-1);
                     debug("Level restarted successfully...");
                 }
                  // 2
                 catch (System.ArgumentException e)
                 {
                                                  // 3
                                                  Utilities.RestartLevel(0);
                     debug("Reverting to scene 0: " +
                        e.ToString());
                 }
                                         // 4
                                         finally
                                         {
                                                 debug("Restart handled...");
                                          }
             }
         }
     }
 }

Let's break down the code:

Declares the try block and moves the call to RestartLevel() inside with a debug command to print out if the restart is completed without any exceptions

Declares the catch block and defines System.ArgumentException as the exception type it will handle and e as the local variable name

Restarts the game at the default scene index if the exception is thrown:

Uses the debug delegate to print out a custom message, plus the exception information, which can be accessed from e and converted into a string with the ToString() method

Since e is of the ArgumentException type, there are several properties and methods associated with the Exception class that you can access. These are often useful when you need detailed information about a particular exception.

Adds a finally block with a debug message to signal the end of the exception-handling code:

When RestartLevel() is called now, our try block safely allows it to execute, and if an error is thrown, it's caught inside the catch block. The catch block restarts the level at the default scene index and the code proceeds to the finally block, which simply logs a message for us.

It's important to understand how to work with exceptions, but you shouldn't get in the habit of putting them in everywhere in your code. This will lead to bloated classes and might affect the game's processing time. Instead, you want to use exceptions where they are most needed—invalidation or data processing, rather than game mechanics.

C# allows you the freedom to create your exception types to suit any specific needs your code might have, but that's beyond the scope of this book. It's just a good thing to remember for the future: https://docs.microsoft.com/en-us/dotnet/standard/exceptions/how-to-create-user-defined-exceptions.

Before we close out the chapter, there's one more topic that you need to be introduced to, and that's design patterns. While we won't go into the actual code for these patterns (there are entire books on that), we will talk about their purpose and usability in programming.

Design pattern primer

Before we wrap up the chapter, I want to talk about a concept that will play a huge part in your programming career if you choose to continue: design patterns. Googling design patterns or software programming patterns will give you a host of definitions and examples, which can be overwhelming if you've never encountered them before. Let's simplify the term and define a design pattern as follows:

A template for solving programming problems or situations that you'll run into on a regular basis during any kind of application development. These are not hardcoded solutions—they're more like tested guidelines and best practices that can be adapted to fit a specific situation.

There's a lot of history behind how design patterns became an integral part of the programming lexicon, but that excavation is up to you. If this concept strikes a chord with your programming brain, start with the book Design Patterns: Elements of Reusable Object-Oriented Software and its authors, the Gang of Four.

The design patterns we're talking about only apply to object-oriented programming (OOP) languages and paradigms, so they're not universally applicable if you are working in non-OOP environments. 

Common game patterns

While there are over 35 documented design patterns broken up over four subdomains of functionality, only a few of them are uniquely suited to game development. We'll spend a little time here briefly introducing them so that you get the hang of what we've been talking about so far:

The Singleton pattern ensures that a given class only has one instance in a program, paired with one global access point (very useful for game manager classes). 

The Observer pattern lays out a blueprint for notification systems, alerting subscribers to changes in behavior through events. We've seen this in action on a small scale with our delegate/event examples, but it can be expanded to cover much more ground.

The State pattern allows an object to change its behavior based on what state it's in. This is extremely useful in creating smart enemies who appear to change tactics based on player actions or environmental conditions.

The Object Pool pattern recycles objects that aren't being used anymore, instead of having the program create new ones each time. This would be a great update to the shooting mechanic in Hero Born since there could potentially be a lag if too many bullets were spawned on a machine with low processing power.

If you're still unsure of how important design patterns are, know that Unity is built along the lines of the Composite (sometimes called the Component) pattern, which allows us to construct complex objects made up of separate functional pieces.

Again, this barely scratches the surface of what design patterns can do in real-world programming situations. I highly encourage you to dig into their history and application as soon as you finish up the next chapter—they'll be one of your best resources going forward.

Summary

While this chapter brings us to the end of our adventure into C# and Unity 2020, I hope that your journey into game programming and software development has just begun. You've learned everything from creating variables, methods, and building class object to writing your game mechanics, enemy behavior, and more. 

The topics we've covered in this chapter have been a level above what we dealt with for the majority of this book, and with good reason. You already know your programming brain is a muscle, which you need to exercise before you can advance to the next plateau. That's all generics, events, and design patterns are: just the next rung up the programming ladder.

In the next chapter, I will leave you with resources, further reading, and lots of other helpful (and, dare I say, cool) opportunities and information about the Unity community and the software development industry at large.

Happy coding!

Pop quiz – intermediate C#

What is the difference between a generic and non-generic class?

What needs to match when assigning a value to a delegate type?

How would you unsubscribe from an event?

Which C# keyword would you use to send out an exception in your code?

The Journey Continues

If you started this book as a complete newcomer to the world of programming, congratulations on your achievement! If you came in knowing a bit about Unity or another scripting language, guess what? Congratulations to you as well. If you began with all the topics and concepts we covered already firmly solidified in your head, you guessed it: congratulations. There is no such thing as an insignificant learning experience, no matter how much or how little you may think you came away with. Revel in the time you spent learning something new, even if it only turned out to be a new keyword.

As you reach the end of this journey, it's important to look back at the skills you've acquired along the way. As with all instructional content, there's always more to learn and explore, so this chapter will focus on cementing the following topics and giving you resources for your next adventure:

Programming fundamentals 

Putting C# into action

Object-oriented programming and beyond

Approaching Unity projects

Unity certifications

Next steps and future learning

Scratching the surface

While we've done a good amount of work with variables, types, methods, and classes throughout this book, there are still areas of C# that were left unexplored. Learning a new skill shouldn't be a simple bombardment of information without context; it should be a careful stack of bricks, one on top of the other, each building on the foundational knowledge already acquired.

Here are some of the concepts you'll want to look into as you progress in your programming journey with C#, regardless of whether it's with Unity:

Optional and dynamic variables

Debugging approaches

Concurrent programming

Networking and RESTful APIs

Recursion and reflection

LINQ expressions

Design patterns

As you revisit the code we've written throughout this book, don't just think about what we accomplished, but also about how the different parts of our project work together. Our code is modular, meaning actions and logic are self-contained; our code is flexible, making it easy to improve and update; our code is clean, making it readable to anyone who looks at it down the line, even if that's us.

The takeaway here is that digesting basic concepts takes time. Things don't always sink in on the first try, and the "Aha!" moments don't always come when you expect. The key is to keep learning new things, but always with one eye on your foundation.

Let's take our own advice and revisit the tenets of object-oriented programming in the next section.

Remembering your object-oriented programming

Object-oriented programming is a vast field of expertise, and its mastery requires not only study but the time spent applying its principles to real-life software development. With all the foundational information you learned in this book, it might seem like a mountain you're just better off not even attempting to climb. However, when you feel that way, take a step back and revisit these concepts:

Classes are blueprints for objects you want to create in code:

Classes can contain properties, methods, and events.

Classes use constructors to define how they are instantiated.

Instantiating objects from a class blueprint creates a unique instance of that class.

Classes are reference types, while structs are value types.

Classes can use inheritance to share common behavior and data with subclasses.

Classes use access modifiers to encapsulate their data and behaviors.

Classes can be composed of other class or struct types.

Polymorphism allows subclasses to be treated the same as their parent class:

Polymorphism also allows subclass behaviors to be changed without affecting the parent class.

Approaching Unity projects

Even though Unity is a 3D game engine, it still has to follow the principles set down by the code it's built on. When you think of your game, remember that the GameObjects, components, and systems you see on screen are just visual representations of classes and data; they're not magical or unknown—they're the result of taking the programming foundations you've learned in this book to their advanced conclusion. 

Everything in Unity is an object, but that doesn't mean all C# classes have to work within the engine's MonoBehavior framework. Don't be limited to thinking in game mechanics; branch out and define your data or behavior the way your project needs. 

Lastly, always ask yourself how you can best separate code out into pieces of functionality instead of creating huge, bloated, thousand-line classes. Related code should be responsible for its behavior and stored together. That means creating separate MonoBehavior classes and attaching them to the GameObjects they effect. I said it at the beginning of this book and I'll say it again: programming is more a mindset and contextual framework than syntax memorization. Keep training your brain to think like a programmer and eventually, you won't be able to see the world any differently.

Unity features we didn't cover

We managed to briefly cover many of Unity's core features in Chapter 6, Getting Your Hands Dirty with Unity, but there is still so much more the engine has to offer. These topics aren't in any particular order of importance, but if you're going forward with Unity development, you'll want to have at least a passing familiarity with the following:

Shaders and effects

Scriptable objects

Editor extension scripting

Non-programmatic UI

ProBuilder and Terrain tools

PlayerPrefs and saving data

Model rigging

Animator states and transitions

You should also go back and dive into the Lighting, Navigation, Particle Effects, and Animation features in the editor.

Next steps

Now that you have a basic level of literacy in the C# language, you're ready to seek out additional skills and syntax. This most commonly takes the form of online communities, tutorial sites, and YouTube videos, but it can also include textbooks, such as this one. Transitioning from being a reader to an active member of the software development community can be tough, especially with the abundance of options out there, so I've laid out some of my favorite C# and Unity resources to get you started.

C# resources

When I'm developing games or applications in C#, I always have the Microsoft documentation open in a window I can get to easily. If I can't find an answer to a specific question or problem, I'll start checking out the community sites I use most often:

C# Corner: https://www.c-sharpcorner.com

Dot Net Pearls: http://www.dotnetperls.com

Stack Overflow: https://stackoverflow.com

Since most of my C# questions relate to Unity, I tend to gravitate toward those kinds of resources, which I've laid out in the next section.

Unity resources

The best Unity learning resources are at the source; video tutorials, articles, free assets, and documentation are all available from https://unity3d.com. However, if you're looking for community answers or a specific solution to a programming problem, give the following sites a visit:

Unity Learn: https://learn.unity.com

Unity Answers: https://answers.unity.com

Stack Overflow: https://stackoverflow.com

Unify Community wiki: http://wiki.unity3d.com/index.php

Unity Gems: http://unitygems.com

There is also a huge video tutorial community on YouTube if that's more your speed; here are my top five:

Brackeys: https://www.youtube.com/user/Brackeys

quill18creates: https://www.youtube.com/user/quill18creates

Sykoo: https://www.youtube.com/user/SykooTV/videos

Renaissance Coders: https://www.youtube.com/channel/UCkUIs-k38aDaImZq2Fgsyjw

BurgZerg Arcade: https://www.youtube.com/user/BurgZergArcade

The Packt library also has a wide variety of books and videos on Unity, game development, and C#, available at https://search.packtpub.com/?query=Unity.

Unity certifications

Unity now offers various levels of certification for programmers and artists that will lend a certain amount of credibility and empirical skill ranking to your resume. These are great if you're trying to break into the game industry as a self-taught or non-computer science major, and they come in the following flavors:

Certified Associate

Certified User: Programmer

Certified Programmer 

Certified Artist

Certified Expert – Gameplay Programmer

Certified Expert – Technical Artist: Rigging and Animation

Certified Expert – Technical Artist: Shading and Effects

Unity also provides preparatory courses in-house and through third-party providers to help you get ready for the various certifications. You can find all the information at https://certification.unity.com.

Never let a certification, or the lack of one, define your work or what you put out into the world. Your last hero's trial is to join the development community and start making your mark.

Hero's trial – putting something out into the world

The last task I'll offer you in this book is probably the hardest, but also the most rewarding. Your assignment is to take your C# and Unity knowledge and create something to put out into the software- or game-development communities. Whether it's a small game prototype or a full-scale mobile game, get your code out there in the following ways: 

Join GitHub (https://github.com).

Contribute to the Unify Community wiki.

Get active on Stack Overflow and Unity Answers.

Sign up to publish custom assets on the Unity Asset Store (https://assetstore.unity.com).

Whatever your passion project is, put it out into the world. 

Summary

You might be tempted to think that this marks the end of your programming journey, but you couldn't be more wrong. There is no end to learning, only a beginning. We set out to understand the building blocks of programming, the basics of the C# language, and how to transfer that knowledge into meaningful behaviors in Unity. If you've gotten to this last page, I'm confident we've achieved those goals, and you should be too. 

One last word of advice that I wish someone had told me when I first started: you're a programmer if you say you are. There will be plenty of people in the community that will tell you that you're an amateur, that you lack the experience necessary to be considered a "real" programmer, or, better yet, that you need some kind of intangible professional stamp of approval. That's false: you're a programmer if you practice thinking like one regularly, aim to solve problems with efficiency and clean code, and love the act of learning new things. Own that identity; it'll make your journey one hell of a ride.

Pop Quiz Answers

Chapter 1 – Getting to Know Your Environment

Pop quiz – dealing with scripts

Q1 | Unity and Visual Studio have a symbiotic relationship.
Q2 | Reference Manual.
Q3 | None, as it is a reference document, not a test.
Q4 | When the new file appears in the Project tab with the filename in edit mode, which will make the class name the same as the filename and prevent naming conflicts.

Chapter 2 – The Building Blocks of Programming

Pop quiz – C# building blocks

Q1 | Storing a specific type of data for use elsewhere in a C# file.
Q2 | Methods store executable lines of code for fast and efficient reuse.
Q3 | By adopting MonoBehavior as its parent class and attaching it to a GameObject.
Q4 | To access variables and methods of components or files attached to different GameObjects.

Chapter 3 – Diving into Variables, Types, and Methods

Pop quiz #1 – variables and types

Q1 | Using camelCase.
Q2 | Declare the variable as public.
Q3 | public, private, protected, and internal.
Q4 | When an implicit conversion doesn't already exist.

Pop quiz #2 – understanding methods

Q1 | The type of data returned from the method, the name of the method with parentheses, and a pair of curly brackets for the code block.
Q2 | To allow parameter data to be passed into the code block.
Q3 | The method will not return any data.
Q4 | The Update() method is called every frame.

Chapter 4 – Using Collections and Controlling Your Code

Pop quiz #1 – if, and, or but

Q1 | True or false
Q2 | The NOT operator, written with the exclamation mark symbol (!)
Q3 | The AND operator, written with double ampersand symbols (&&)
Q4 | The OR operator, written with double bars (||)

Pop quiz #2 – all about collections

Q1 | The location where data is stored.
Q2 | The first element in an array or list is 0, as they are both zero-indexed.
Q3 | No – when an array or a list is declared, the type of data it stores is defined, making it impossible for elements to be of different types.
Q4 | An array cannot be dynamically expanded once it is initialized, which is why lists are a more flexible choice as they can be dynamically modified.

Chapter 5 – Working with Classes, Structs, and OOP

Pop quiz – all things OOP

Q1 | The constructor
Q2 | By copy, rather than by reference like classes
Q3 | Encapsulation, inheritance, composition, and polymorphism
Q4 | GetComponent

Chapter 6 – Getting Your Hands Dirty with Unity

Pop quiz – basic Unity features

Q1 | Primitives
Q2 | The z axis
Q3 | Drag the GameObject into the Prefabs folder
Q4 | Keyframes

Chapter 7 – Movement, Camera Controls, and Collisions

Pop quiz – player controls and physics

Q1 | Vector3
Q2 | InputManager
Q3 | A Rigidbody component
Q4 | FixedUpdate

Chapter 8 – Scripting Game Mechanics

Pop quiz – working with mechanics

Q1 | A set or collection of named constants that belong to the same variable
Q2 | Using the Instantiate() method with an existing Prefab
Q3 | The get and set accessors
Q4 | OnGUI()

Chapter 9 – Basic AI and Enemy Behavior

Pop quiz – AI and navigation

Q1 | It's generated automatically from the level geometry.
Q2 | NavMeshAgent
Q3 | Procedural programming.
Q4 | Don't repeat yourself.

Chapter 10 – Revisiting Types, Methods, and Classes

Pop quiz – leveling up 

Q1 | Readonly
Q2 | Change the number of method parameters or their parameter types.
Q3 | Interfaces cannot have method implementations or stored variables.
Q4 | Create a type alias to differentiate conflicting namespaces.

Chapter 11 – Introducing Stacks, Queues, and HashSets

Pop quiz – intermediate collections

Q1 | Stacks
Q2 | Peek
Q3 | Yes
Q4 | ExceptWith

Chapter 12 – Exploring Generics, Delegates, and Beyond

Pop quiz – intermediate C#

Q1 | Generic classes need to have a defined type parameter.
Q2 | The values method and the delegates method signature.
Q3 | The -= operator.
Q4 | The throw keyword.

Other Books You May Enjoy

If you enjoyed this book, you may be interested in these other books by Packt:

Hands-On Unity 2020 Game Development
Nicolas Alejandro Borromeo

ISBN: 978-1-83864-200-6

Write scripts for customizing various aspects of a game, such as physics, gameplay, and UI

Program rich shaders and effects using Unity's new Shader Graph and Universal Render Pipeline

Implement postprocessing to increase graphics quality with full-screen effects

Create rich particle systems for your Unity games from scratch using VFX Graph and Shuriken

Add animations to your game using the Animator, Cinemachine, and Timeline

Implement game artificial intelligence (AI) to control character behavior

Detect and fix optimization issues using profilers and batching

Unity Certified Programmer: Exam Guide
Philip Walker

ISBN: 978-1-83882-842-4

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 that interact with Unity's Rendering API

Explore Unity APIs for adding lighting, materials, and texture to your apps

Write Unity scripts for building interfaces for menu systems, UI navigation, application settings, and much more

Delve into SOLID principles for writing clean and maintainable Unity applications

Leave a review - let other readers know what you think

Please share your thoughts on this book with others by leaving a review on the site that you bought it from. If you purchased the book from Amazon, please leave us an honest review on this book's Amazon page. This is vital so that other potential readers can see and use your unbiased opinion to make purchasing decisions, we can understand what our customers think about our products, and our authors can see your feedback on the title that they have worked with Packt to create. It will only take a few minutes of your time, but is valuable to other potential customers, our authors, and Packt. Thank you!