10

Revisiting Types, Methods, and Classes

Now that you’ve programmed the game’s 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 being 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 modifiers

While we’ve gotten into the habit of pairing the public and private access modifiers with our variable declarations, like we did with player health and items collected, 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 Intermediate OOP section:

  • const
  • readonly
  • static
  • abstract
  • override

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. For example, you couldn’t mark an instance of our Character class as a constant. A good candidate for a constant value is MaxItems in the GameBehavior class:

public const int MaxItems = 4; 

The above code would essentially lock the value of MaxItems at 4, making it unchangeable. 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. As an alternative, we can use readonly, which won’t let you write to the variable, meaning it can’t be changed:

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. A good place for this would be the Start() or Awake() methods in one of our scripts.

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, like we had with our very first Character class 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.

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:

  1. Create a new C# script in the Scripts folder and name it Utilities.
  2. 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; 
        } 
    } 
    
  3. Delete the code inside RestartLevel() from GameBehavior and instead call the new utility method with the following code:
    // 5
    public void RestartScene()
    {
        Utilities.RestartLevel();
    }
    

Let’s break down the code:

  1. First, it adds the using SceneManagement directive so that we can access the LoadScene() method.
  2. 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.
  3. Next, it creates a public static variable to hold the number of times our player has died and restarted the game.
  4. After, it declares a public static method to hold our level restart logic, which is currently hardcoded in GameBehavior.
  5. Finally, our update to GameBehavior calls RestartLevel() from the static Utilities class when the win or the lose 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 from GameBehavior and put it into its static class, which makes it easier to reuse across our code base. 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 will enable you to build out your own set of utilities and tools when managing larger and more complex projects down the road. Now it’s time to move on to methods and their intermediate capabilities, which includes method overloading and ref and out parameters.

Revisiting methods

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 provides added flexibility when you need more than one option for a given operation.

The RestartLevel() method in Utilities 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 expand 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.

Let’s add an overloaded version of RestartLevel():

  1. 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;
        } 
    }
    
  2. Open GameBehavior and update the call to the Utilities.RestartLevel() method to the following:
    // 4
    public void RestartScene()
    {
        Utilities.RestartLevel(0);
    }
    

Let’s break down the code:

  1. First, it declares an overloaded version of the RestartLevel() method that takes in an int parameter and returns a bool.
  2. Then, it calls LoadScene() and passes in the sceneIndex parameter instead of manually hardcoding that value.
  3. Next, it returns true after the new scene is loaded and the timeScale property has been reset.
  4. Finally, our update to GameBehavior calls the overloaded RestartLevel() method and passes in 0 as the sceneIndex. Overloaded methods are automatically detected by Visual Studio and are displayed by number, as shown here:

    Figure 10.1: Multiple method overloads in Visual Studio

    To provide a complete view of the Unity editor, all our screenshots are taken in full-screen mode. For color versions of all book images, use the link below: https://packt.link/7yy5V.

The functionality in the RestartLevel() method is now much more customizable and can account for additional situations you may need later. In this case, it is restarting the game from any scene we choose.

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.

Next up, we’re going to cover two additional topics that can take your method game to a whole new level—ref and out parameters.

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. This protects us from making unwanted changes to existing variables when we use them as method parameters. While this works for most cases, there are situations where you’ll want to pass in a method argument by reference so that it can be updated and have that change reflected in the original variable. 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.

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()
    { 
       // ... No changes needed ...   
    } 
    public static bool RestartLevel(int sceneIndex)
    { 
        // 3 
        Debug.Log("Player deaths: " + PlayerDeaths); 
        string message = UpdateDeathCount(ref PlayerDeaths);
        Debug.Log("Player deaths: " + PlayerDeaths);
        Debug.Log(message);
        SceneManager.LoadScene(sceneIndex);
        Time.timeScale = 1.0f;
        return true;
    }
}

Let’s break down the code:

  1. First, it declares a new static method that returns a string and takes in an int passed by reference.
  2. Then, it updates the reference parameter directly, incrementing its value by 1 and returning a string that contains the new value.
  3. Finally, it debugs the PlayerDeaths variable in RestartLevel(int sceneIndex) before and after it is passed by reference to UpdateDeathCount(). We also store a reference to the returned string value from UpdateDeathCount() in the message variable and print it out.

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:

Background pattern  Description automatically generated

Figure 10.2: Example output from ref parameters

For clarity, we could have updated the player death count without a ref parameter because UpdateDeathCount() and PlayerDeaths are in the same script. However, if this wasn’t the case and you wanted the same functionality, ref parameters are super useful.

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, which means they’re similar tools but they’re not interchangeable—each has its own use cases:

  • 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 have replaced 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. It’s also more flexible than the ref keyword because the initial parameter values don’t need to be set before they’re used in the method. The out keyword is especially useful if you need to initialize the parameter value before you change it. Even though these keywords are a little more esoteric, it’s important to have them in your C# toolkit for special use cases.

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.

Intermediate OOP

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 template and have them perform different actions based on a specific scenario?”

To answer this question, we’ll be learning about interfaces, abstract classes, and class extensions.

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, they contain the implementation blueprint, and it’s up to the adopting class or struct to fill in the values and methods outlined in the interface.

You can use interfaces with both classes and structs, and there’s no upper limit to how many interfaces a single class or struct can adopt.

Remember, a single class can only have one parent class, and structs can’t subclass at all. Breaking out functionality into interfaces lets you build up classes like building blocks, picking and choosing how you want them to behave like food from a menu. This would be a huge efficiency boost to your code base, breaking away from long, messy subclassing hierarchies.

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 have the freedom to be separate and exhibit different behaviors while still sharing common functionality.

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 might need to implement for sharing 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:

  1. First, it declares a public interface called IManager using the interface keyword.
  2. 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.

  1. Finally, it defines a method named Initialize() with no return type for the adopting class to implement. However, you could absolutely have a return type for a method inside an interface; there’s no rule against it.

