April 23, 2021
5 minutes

Learn C# for Unity — Lesson #7: Integrate Your Code into Unity

by

Arthur Ribeiro / Dejan Gajsek

You've come far young padawan, and it's time for some hands-on programming.

Muffin Clicker

Inspired by the classic Cookie Clicker, in our project, we'll be collecting muffins instead (not very original, I know. Bare with me.)

Check the playable game, download the source ( GitHub) and let's get crackin'!

By the way, we build this game during our C# Scripting Fundamentals course which you can take it as itself or as a complementary asset if you enroll in the XR Development with Unity course.

Mechanics

  1. Every time the player clicks the big muffin on the center, they are awarded muffins.
  2. There are 2 Power-ups:
    • Sugar Rush (Milkshake): Multiplies every muffin award during a defined period
    • Fancy Muffin: Awards a large number of muffins
  3. Power-ups have a cooldown effect after each activation
  4. There are 3 upgrades:
    • Muffin: Increase the award for each click
    • Sugar Rush: Increase the multiplier and the duration, and decrease the cooldown
    • Fancy Muffin: Increase the reward per activation and decrease the cooldown
  5. Upgrades cost muffins based on the current upgrade level

Project Architecture

The idea of this post is to give you an overview of a fully built project, so you can have a clearer understanding of how it all comes together. The first good step is to understand what classes exist in the project, how they relate to each other, and what is their purpose.

Most times you will have to do this by looking through the code itself and building this mental map by yourself. Sometimes though, there is some documentation 🙂

Unified Modeling Language (UML)

UML stands for Unified Modeling Language and is intended to provide a standard way to visualize the design of a system. What I am using here is something loosely inspired by UML, and that is why I added some subtitles to describe what each node means ;).

The arrows are following the UML standard:

  • Arrows for inheritance
  • Diamond for aggregation - "Belongs to"
  • A Simple line for the association - "Communicates with each other"

c# muffin clicker architecture

The main takeaways here are:

  • Make sure to understand the responsibilities of each class; For me, this is the most important information here.
  • The GameManager is a key component in this design, understanding it will be paramount before understanding the whole.
  • MonoBehaviours are used a lot!
  • This image describes only the architecture of the code; Scenes, prefabs, and other Unity assets are not represented.

Unity Specific Concepts

MonoBehaviours

MonoBehaviours are the bread and butter of Unity programming, as they are necessary to create custom components that are added to game objects. You use it by making a class inherit from MonoBehaviour, and voilà you created a new component.

This style of programming is called Components-based development. For now, this shouldn't matter as much, but in time as you become more familiar with programming and designing systems, you will see that there are plenty of ways to tackle a problem, and how these different approaches affect your work.

If you create a new script through Unity, it will automatically create a class with the same name as the file name you gave it, and make it a MonoBehaviour. It is very important to keep the class name identical to the filename, otherwise, your component won't be visible in unity, even though it compiles!

Apart from becoming a usable component, MonoBehaviours gives a bunch of functionality to your class:

  • Easy access to the Transform component and associated GameObject
  • Some functions to interact with other components and gameObjects
  • Something unity called "Messages", which can be interpreted as events, some examples:
    • Start
    • OnCollisionEnter
    • OnDestroy
    • FixedUpdate
    • OnApplicationFocus

Order of Execution

Some of these Unity Messages are common to use. Some of them are automatically added to your class when you create them through Unity. It is really important to know them, when they are executed, and what's their purpose.

Awake

Awake function is called when the script instance is being loaded, that is, when a Scene loads, or when an inactive GameObject is set to active, or after a GameObject created with Object. Instantiate is initialized.

Use it to initialize variables or states before the application starts.

Start

The Start is called on the frame when a script is enabled just before any of the Update methods are called the first time.

Like the Awake function, Start is called exactly once in the lifetime of the script. However, Awake is called when the script object is initialized, regardless of whether or not the script is enabled.

Use it to initialize variables or states before the application starts.

The Awake function is called on all objects in the Scene before any object's Start function is called. This fact is useful in cases where object A's initialization code needs to rely on object B's already being initialized; B's initialization should be done in Awake, while A's should be done in Start.

