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:

  1. Change the access modifier in front of currentAge from public to private and save the file.
  2. 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:

  1. Declare a public method with a void return type called GenerateCharacter().
  2. Add a simple Debug.Log() that prints out a character name from your favorite game or movie.
  3. 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:

  1. Choose a character prints out first because it's the first line of code.
  2. When GenerateCharacter() is called, the program jumps to line 23, prints out Character: Spike, then resumes execution at line 17.
  3. 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:

  1. 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.
  2. Update Debug.Log() so that it uses these new parameters.
  1. 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:

  1. Change the return type in the method declaration from void to int.
  2. 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:

  1. 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.
  2. 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.
  3. Comment out the debug log inside GenerateCharacter() with two backslashes (//) to make the console output less cluttered.
  4. 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

  1. What is the proper way to write a variable name in C#?
  2. How do you make a variable appear in Unity's Inspector window?
  3. What are the four access modifiers available in C#?
  4. When are explicit conversions needed between types?
  5. What are the minimum requirements for defining a method?
  6. What is the purpose of the parentheses at the end of the method name?
  7. What does a return type of void mean in a method definition?
  8. How often is the Update() method called by Unity?
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.130.31