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 Speed and Time 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. My Coding Standard 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 GameObject s 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 Component
s 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 Component
s 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 Component
s 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.
Note that if you do not assign a reference in the Inspector, the field will be The |
Calling |
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 Component
s 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. This topic is covered more thoroughly in the Component Referencing section.