Update

The Update is called every frame if the MonoBehaviour is enabled.

Use it to implement your game logic, like checking if the cooldown time of a power-up was reached. The Update is the most commonly used function to implement any kind of game script.

Fixed Update

Fixed Update is a frame-rate independent method commonly used for physics calculations. It has the same frequency as the physics system, which can be found at Edit > Settings > Time > Fixed Timestep.

Use FixedUpdate when dealing with Rigidbodies.

C# Focused Content

Variables and Data Types

There are examples of data types all across the classes, which is expected. Let's take a look at the Muffin class so you can get an idea of how many variables a class can end up with!

In this example, we can see how variables, properties, and methods use data types. While some of them should be familiar like int and float, there are many new ones here! Most of them are custom Unity types, the main ones to learn from here are:

  • double - Also a C# builtin DataType similar to float, but with more precision and better for large numbers. It is used here because the number of muffins in this kind of game can scale!
  • Vector2 / Vector3 - Representation of 2D / 3D vectors and points.
  • Coroutine - This Will be covered later in this post 🙂
  • RectTransform - Similar to Transforms, but used only when positioning stuff in the UI.

Enums

A new foe has appeared! Enums!!

Enum is a set of named constants.

The main purpose of enums is readability, as it is way better to understand "WeekDay.Monday" than "WeekDays[0]" or something like that, especially when each culture starts counting from a different day. Unity has a custom inspector field for enums.

The UpgradableType is an example of enums in the project.

Programming Logic and Conditionals

I selected a couple of functions with some good examples of Conditionals.

GameManager class

This function shows 2 different boolean expressions and a new way to use if statements. If you look closely, the first if statement doesn't use the curly brackets "{}" to define its body. When this happens the next statement will be considered the if statements body, no matter the indentation! (C# always ignores whitespaces)

It is your first time seeing an Exception as well. Throwing an exception is something that can be done when there's a possibility the code execution enters an unwanted, unsafe path and you want to stop the thread from executing further to prevent further damage. Ultimately it will also log the error to the Unity Console.

NumberPrettifyLib class

This example is a straightforward use of the switch statement but showcases the many syntaxes possible for it.

Functions

Again, examples of functions can be found all over the project, but here is some interesting one:

In this example, we have the SaveData constructor method with overloads. Just as a reminder, this is the anatomy of a method:

But you might be wondering, what is that ": this()" doing there?!

A constructor can invoke another constructor in the same object by using the "this" keyword. It can be used with or without parameters, and any parameters in the constructor are available as parameters to "this", or as part of an expression.

Similarly, you can invoke the constructor of a BaseClass by using the "base" keyword, just like the "this" was used here.

Loops

This is an example snippet from the Muffin class:

Here we see the aforementioned List class, which works like an array, but as you can see, it has an Add and Remove method, these are some of the extra functionality a List provides.

Another new thing here is the "var" keyword. That can be used when the dataType of the variable can be inferred from the context. In this instance, if we are iterating over a List of MuffinClickRewardAnimationInfo, that variable can only be of the type MuffinClickRewardAnimationInfo.

Some more example of loop usages:

  • Muffin.AnimateSpinlights > for loop
  • Muffin.AnimateClickRewards > foreach loop
  • Muffin.MuffinButtonAnimation > While loop with Coroutines
  • GameManager.SaveCoroutine > Do.. While loop with Coroutines

Classes and Inheritance

As discussed earlier, every component script used by Unity must inherit from MonoBehaviours, and what this entails.

But, as seen before on the UML chart, the Powerup class is another example of inheritance.

These classes pack a lot of inheritance material, it covers all the inheritance concepts taught in the previous chapter:

  • Abstract methods and class
  • Virtual
  • Protected, Public and Private accessibility
  • Overrides of methods and properties
  • Base class calls

Some key takeaways here:

  • In this example, the Powerup class also inherits from MonoBehavior, but this is not obligatory
  • All abstract fields were overridden
  • Update function showcases a way to add logic with an override, but keep using the base class implementation

Serialization

Serialization is the process of transforming data structures or object states into a format that can be stored and reconstructed later. The reverse process is called deserialization.

