Methods

Methods are where you define the instructions you want your script to execute.

Executing a method is referred to as "calling" it.

The description in the above link is slightly misleading for development in Unity because while the Unity Engine itself does presumably use a Main method internally as the main entry point for the application as described, your scripts will instead use various MonoBehaviour Messages which are methods you can define that Unity will execute in response to certain events.

Also note that the first two sentences on the MonoBehaviour page as of this writing are false ("MonoBehaviour is the base class from which every Unity script derives. When you use C#, you must explicitly derive from MonoBehaviour."). Scripts that you want to attach to a GameObject in the scene do need to Inherit from MonoBehaviour, but there are lots of situations where you do not need to do so. Animancer for example has around 80 scripts (excluding the examples) but only 8 of them inherit from MonoBehaviour.

Methods are executed sequentially from the top down one step at a time. Calling another method will execute it entirely before continuing to execute the calling method. For example:

using UnityEngine;

public class Example : MonoBehaviour
{
    // A Field.
    private int _Counter;

    // _Counter starts at 0.

    // MonoBehaviour Messages do not need to be public for Unity to call them.
    // void is the Return Type.
    private void Awake()
    {
        // The ++ Operator increases the value by 1.
        _Counter++;
        // _Counter is now 1.

        // Call the AddSix method (see below).
        AddSix();
        // _Counter is now 7.

        _Counter++;
        // _Counter is now 8.
    }

    private void AddSix()
    {
        // The += Operator is the same as writing "x = x + y".
        _Counter += 6;
    }
}

Parameters

Parameters allow values to be passed into a method by the caller. For example:

using UnityEngine;

public class Example : MonoBehaviour
{
    private int _Counter;

    // _Counter starts at 0.

    private void Awake()
    {
        // Call the AddValue method using 2 as the "value" parameter.
        AddValue(2);
        // _Counter is now 2.
    }

    private void AddValue(int value)
    {
        _Counter += value;
    }
}

Overloads

In general, everything in C# needs a unique name (types, fields, etc.). However, methods can be Overloaded by declaring multiple methods with the same name and different parameters. Take Unity's Mathf.Clamp method for example:

public struct Mathf// No idea why Mathf is not a static class since all its members are static.
{
    public static float Clamp(float value, float min, float max)
    {
        if (value < min)
            return min;

        if (value > max)
            return max;

        return value;
    }

    public static int Clamp(int value, int min, int max)
    {
        if (value < min)
            return min;

        if (value > max)
            return max;

        return value;
    }
}

Both of the above methods do the same thing so they can be reasonably given the same name and when you call it the parameters you use will determine which overload actually gets called. Another example can be seen in Animancer itself where the AnimancerComponent class has multiple Play Methods to give different options for how you want to determine which animation to play and whether you want it to fade in over time or not.

Return Values

Similar to how Parameters allow the caller to pass values into a method, Return Values allow the method to return a value back to the caller. For example:

using UnityEngine;

public class Example : MonoBehaviour
{
    // void means this method does not return anything.
    private void Awake()
    {
        var counter = 4;
        counter += DoubleValue(6);// DoubleValue returns 12.
        // counter is now 16.
    }

    // int means this method returns an int.
    private int DoubleValue(int value)
    {
        return value * 2;
    }
}

Flow Control

Instead of executing every instruction once each in the same order every time the program is run, you will often need to control the flow of the program to only do things under certain conditions or repeat the some instructions until a condition is met. The most common flow control Statements are:

Keyword Effect
if else Execute some code depending on whether a condition is true or false. if statements used to assign a value can also be written using the Conditional Operator.
switch A more complex form of if that allows you to choose between multiple cases (more than just true or false).
while Like if, but when it reaches the end of the code block it loops back to check the condition again. So if the condition is never false, it will get stuck in an "infinite loop" as it keeps executing the same code repeatedly without continuing on to run the rest of the program (meaning it looks like the program is frozen because it does not even get to render new frames to the screen).
for A more complex form of while which allows you to initialise variables before it starts, check a condition, and specify what happens at the end of the loop, all in one statement.
foreach Similar to for, but specifically used to iterate through every element in a collection (such as an Array).
return Immediately ends the current method. If the method has a non-void Return Type, it must return a value of that type.