You’ve now created a blueprint for all manager scripts, meaning that each manager script adopting this interface needs to have a state property and an initialize method. Your next task is to use the IManager interface, which means it needs to be adopted by another class.

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 ... 
    void Start() 
    { 
        ItemText.text += _itemsCollected;
        HealthText.text += _playerHP;
        // 4
        Initialize(); 
    }
    // 5 
    public void Initialize()  
    { 
        _state = "Game Manager initialized..";
        Debug.Log(_state);
    }
}

Let’s break down the code:

  1. First, it declares that GameBehavior adopts the IManager interface using a comma and its name, just like with subclassing.
  2. Then, it adds a private variable that we’ll use to back the public State value we have to implement from IManager.
  3. Next, it adds the public State variable declared in IManager and uses _state as its private backing variable.
  4. After that, it calls the Initialize() method inside the Start() method.
  5. 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:

Background pattern  Description automatically generated

Figure 10.3: Example output from an interface

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. Just for fun, let’s set up a new manager script to test this out:

  1. In the Project, right-click inside the Scripts folder and choose Create | C# Script, then name it DataManager.
  2. Update the new script with the following code and adopt the IManager interface:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    public class DataManager : MonoBehaviour, IManager
    {
        private string _state;
        public string State
        {
            get { return _state; }
            set { _state = value; }
        }
        void Start()
        {
            Initialize();
        }
        public void Initialize()
        {
            _state = "Data Manager initialized..";
            Debug.Log(_state);
        }
    }
    
  3. Drag and drop the new script onto the Game_Manager object in the Hierarchy panel:
Graphical user interface, text, application  Description automatically generated

Figure 10.4: Data Manager script attached to a GameObject

  1. Click Play and scroll to the beginning of the console logs, as these should be the first messages you see:
Background pattern  Description automatically generated

Figure 10.5: Output from Data Manager initialization

While we could have done all of this with subclassing, we’d be limited to one parent class for all our managers. Instead, we have the option of adding new interfaces if we choose. We’ll revisit this new manager script in Chapter 12, Saving, Loading, and Serializing Data. This opens up a whole world of possibilities for building classes, one of which is a new OOP concept called 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. This is one of the key differences from interfaces—in situations where you might need to set initial values, an abstract class would be the way to go.

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’s default implementation.

For example, let’s take the IManager interface functionality we just wrote and see what it would look like as an abstract base class. Don’t change any of the actual code in our project, as we still want to keep things working as they are:

// 1 
public abstract class BaseManager  
{ 
    // 2 
    protected string _state = "Manager is not initialized...";
    public abstract string State { get; set; }
    // 3 
    public abstract void Initialize();
}

Let’s break down the code:

  1. First, it declares a new class named BaseManager using the abstract keyword.
  2. Then, it creates two variables: a protected string named _state that can only be accessed by classes that inherit from BaseManager. We’ve also set an initial value for _state, something we couldn’t do in our interface.

    We also have an abstract string named State with get and set accessors to be implemented by the subclass

  1. Finally, it adds Initialize() as an abstract method, also to be implemented in the subclass.

In doing so, we have created an abstract class that does the same thing as an interface. 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 = "Combat Manager initialized..";
        Debug.Log(_state);
    }
}

If we break down the preceding code, we can see the following:

  1. First, it declares a new class called CombatManager that inherits from the BaseManager abstract class.
  2. Then, it adds the State variable implementation from BaseManager using the override keyword.
  3. 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 building block-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.

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 by 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 mandatory 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.

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:

  1. First, it declares a namespace named CustomExtensions to hold all the extension classes and methods.
  2. Then, it declares a static class named StringExtensions for organizational purposes; each group of class extensions should follow this setup.
  3. 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
  4. 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.

In practice, this will let you add any of your own custom functionality to existing C# classes or even your own custom ones. Now that the extension is part of the String class, let’s test it out. 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 other changes needed ...
    private string _state;
    public string State
    {
        get { return _state; }
        set { _state = value; }
    }
 
    void Start() 
    { 
        // ... No changes needed ... 
    } 
    public void Initialize()  
    { 
        _state = "Game Manager initialized..";
        // 2 
        _state.FancyDebug();
        Debug.Log(_state);
    }
}

Let’s break down the code:

  1. First, it adds the CustomExtensions namespace with a using directive at the top of the file.
  2. 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 FancyDebug() is called on, its length will be printed out properly, as shown here:

Figure 10.6: Example output from custom extension

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 the book. In the next section, you’ll learn about the larger role that namespaces play in C# and how to create your type alias.

Namespace conflicts and type aliasing

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.

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.

For more information on the using keyword and type aliasing, check out the C# documentation at: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive.

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 that what you’ve learned in this chapter is all that there is to know about these concepts. Take it 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

  1. Which keyword would mark a variable as unmodifiable but requires an initial value?
  2. How would you create an overloaded version of a base method?
  3. What is the main difference between classes and interfaces?
  4. How would you solve a namespace conflict in one of your classes?
  5. Don’t forget to check your answers against mine in the Pop Quiz Answers appendix to see how you did!

Join us on discord!

Read this book alongside other users, Unity game development experts and the author himself.

Ask questions, provide solutions to other readers, chat with the author via. Ask Me Anything sessions and much more.

Scan the QR code or visit the link to join the community.

https://packt.link/csharpwithunity

..................Content has been hidden....................

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