Inheritance

Inheritance allows one class to extend and modify the functionality of another. The inheriting class is called the "derived" class and the class it inherits from is called the "base" class. The Spider Bot example uses this concept to define some general behaviour in a base class with more specific behaviour in a simple derived class so that the Directional Blending example can reuse the same general behaviour from the base class in its own more complex derived class.

The declaration of a class specifies what it inherits from using a : after its name like so:

public class Example : MonoBehaviour// Example inherits from MonoBehaviour.
{
}

By inheriting from MonoBehaviour, the Example class gains all of its members which are listed in the Unity Documentation (the "Messages" work differently and are explained in the MonoBehaviours section below).

Polymorphism

Polymorphism means that an instance of a derived class can always be treated as an instance of its base class and the base class can define abstract and virtual members (see Keywords below) for derived classes to override. For example:

public abstract class Item
{
    public readonly string Name;
    public readonly string Description;

    // Constructor.
    public Item(string name, string description)
    {
        Name = name;
        Description = description;
    }

    public virtual void Activate(Character user)
    {
        // Do nothing by default.
    }
}

// Potion inherits from Item, therefore a Potion is an Item.
public class Potion : Item
{
    public readonly int HealingAmount;

    // If the base class does not have a default parameterless Constructor,
    // derived classes must explicitly call one of its constructors.
    public Potion(string name, string description, int healingAmount)
        : base(name, description)// Call the base constructor.
    {
        HealingAmount = healingAmount;
    }

    // The override keyword replaces the base virtual method with this one.
    public override void Activate(Character user)
    {
        // Call the base method.
        // This is optional and in this case will do nothing because it is empty.
        base.Activate(user);

        user.Health += HealingAmount;
    }
}

public class Character
{
    public readonly List<Item> Inventory = new List<Item>();

    public void InitializeStartingItems()
    {
        // Inventory is a List of Items.
        // A Potion is an Item.
        // So we can add a Potion to the Inventory.
        Inventory.Add(new Potion("Healing Potion", "Heals 10 HP", 10));
    }

    public void UseLastItem()
    {
        var index = Inventory.Count - 1;// The index of the last item.
        var item = Inventory[index];// Get the item.
        Inventory.RemoveAt(index);// Remove it from the list.
        item.Activate(this);// Activate it on yourself.

        // Since the item is a Potion, it will call the Activate method in the Potion class.
    }
}

This concept is further demonstrated in the Named Animations example.

Keywords

There are two inheritance keywords that can be used to modify a class:

Class Keyword Effect
sealed Prevents the class from being inherited. Otherwise all classes can be inherited by default. The Coding Standard used in Animancer recommends that all classes be sealed by default to prevent inheritance of classes that were not specifically intended for it.
abstract Prevents any instances of the class from being created. The Item class in the above example is abstract so that you can never create a new Item(...), you have to create a specific type of Item such as a Potion.

There are also four keywords which can be used on methods (and properties):

Method Keyword Effect
virtual Used in the base class to declare a method with a default implementation that can be replaced by using the override keyword in a derived class as shown in the above example.
abstract Used in the base class to declare a method without a body. The Activate method in the above example could be declared as public abstract void Activate(Character user); (without a { code block }) to avoid specifying a default implementation and force every derived class to override it.
override Used in the derived class to replace the contents of a virtual or abstract method from the base class.
sealed Used in the derived class alongside override to prevent further derived classes from overriding that method again.

Common Bases

Most scripts in Unity inherit from either of the following base classes:

MonoBehaviour ScriptableObject
This allows instances of the script to be attached as components to GameObjects in the scene. This allows instances of the script to be saved in your project as asset files.
You can attach them by either using the Add Component button in the Inspector or dragging and dropping the script from the Project window onto an object in the Hierarchy or into the Inspector. They don't automatically have a way to create them, but you can give them a CreateAssetMenu attribute to allow them to be created via the Assets/Create/... menu.
Instead of creating instances in scripts using the new operator like other classes, they must be created by getting a GameObject and calling its AddComponent method. Instead of creating instances in scripts using the new operator like other classes, they must be created using the static CreateInstance method.
When you leave Play Mode, Unity undoes all changes to scene objects, reverting the scene back to the state it was in before you entered Play Mode. Since they are assets rather than objects in the scene, any changes you make to them while in Play Mode will remain when you return to Edit Mode and you can reference the same asset from any number of objects across multiple scenes.