In this project, the class SaveData is serialized before being saved in the PlayerPrefs.
To accomplish this, it uses the JsonUtility class to serialize and deserialize its fields to a format called JSON.

JsonUtility can only work on serializable classes, so the SaveData class must be tagged with the class attribute Serializable.

  • Not all fields can be serialized, like Dictionaries. That is why the function Serialize and Deserialize methods specifically deal with the upgradableSaveData class variable.
  • Only public fields are serialized. Private fields must be tagged with a "SerializeField" to be included in the serialization.

Singleton Design Pattern

The class GameManager is an example of a Singleton. Since there should always be only one instance of a GameManager, and it is accessed all over the application, as we can see through the UML chart, it makes sense to turn it into a Singleton.

Creating a Singleton is simple, this is a snippet extracted from the GameManager class, that implements the Singleton pattern:

To use the singleton just go "GameManager.Instance", this can be found in the FancyMuffinPowerup, HeaderUI, MilkshakePowerup, Muffin, Powerup, and UpgradeButton classes, like I said, it is used all over 🙂

It is important to know that different implementations also guarantee a single instance and global access.

Unity Specific Concepts | part 2

Coroutines

When you call a function, it runs to completion before returning. This effectively means that any action taking place in a function must happen within a single frame update; a function call can’t be used to contain a procedural animation or a sequence of events over time, for example.

A coroutine is like a function that can pause execution and return control to Unity but then continue where it left off.

Usage

Make sure that your method returns IEnumerator, and has at least one yield statement.
There are different ways to call the yield statement, depending on if you want the method to resume the next frame, after X time, or after another coroutine finishes.

PlayerPrefs

PlayerPrefs are the way Unity handles persistence in the applications, providing simple functions like:

C# circuit stream

It is common to use third-party persistence solutions, like more established databases such as SQL.

Some example usages can be found at the functions:

  • GameManager.LoadSaveData
  • GameManager.Save

Static Classes

Unity provides some static classes that fall into those good use cases mentioned earlier:

Mathf

Mathf is a library of mathematical functions covering a wide range of expressions:

These functions use only the arguments provided through the method and don't rely on any previous state for its logic, making them pure functions, good use of static as discussed in lesson 5.

Some example usages can be found at the functions:

  • FancyMuffinPowerup.AnimatePowerupActivation
  • NumberPrettifyLib.PrettifyNumber
  • Muffin.AnimateClickReward

Time

The class Time is another good example of static classes, it provides a bunch of properties to get time information from Unity:

Some example usages can be found at the functions:
Muffin.Update
LittleMuffin.UpdateTransform

UI

This example game is made entirely on top of the Unity UI System. Unfortunately, since these blog posts are focused on C#, I won't cover this. My advice is to take a look at how the MuffinClickerScene is set up, play around with the UI in a different test scene and look up some tutorials on the net.

The main concepts to master are:

  • Anchors (really important!)
  • Visual components: Image, Text Mesh Pro
  • Canvas and Canvas Scaler
  • Auto Layout: Vertical / Horizontal layout, Layout Element

General Tips

Integrated Development Environment - IDE

Make sure that you are using a good IDE (Integrated development environment) software with all its capabilities working, the most important ones being Intellisense and Debug features (Breakpoints, flow control, ...).

Visual Studio 2019 is already a great free tool, and I also recommend Rider.

GIT

Git is a version control system, meaning it will keep the history of your changes and allow you to better work collaboratively. This allows you to work without being concerned about breaking your project as you will always have the git commits to revert to.

Git is extensively used across all different development environments, and Unity is not an exception. Learning git is good whether you intend to keep working with unity or not.

Start Small

This project is an example of the many features C# has to offer, and how to use them with Unity, but it was done incrementally, and this can be seen through the git commits of the project.

The milestones are there because they are a base for our C# Scripting Fundamentals in Unity, but they show clearly how each feature was tackled step by step.

Go slowly, take this project and add some new features, maybe a new powerup. Or start a new one, but isolate only the core mechanic you want to make, and focus only on that.

Download Syllabus

Download XR Development with Unity Course Syllabus

Share this