In Visual Studio, if you type any of the above keywords (except return) then press Tab twice it will insert a default Snippet for you. If the snippet contains multiple things for you to fill out such as with a for loop, you can press Tab to cycle through them and then Enter when you are done. This is especially useful for switch statements because if you enter an enum as the target, it will automatically insert a case for every value in that enum.

Properties

Properties are special methods which appear to be used more like Variables instead of being called like regular methods even though they are actually methods internally. They are defined using the get and/or set keywords like so:

Property Equivalent Methods
private int _Number;

public int Number
{
    get
    {
        return _Number;
    }
    set
    {
        _Number = value;
    }
}

public void Example()
{
    Number = 6;
    // The _Number field is now 6.
}
private int _Number;

public int get_Number()
{
    return _Number;
}

public void set_Number(int value)
{
    _Number = value;
}

public void Example()
{
    set_Number(6);
    // The _Number field is now 6.
}

Properties are very useful for validating input values or reacting to changes, for example:

public class Health : MonoBehaviour
{
    private int _CurrentHealth;

    public int CurrentHealth
    {
        get => _CurrentHealth;
        set
        {
            // Treat any value less than zero as zero.
            if (value < 0)
                value = 0;
        
            _CurrentHealth = value;

            // Here we could update the health bar in the GUI or something.
		    
            // If the value is zero or less, destroy the GameObject this script is attached to.
            if (value <= 0)
                Destroy(gameObject);
        }
    }
}

Auto-Properties

You can define properties without a body for the getter or setter to have the compiler automatically create a backing firld for it, which is known as an Auto-Property:

Auto-Property Equivalent Regular Property
public int Number { get; set; }
private int _Number;

public int Number
{
    get
    {
        return _Number;
    }
    set
    {
        _Number = value;
    }
}

In the above example, the property does not really accomplish anything and could simply be a public field. However, it is possible to give the getter and setter different Access Modifiers (for regular properties as well) like so:

// Anything can get the value of this property.
// But it can only be set from within the declaring type.
// This is known as a read-only auto-property.
public int Number { get; private set; }

The Static section contains an example that makes use of read-only auto-properties.

Constructors

Constructors are a special type of method which can be used to define how instances of a particular class or struct can be created and initialise their default values. A constructor has no return type and its name matches the declaring type like so:

public class Person
{
   public readonly string FirstName;
   public readonly string LastName;
   
   public Person(string firstName, string lastName)
   {
      FirstName = firstName;
      LastName = lastName;
   }
}

// In some other method:
var person = new Person();// Error: Person does not have a parameterless constructor.
var john = new Person("John", "Smith");

If you do not define any constructors in a type, the compiler will automatically give it a default parameterless constructor.

Constructors cannot be used in classes that Inherit from MonoBehaviour because they need to be added to a specific GameObject using its AddComponent method. Technically, you can use a parameterless constructor but it will not be able to do much because it will be executed before Unity gives the component its reference to the GameObject or any of its Serialized Field data, so you will generally want to use the Awake message instead.

Readonly Fields

Fields with the readonly keyword can only be assigned in a Constructor or Field Initialiser:

// Using the above Person class.
var john = new Person("John", "Smith");
if (john.FirstName == "John")// We can get the value because it is a public field.
{
    john.FirstName = "Jim";// But this gives a compiler error because it is a readonly field.
}

// But we could always make a new Person with the name we want:
var jim = new Person("Jim", john.LastName);

Delegates

A Delegate is a variable which references a method. The most common type of delegate is System.Action which represents a method with no parameters and void as its return type. There are also generic System.Action<T> and System.Func<TResult> delegates which can take parameters and return values. Though if you need parameters you will often want to define your own delegate type so that you can properly name the parameters, for example:

public delegate int PerformCalculation(int x, int y);

Delegates can be used as a type of Variable just like any other, except that you assign methods to them and can call them like methods (calling a delegate is referred to as "invoking" it):

using System;// For System.Action.
using UnityEngine;// For UnityEngine.MonoBehaviour.

public class Health : MonoBehaviour
{
    public event Action onDeath;

    private int _CurrentHealth;

    public int CurrentHealth
    {
        get => _CurrentHealth;
        set
        {
            if (value < 0)
                value = 0;
        
            _CurrentHealth = value;

            if (value <= 0)
            {
                // If an onDeath delegate has been registered, invoke it.
                if (onDeath != null)
                    onDeath();
                else// Otherwise just destroy the GameObject this script is attached to.
                    Destroy(gameObject);
            }
        }
    }
}

