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(Creature 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(Creature 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 Creature
{
    public readonly List<Item> Inventory = new List<Item>();

    public void InitialiseStartingItems()
    {
        // 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(Creature 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.

MonoBehaviours

Most scripts (but not all of them as the following link would have you believe) inherit from MonoBehaviour so that they can be attached to GameObjects in the scene. This lets them inherit all the regular members such as the enabled property and GetComponent methods, as well as allowing them to receive special "Messages" which do not utilise Polymorphism. Note that when you inherit from MonoBehaviour, the name of the script file must exactly match the name of your class, otherwise Unity will not allow you to attach that script to any objects.

Receiving a message does not use the override keyword, you simply declare a regular method with the appropriate name and parameters (usually none, but check the documentation for the specific message you are receiving) like so:

public class Example : MonoBehaviour
{
    private void Awake()
    {
    }
}
Message Description
Awake Called when the script instance is first initialised.
OnEnable Called when the script is enabled and the GameObject is active.
OnDisable Called when the script is disabled or the GameObject is inactive.
Update Called every frame while the MonoBehaviour is enabled.
FixedUpdate Called at a fixed frame rate, independant of rendering.

Your message receiver methods can use any Access Modifiers and Unity will still be able to call them:

  • Since you will not generally want to call them manually, they should not generally be public.
  • If both the base class and derived class implement the same message, Unity will only call the method in the derived class.
    • If the base class is not sealed, all message receivers should be protected so that adding a receiver in a derived class will display a warning that a method with that name already exists. If you see that warning, you can simply go to the base class and make its method virtual then have the derived class override it like a normal inherited method (and call base.MethodName(); if you want).
    • If the base class is sealed, it cannot contain any protected members (except those it inherits). So in that case all message receivers should be private and whenever you remove the sealed keyword from a class you should check through it to change them to protected.

MonoBehaviour classes cannot be created with the new operator like other classes. Instead, they must be added to a specific GameObject using its AddComponent method.

Scriptable Objects

Another common class to inherit from is ScriptableObject, which allows instances to be saved in your project as asset files. Since they are assets rather than objects in the scene, any changes you make to them while in Play Mode will not be undone when you return to Edit Mode and you can reference the same asset from any number of objects across multiple scenes.

ScriptableObject classes are often given a CreateAssetMenu attribute to allow them to be created via the Assets/Create/... menu.

Animancer uses ScriptableObjects for several purposes:

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 Creature 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)).

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.