An additional resource for you is a Unity “SampleGame” found in the SampleGame directory in the BookContents Git repo. A small set of free to use assets are ready for you to use in your own projects. This project also contains many helper objects that will show you how games are built.
Often, small game studios store their art assets in the Git repo along with the source code for the game. I’ve stored some asset files in the Art Assets directory next to the Unity Project. Feel free to inspect the 3D models in Blender and the 2D assets in Gimp. Both Blender and Git are open source and free to use for noncommercial use.
In the Unity Project directory called Unity-SampleGame you’ll find that Assets has various directories to keep the Assets directory relatively organized. All the assets used specifically for the sample game are under the SampleGame sub-directory. In there, you’ll find that Meshes, Prefabs, Materials, Textures, Scripts, and others have their own homes. Starting many projects in Unity 3D has formed a habit of organization. Your projects don’t need to follow the same pattern, but anything similar will help in the future.
When an asset from the Unity asset store is downloaded, you’ll usually find that it will live in its own directory, not unlike the one created for SampleGame. This convention of creating a directory in Assets for your own work is standard and should be followed to avoid conflicting files.
To get started with a new Unity 3D project you’ll want to look at a checklist of a few settings before using source control. First are the meta files. Unity 3D creates additional text files that help link different assets to one another. To remember these associations in Git, you’ll want to make them visible. Meta files allow the editor to restore the associations when a project is opened. Select Edit → Project Settings → Editor and find the drop-down menu with the Version Control section and set the Mode to Visible Meta Files.
A game development cycle starts with a game design document. This includes what the player does and concept art as to what the game looks like as the player experiences your game. Often game designs also include some story and a world description. Assuming all of that is done, the engineering begins.
Programmers begin with the Unity Project and sometimes start some sort of technical document. A large game would often involve specific programming goals like database management for inventory, automatic loot box generation, or a multi-player match-making strategy. Before all of that comes prototyping and experimentation.
It’s common for the experimentation to change the game design. Through experimentation unexpected game play experiences can emerge. New game play elements appear as code is written and unexpected results often appear either by accident or inspiration. Experimentation in Unity 3D means reading what Unity 3D has in its tool kit. Documentation available online often gives a general description of each Unity 3D function, but that hardly has meaning without seeing how the function works or what it does.
3.3.1 Movement Experiments
In the SampleGame Unity Project open the Assets/SampleGame/Scenes/Chapter3 Unity scene. A strange little object made of colored arrows should sit in the middle of the scene. This was created with a box collider and a rigid body attached. This gives the object physics properties that will make it fall and land on the ground under it without falling over.
This object has been named TransformPrimitive to denote that we’re going to use this to show us how to use the Transform Component of the Game Object. This little object has arrows pointing in each coordinate direction with a + sign pointing in the positive direction of that axis. Attached to this object is a script called MovementExperiment.cs which we’ll open in Visual Studio to inspect. This can be found in the Assets/SampleGame/Scripts directory in the project. The Start function contains a single line:
This line tells the object to set the GameObject’s transform.position to a new Vector3 with the values for x and y set to 0 and z set to 1. As a hint, since we’re new to Unity 3D most objects in a scene hierarchy are GameObjects. The GameObject is the fundamental type of “thing” that makes up the different elements that can interact within the game’s scene. This is discovered by reading the Unity Documentation.
3.3.2 Reading Programmer Documentation
Programming documentation has become roughly standardized, to some extent programmers have come to use a similar format when it comes to sharing how their code is used. Unfortunately, it’s still written by programmers, so for the uninitiated, definitions are obtuse.
Most of the time there’s some type of outline with entries describing everything available in the API, or Application Program Interface. You can open the web page directly from Unity with the Help → Scripting Reference menu selection.
The following page is the Unity Scripting Reference. A column on the left shows you everything that the Unity 3D engineers have written anticipating what you might want to do in your game. This assumes you know what to do with the information.
Under the UnityEngine are most of the different systems that you’ll eventually find useful for your game. In UnityEngine/Classes/GameObject you’ll find an entry describing the GameObject.
After the description you’ll see the different things accessible inside of the object, or the object’s members. Each one of the members is something that you can access, read, or ask the object to do. The members are separated as Properties, Constructors, Public Methods, etc.
Clicking on a member of the object will usually show you a similar page with the name of the function, sometimes followed by some example for using the member in question. The documentation for GameObject tells us that Transform is always added to a GameObject when created.
In addition to online documentation, Visual Studio has useful information built-in. Open the SampleGame/Chapter3/Scripts/MovementExperiment.cs script and Right click on the word position in Visual Studio and select Peek Definition Alt+F12 from the pop-up menu. In Visual Studio for Mac you’ll select Go To Declaration ⌘D to see a similar description.
In Visual Studio for Mac
A new window, or part of a window appears beneath the line of code we’re looking at. This window allows us to scroll through different members of the Transform class that position shares a relation with. As you scroll through the different listings you’ll see a get; and a set; by some of the members and just get; listed under others. The members with set; mean this is a property you can change, ones with just get; you’re only able to read.
Scrolling down even further you’ll see some listings which end with (); some of these have additional code in the parenthesis, these are called function members. Clicking on the small box with the + sign in the left margin expands a comment left by the programmer with a summary of what the function or property member is used for.
Expanding the comment next to Position reveals the summary: The position of the transform in world space. The name of the member is preceded by Vector3 indicating the type the property represents. With this you have the start of how to navigate around in Visual Studio just enough to begin experimenting. Press Escape to close the window or click on the close icon on the top right of the tab.
If we wanted to experiment with rotation, we might look at localEulerAngles see if we can change the transform primitive’s rotation when the game starts. Pressing Play shows the transform primitive tipped up for a moment before falling. As we learn more vocabulary, the different sections in the documentation will have more meaning. The headings Static Methods, Inherited Members, and some others may not mean much right now, but as you read on they will.
The scripts you create for your game should live together in their own directory. When you download additional Unity Packages from the Asset store they will usually also live in their own respective project directories. Assets like your Materials, Prefabs, Scenes etc. should all live in their own directories to stay organized.
Unity 3D also has included a few of their own built-in packages for in-app purchases and analytics which live in their own sub-directories in the Packages directory in your projects next to the Assets directory.
When you feel more confident, continue some experiments on your own. If you can’t get things working immediately don’t let that put you off. As you learn more about C# you’ll also have more knowledge to experiment with. As you feel more comfortable with the language, you’ll also learn to read and understand what the comments left by the Unity 3D programmers mean.
For now, at least, we have a project setup in which we’ll be able to construct our sample game.
3.4 Tokens
To get started open the BookContents/Chapters/Chapter3 Unity Project. To see the example code, look in the Assets and open the C# files named after each section. To follow along with this chapter you should open the Tokens.cs file in Visual Studio to see what the code looks like.
In written English the smallest elements of the language are letters, numbers, and punctuation. Individually, most letters and numbers lack specific meaning. The next larger element after the letter is the word. Each word has more meaning, but complex thoughts are difficult to convey in a single word.
To communicate a thought, we use sentences. The words in a sentence each have a specific contribution to the intent, as seen in the diagram below. To convey a concept, we use a collection of sentences grouped into a paragraph. And to convey a story we use a collection of many paragraphs organized into chapters and integrated into a book.
Programming has similar organizational mechanisms. The smallest meaningful element is a token, followed by statements, code blocks, and functions, followed by classes and namespaces, and eventually a program, or in our case the program is a game. Every file created in the process of writing C# is called a Compilation Unit, or source file.
We will begin with the smallest element and work our way up to writing our own classes. However, it’s important to know the very smallest element to understand how all the parts fit together before we start writing complex code.
3.4.1 Writing C#
C# is an English-based programming language; like any other C-like programming language, it can be broken into tokens, the smallest meaningful fragment of text that the computer can understand. In a mechanical way of thinking, writing a story is the process of arranging words to convey meaning. In much the same way, writing code is arranging tokens to instruct the computer to carry out a process.
A token is made of a single character or a series of characters. For instance, in a simple statement like the following there are five tokens.
Tokens can be categorized as a keyword, identifier, literal, assignment operator, and separator. The above statement contains five tokens with spacing between each provided by white space. Together these tokens create an assignment statement.
The keyword int is followed by the identifier i. This is followed by an operator = which assigns the identifier on the left of the operator and the value of the literal 0 on the right. The last token ; is a separator which ends the statement. White spaces and comments, which we have yet to cover, are not considered tokens, although they can act as separators. This is a lot to take in at once, but each one of these words will be further explained as we continue.
The code shown above is two statements, the separator keeps the computer from misreading the code as a single statement. However, there are humans involved with writing code, and thus code needs to be readable by humans, or any other life form which might read your code. Proper code means following a specific formatting style. We’ll dive into more about proper code style later in this chapter.
Each group of characters, or text, is converted by a lexical analyzer, sometimes called a lexer, in the computer and turned a symbol. This process is called tokenization, or as a computer scientist would say, a lexer tokenizes your code into symbols. Once tokenized, the symbols are parsed; this process organizes the tokens into instructions for the computer to follow. This unique vocabulary does frame programmers as a strange group of alien beings with a language all to themselves; I promise, however, conversations about lexical analyzers don’t come up very often when talking with most programmers.
This may seem like a great deal of work and we are jumping ahead into more complex computer science topics, but all of this happens behind the scenes, so you don’t need to see any of this taking place. It’s important to understand what a compiler does and how it works, so you can write code in a way that the computer can understand. Therefore, a simple typo will stop the lexer from building code.
A computer can’t interpret or guess at what it is you’re trying to do. Therefore, if you mistype int i = 0: and not int i = 0; the last token cannot be converted by the lexer into a proper symbol, and thus this statement will not be parsed at all. This code results in an error before the code is even compiled.
3.4.2 Comments a First Look
In the Assets directory open the Comments script. One of the first types of tokens we’ll want to investigate is the comment. Each section in the code will have a corresponding comment like the one below.
We’ll go into more detail on various types of comments and how they’re used in Section 3.14, but it’s important to know what they look like before moving on. In each C# source file, you’ll see various with // at the beginning of the line. This tells the lexical analyzer to ignore anything appearing after the // on that line.
Comments allow the author of the C# script to inform other readers of any information not included in the code itself. In addition to the // comment is the /* comment */ where anything between the start of the /* comment and the ending */ will also be invisible from the lexical analyzer.
Both types of comments are used extensively through all the code samples provided with each chapter.
In some cases, working code will be hiding behind a comment. To see how the code works, delete the // at the start of the line of code to reveal the statement to the lexical analyzer for compiling. To hide the code, simply put the // back at the beginning of the line. Programmers often leave code they’re working on in comments to keep work in progress knowing that the code isn’t fully functioning.
Single line comments that use // are used extensively throughout the code with the intent for you to uncomment them to see how code works. Uncommenting one line and commenting out another is a common strategy to test out various ways to solve a problem.
In a clean code base the code itself will speak clearly as to what it’s doing. Comments would be kept to a minimum. If a function requires comments to explain how it works, then the function could be re-written to be easier to understand.
3.4.3 Separator Tokens
The most common separator is the semicolon (;). This mark of punctuation is used to end a code statement. Other separator tokens come in pairs. Curly braces are used in pairs to separate groups of code statements. The opening curly brace is the { with the pointy end pointed to the outside of the group; this is paired with the } which closes the curly brace pair.
Parentheses start with the ( and end with the ) while square brackets start with [ and end with ]. The different opening and closing braces, brackets, and parentheses have specific purposes that are used to help identify what they are being used for. There are angle brackets < and > as well; these are used to surround data types. And don’t forget both single quotes ‘ and double quotes ”, which are used in pairs as well to surround symbols called strings.
NOTE: In many word processors a beginning and an ending quote (“ and ”) are often used. The lexical analyzer doesn’t recognize these smart quotes, so the text editor for programming uses straight quote marks (“) instead. These subtle changes aren’t so subtle once you realize that letters, numbers, and every other character used in programming are all parsed as American Standard Code for Information Interchange (ASCII) or unicode transformation format (UTF)-8, -16, or -32. Both ASCII and UTF are names given to the database of characters that the computer uses to read text. Computers don’t use eyes to read. Rather, they use a database of numbers to look up each character being used. Therefore, to a computer “ looks more like 0×201C and “ like 0×0022 (the expected quotation mark), if we were using UTF-16.
Curly braces are used in code to separate statements. Parentheses are usually used to accept data. Square brackets are often used to help identify arrays. We’ll get into arrays later; imagine something like a spreadsheet or a graph paper until we get to their explanation.
The above statement assigns three numbers to an array. The curly braces contain a pair of statements separated by a comma. The second statement in the curly braces is converting a number with a dot (.) into a number without a dot in it. This will take a bit of explanation, but it’s good to get used to seeing this sort of thing. We’ll get to what an array is soon enough, as well as a dot operator.
Often, when learning to program you’ll see many strange tokens which might not seem to have any meaning. It’s up to you to observe every token you come across and try to understand what it does. If none of the statements makes sense that’s fine. At this point you’re not expected to know what all this means yet, but you soon will.
Operator Tokens
Operators are like keywords but use only a few non-word characters. The colon or semi-colon is an operator; unlike keywords, operators change what they do based on where they appear, or how they are used. This behavior shifting is called operator overloading, and it’s a complex subject that we’ll get into later. It’s also important to know that you can make up your own operators when you need to.
Commas in C# have a different meaning. A comma is used to separate different data values. Another special operator token is the semi-colon.
Here, the : was used following the name of the class to inform the compiler we’re going to be adding to a pre-existing class called MonoBehaviour. In this case, : will tell our class to add new behaviors to MonoBehaviour. This means to build upon a class which already exists. We’re creating a new child object related to MonoBehaviour; this is a class that was created by the developers at Unity 3D.
Here’s a list of the various math related operators you can expect to find in C#.
Operators provide our basic ability to do math in code. Operators are special characters that take care of specific operations between variables. Notation or the order in which operators and variables appear is normally taught with the following notation or mathematic grammar: a + b = c. However, in programming the following is preferred: c = a + b; the change is made because the = operator in C# works differently from its function commonly taught in math class.
The left side of the = operator is the assignment side, and the right side is the operation side. This puts the emphasis on the value assigned rather than how it’s assigned. The result is more important than how the problem is solved since the computer is doing the math. In programming the above example should be read as “c is assigned a plus b.”
3.4.5 Literals
Literals are values which come in many different forms. Literals are things you assign to variables, or compare variables to. Literals can be considered tokens used in a literal manner. Numbers can be called numeric literals. Words like “This is a string” is also considered a literal.
Numbers are literals as well. The int or integer is a hole number like 1 or 100. We are used to seeing large numbers like 10,000 or ten thousand with a comma separating every thousand. However, commas have a different meaning in C#. To the compiler, 10,000 is a value 10 followed by a different value 000, a second number. This causes problems so large numbers need to be written without the comma. So, ten thousand in C# needs to be written as 10000 without the comma.
When assigning values to variables we often use literals to set a value for something specific. For example, if we wanted to convert radians to degrees we use the equation degrees = (radians × π) / 180 to get an answer. In C# that would look like the following:
The example shows float radians = 3.14159f; where 3.14159f is the literal assigned to radians. Later Mathf.PI is not a literal but a different type of number called a const; more on that later. radians which is a variable that was assigned a literal. Then we divide that by 180 which is a literal.
When values like 180 are used in code directly it’s sometimes called a “hard-coded” value. In cases where the math isn’t going to change, like in the radian to degree conversion, this isn’t a bad practice. However, in some cases like screen resolution a hard-coded value might work against you.
If you wanted to check where a mouse pointer was in relation to the center of a screen you might use half the screen width and half the screen height to get the center of the screen. Assuming you’re on a computer with a 1920 pixel wide screen that’s 1080 pixels tall you might use x = 960 y = 540 as the center of the screen. This would be wrong if the screen resolution were to change. You can’t change a hard-coded value once you’ve built your project into a game to distribute.
When something is put into quotes, as in “I’m a literal,” you are writing a string literal. Literals are common throughout nearly all programming languages and form the base of C#’s data types. Right now, we’re dealing with only a few different types of literals, but there are many more we will be aware of soon.
3.4.6 Transitive and Non-Transitive Operations
In math class it’s sometimes taught how some operations are transitive and others not. For instance, 2 + 3 + 4 and 4 + 3 + 2 result in the same values. Where each number appears between the operators doesn’t matter, so the + operator is considered transitive. This concept holds true in C#. Results change when we start to mix operators.
This code fragment shows two different results using the same operators but with different number placement. We’ll see how to overcome these problems later when we start using them in our game. Operator order is something to be aware of, and it’s important to test each step as we build up our calculations to make sure our results end up with values we expect.
To ensure that the operations happen as you expect, either you can use parentheses to contain operands or you can do each calculation. We’ll dive further into more detail with a second look at operators later, but this will have to wait until we have a much better understanding of the basics of C#.
3.4.7 Putting It All Together
The Unity 3D engineers have prepared many data types, functions, objects, and classes tailored specifically for game development. Classes often work together to share and manipulate data. There are exceptions to this rule, but C# allows for many different writing styles and paradigms.
Creating data produces components of an object. In general, you’ve created something that can be reused many times. Once you have created a zombie by adding brain-seeking logic and how many bullets are required to stop him, it’s only a matter of duplicating that zombie to create a mob to terrorize your player.
As we proceed we’ll become more familiar with what it means to use classes as objects; the tutorials will be aimed at making sure that this is all clear.
In the Assets directory for Chapter 3 you’ll find a Tokens_Scene Unity scene. Open this and press the Play-in-Editor button. Open the Console window and you’ll see the following output.
Let’s break this code sample down. The first word is public, which is a keyword followed by void another keyword token. Start() is an identifier. A Tokens game object in the Unity scene has the Tokens.cs script attached and Unity executes the Start() function when the game begins.
The parentheses after the Start are operators used when we need parameters for our function. The parameters are discussed in Section 3.3.2. For now, take in the fact that both opening (and closing) parentheses are necessary after function identifiers. They’re useful; trust me.
To begin the contents of the function we start with curly braces, the opening { and closing } curly braces. Everything that the function will do must be contained between the two braces. Any text appearing outside of them will not be part of the Start() function. The code contained in the curly braces becomes the function block of code, or code block.
The first statement in the function’s code block is int i = 0; this is an assignment statement, where we declare int i and then use the = to assign i a literal value of 0. This is then separated from the next statement with the ; operator.
The assignment statement is followed by while (i < 10), which is called a looping condition. We’ll read more on looping conditions later in Section 4.12. This looping statement is followed by an opening { curly brace. This indicates a code block specifically written for the while loop.
The code block contains the statement Debug.Log(i);, which is a function call using i as a parameter or argument. After the print function call is i++;, which applies the post-increment operator ++ to the variable i. This code block is ended with the closing } curly brace.
The while code block and the int = 0; are both within the Start() code block. To make the code more readable, tabs are added to indicate a separation between each section of code. Now bask in the knowledge that we’ve got words to describe everything presented in the above code sample. We may not understand everything that has been written, but we’ve got some vocabulary where we can get started.
3.4.8 What We’ve Learned
Programmers are meticulous about syntax. They have to be. The following two statements are very different: int myint0 = -1; and Int my 0int = – 1:. The first one will compile; the second one has at least five errors. You may not be able to spot the problems in the second statement, but by the end of the next section you will.
Internet forums are a great source for getting help when learning how to write code. When asking programmers for help it’s best to limit your subject and consider your words carefully. Asking “How would I write a video game where you shoot zombies with a flame thrower” involves so many different concepts it’s impossible for anyone to tell you where to start. At best you might get a link to a book on Amazon. In a single post, you’re asking for too much.
If you formulate a smaller question like, “How do I attach flames to a zombie after it’s been hit by a projectile?” you’re more likely to get an answer. When talking to a programmer, using proper syntax, and providing context as to what you’re code looks like and asking why it doesn’t work, you are more likely you’ll get the answer you were looking for.
3.5 Statements and Expressions
To continue, open the StatementsAndExpressions.cs file to follow along. When reading a book or story, you extract meaning from an ordered chain of words. In a similar way, computers extract commands from a chain of ordered instructions. In English we call this a sentence; programmers call this a statement. A statement is considered to be any chunk of code which accomplishes some sort of task separated by a semicolon.
At the center of any given task is the algorithm, not to be confused with a logarithm. An algorithm is a systematic process that accomplishes a task. In many ways you can think of it as a recipe, or rather a recipe is an algorithm for making food.
It could take one or two statements to accomplish a task, or it could take many hundreds of statements. This all depends on the difficulty of a given task. Each individual statement is a step toward a goal. This is the difference between spending a few minutes frying an egg, or spending an hour baking a soufflé. Each individual step is usually fairly simple; it’s the final result that matters.
Like sentences, statements have different forms. Statements can declare and assign values to variables. These are called declaration and assignment statements. These statements are used to set up various types of data and give them names.
3.5.1 Expressions
The subjects of your statements are called identifiers. An assignment statement is used to give an identifier a value. When you read “The menu has kale for lunch and broccoli for dinner.” The C# version might look a bit like the following:
Assignment statements often incorporate some sort of operation. These are called expressive statements. Different from expressing an emotion, expressions in code look more like “x + y.” Expressions process data. After processing, the result of an expression can be assigned to a variable. We’ll learn more about variables and assignments in Section 3.10.2. Literals can also be used in an expression, for example myInt = x + 1; where 1 is a literal, but x may be a variable assigned before this expression. Together, x + 1; is an expressive statement, but x and 1 alone are not.
A collection of statements is called a code block, like a building block, something that’s used to build. When writing a story, we call a collection of sentences a paragraph. The statements in a block of code work with each other to accomplish a task. An expression can also ask a block of code to execute. This is called an invocation expression or an expression that invokes a method.