public class Creature : MonoBehaviour
{
    [SerializeField]
    private Health _Health;

    private void Awake()
    {
        // Register our Die method for the Health component to call.
        _Health.onDeath += Die;
    }

    private void Die()
    {
        // Play a death animation.
        // Turn the model into a ragdoll.
        // Create an explosion.
        // etc.
    }
}
  • The Animancer Events system uses delegates to allow users to specify their own methods that they want Animancer to call at specific times throughout an animation.
  • Delegates are Reference Types, meaning that they need to be Garbage Collected.
  • Multiple methods can be combined into a single delegate using the + and += operators (then removed with - and -=), which creates a Multicase Delegate.
  • The event keyword is a special Access Modifier which can be used on delegate fields to allow other classes to add and remove methods from it without allowing those classes to invoke it.

Inline Delegates

If you do not already have a method you want a delegate to point to, you can use an Anonymous Method or Lambda Expression to define the target method in the same place where you assign it:

// Using the Health class from the above example.

public class Creature : MonoBehaviour
{
    [SerializeField]
    private Health _Health;

    private void Awake()
    {
        // Lambda expression:
        _Health.onDeath += () =>
        {
            Debug.Log("Creature Died");
        };
	    
        // One-line Lambda expression:
        _Health.onDeath += () => Debug.Log("Creature Died");
	    
        // Anonymous method:
        _Health.onDeath += delegate()
        {
            Debug.Log("Creature Died");
        };
    }
}

Unity Events

Unity's Serialization System cannot serialize regular delegates or show them in the Inspector, which is where UnityEvents come in. They have several limitations on the types of methods they can call:

  • Access Modifier must be public.
  • Return Type must be void.
  • Must have either no Parameters or one parameter of any of the following types: bool, int, float, string, or anything derived from UnityEngine.Object (including any MonoBehaviour or ScriptableObject).

Any unsupported methods will simply not be listed in the menu.

One common use for UnityEvents is in Buttons and other UI components so that they can easily be set up to call methods in your scripts.

If you want your own scripts to have events which you can set up in the Inspector like that, you can use a UnityEvent like any other Serialized Field. However, you may be interested in the free UltEvents plugin (also made by Kybernetik, like Animancer) which serves the same purpose but does not have the same restrictions (see UltEvents vs. UnityEvents for a comparison). Unfortunately, it is not possible to simply change Unity's UI Buttons to use UltEvents instead of UnityEvents, however it is possible to redirect events to an UltEventHolder.

Note that UnityEvents (and UltEvents) are less efficient than regular Delegates so if you want to use events in your own scripts which you do not need to set up in the Inspector, then you should just use regular Delegates.

Extension Methods

Extension Methods allow you to "add" methods to an existing type without modifying or inheriting from it. They are declared as static methods with the this keyword before their first parameter and can be called like instance (non-static) methods. For example:

// Extension methods must be in a static class.
public static class ExtensionMethods
{
    public static Color WithAlpha(this Color color, float alpha)
    {
        return new Color(color.r, color.g, color.b, alpha);
    }
}

// In some other method:

var color = Color.red.WithAlpha(0.5f);
// Color.red is  (r=1, g=0, b=0, a=1)
// color will be (r=1, g=0, b=0, a=0.5f)

// You can also still use it as a regular static method:
var color = ExtensionMethods.WithAlpha(Color.red, color);

Expression Bodied Members

Expression Bodied Members are a shorter syntax for writing a method if it only contains a single expression. The syntax uses the => operator instead of curly braces ({ }) much like a Lambda Expression used in an Inline Delegate. If the method returns a value, the return keyword is not necessary. For example, the following regular method:

public string GetFullName()
{
    return firstName + " " + lastName;
}

Could be written as a single line:

public string GetFullName() { return firstName + " " + lastName; }

Which can be shortened a bit using an expression body:

```cs
public string GetFullName() => firstName + " " + lastName;

Read-Only Properties can also have expression bodies, which is particularly useful for providing a public accessor for a non-public Serialized Field, for example:

// Private serialized field.
[SerializeField]
private AnimancerComponent _Animancer;

// Public accessor so that other types can get the value but not set it.
public AnimancerComponent Animancer => _Animancer;