Animancer uses them for several purposes:

Animancer uses them for several purposes:

Both of those base types have certain Messages that Unity will send them when specific events occur.

Also note that when you inherit from either of these classes, the name of the script file must exactly match the name of your class, otherwise Unity will not allow you to create instances of them.

Interfaces

An Interface is like an abstract base class which only contains abstract Methods and Properties except that where a derived class can only inherit from one base class, it can implement any number of interfaces. Structs can also implement interfaces even though they cannot inherit from each other. Most naming conventions put an I prefix in front of all interface names.

They are very useful for decoupling generalised systems from specific implementations. For example, the Animancer Events system requires a script to check the animation time every frame to determine when to trigger any events that have been registered, but there is no reason for the main update method (AnimancerPlayable.PrepareFrame) to actually know anything about events. Instead, it has a List<IUpdatable> so that it can simply call methods on that interface without knowing what it is actually doing and the event system can implement the interface without being directly connected to the main update loop. This means that if you want to add your own logic that needs to run every update, you can also implement IUpdatable and register for updates without needing to make any modifications to Animancer itself.

The Doors example also uses an interface to implement an interaction system. The system simply checks if a target object implements the appripriate interface without knowing anything about what the interaction will actually do and the interactable object (the Door) does not know how it was triggered, so either part could be replaced or modified without affecting the rest.

Composition

Composition Over Inheritance is the principle that it is easier to create reusable code by giving classes fields that reference other classes instead of inheriting from them. It is especially important in C# where each class can only inherit from one base class. Unity has an inbuilt Component system where each GameObject has a list of Components attached to it instead of only being able to inherit from one base class (MonoBehaviour inherits from Behaviour which inherits from Component). This allows much greater flexibility, for example: rather than having Character inherit from Movable, Renderable, Destructible, etc. to share their functionality in a fixed hierarchy, having those each as separate Components would allow you to easily implement a character that cannot move (such as a plant monster) or an invulnerable character (such as a friendly NPC) by simply not giving it the appropriate component.

One downside of using separate Components is that it becomes slightly harder to communicate between them. For example, there are several classes which inherit from AnimancerComponent in order to override parts of its behaviour (see Component Types for details), but there is usually no need for such a close connection when all you really want to do is use an AnimancerComponent in your scripts. There are two main ways of using another Component in your script:

Serialized Field Get Component

When you declare a Serialized Field it will show up in the Inspector so you can assign a reference to it.

public class Example : MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;
    
    private void Awake()
    {
        _Animancer.Play(...);
    }
}

Note that if you do not assign a reference in the Inspector, the field will be null so the Awake method will throw a NullReferenceException when it tries to use it (unless you check if (_Animancer != null)).

The OnValidate Message can be used to call AnimancerUtilities.GetComponentInParentOrChildren to search for a reference for any components that haven't already been assigned. This allows the script to automatically pick a default reference while still allowing it to be assigned manually.

GetComponent returns a Component of the specified type attached to the same GameObject:

public class Example : MonoBehaviour
{
    private void Awake()
    {
        var animancer = GetComponent<AnimancerComponent>();
        animancer.Play(...);
    }
}
  • It includes derived types, so GetComponent<MonoBehaviour>() will return anything that inherits from MonoBehaviour.
  • If there is more than one, it returns the top one.
  • If there are none, it returns null. In this case, that means the Awake method will throw a NullReferenceException when it tries to use it (unless you check if (animancer != null)).

Calling GetComponent every time you need it is inefficient so if you want to use the AnimancerComponent in other methods you should get it once in Awake and store it in a Field (this is often referred to as "caching" the reference).

The Animancer Examples use Serialized Fields because it makes them more flexible and reliable. When you add an example script to an object, you can see what references it needs in the Inspector and you can assign appropriate Components from any GameObject instead of needing to add the example script to the same GameObject as the other things it needs and not even seeing any indication of that requirement without reading the script.