Whether you remember or not, you’ve been creating classes all along in the previous lessons. I have commented briefly on various features of classes like inheritance and constructors, but there’s a lot more to cover. In this lesson, we will focus on those topics in greater depth and introduce a few more advanced topics like polymorphism, static constructors, abstract classes and static classes.
Inheritance
Inheritance is the concept that one class takes all of the functionality of a “parent” or “base” class while providing the opportunity to extend or modify the logic previously available. The template code provided by Unity for all of your scripts will cause your script to inherit from “MonoBehaviour” by default.
You can tell what class your script inherits from by looking after the colon in the class declaration:
public class Demo : MonoBehaviour { // I inherit from MonoBehaviour }
You don’t have to inherit from MonoBehaviour. You can specify any other class instead such as “Foo” (note that I would have to have created a class called Foo for this to compile):
public class Demo : Foo { // I inherit from Foo }
If you don’t specify a class to inherit from, you are still inheriting from something – System.Object:
public class Demo { // I inherit from System.Object even though I don’t mention it }
In C#, a class can only have one base class. If you want something like multiple-inheritance, consider using interfaces or component based architectures instead.
Polymorphism
MonoBehaviour inherits from Behaviour which in turn inherits from Component. Component inherits from UnityEngine.Object which ultimately inherits from System.Object. I can say that a MonoBehaviour IS any of those base classes (but I can’t always say the reverse). Consider the following example:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class Demo : MonoBehaviour { void Start () { List<System.Object> myList = new List<System.Object>(); myList.Add("Hello World"); myList.Add(42); myList.Add(Camera.main); for (int i = 0; i < myList.Count; ++i) Debug.Log( i + ": " + myList[i].ToString() ); } }
In this snippet, I declare a generic list of objects. A generic list is restricted to holding a single data type. However, I add three very different things: a string of text, an integer value, and a Camera component – all without problem. Each of the “concrete” types still share a “base” of “Object” and are therefore valid additions to the list.
Because of polymorphism, I can treat all three of these very different items in exactly the same way! To demonstrate, I loop through all of the elements of the list and call a method they all share “ToString” and print the result to the console. The output is as follows:
0: Hello World
1: 42
2: Main Camera (UnityEngine.Camera)
Each item knew how to convert itself to a string even though they can all handle the process differently. For example, the Camera component printed an informative message which included the name of the GameObject it was attached to. The “client” code (our Demo script which printed the messages to the console) doesn’t need to know that the objects are different, or how they will respond to the method call. It only needs to know that they have the method.
It is always safe to treat an object like a base class version of itself. However, it is not always safe to treat a reference of a base class as a class which inherits from it. Consider the following line of code:
Camera c = myList[2];
Even though I know that the object at index 2 of myList IS a camera, I can not add this line to the end of the method. If you try it you will get an error, “error CS0266: Cannot implicitly convert type `object’ to `UnityEngine.Camera’. An explicit conversion exists (are you missing a cast?)”
The reason this error occurs is because even though all Camera components are Objects, not all Objects are Cameras. This is evident even in the demo, because the other two “Objects” in the list are not Cameras.
In order to work with the object in its true form, you must now “cast” the object back. You can also check the “type” of an object first to make sure the cast will work:
// Check the type of an object reference with the “is” keyword if (myList[2] is Camera) { // Cast the reference by using the name of the class in parenthesis Camera c = (Camera)myList[2]; }
Alternatively, you can use a keyword “as” to attempt to treat one object as a specified type:
Camera c = myList[1] as Camera; Debug.Log("success: " + (c != null));
Note that in the previous example, I referenced the object at index 1 (an integer) which can not be cast to a Camera. When using the keyword “as”, our local variable will be assigned a “null” reference in a case where the cast fails.
How To: Modify base class functionality
We saw that the Camera’s ToString implementation is different from an integer’s ToString implementation. Let’s suppose we wanted our Demo script to create a different implementation as well. Put the cursor outside of your other methods and begin typing “override ” the auto complete features of your script editor should hopefully kick in and begin suggesting methods which you can modify. MonoDevelop shows me: Equals, GetHashCode, and ToString as valid options to override. Upon selecting ToString, my editor created the following snippet:
public override string ToString () { return string.Format ("[Demo]"); }
The signature of this method should look familiar to you with the exception of the keyword “override” which is what informs us that we are able to either replace, modify, or extend functionality from the base class.
With the implementation as is, calling ToString on instances of our class will inform users that they are referencing an instance of the Demo class. If we had extra information which would be convenient to know we could add it to the string. For example, imagine that we had two fields called ‘x’ and ‘y’ which hold coordinate information. I could then use the following line which would tell us that we had a Demo instance as well as what values it currently held:
return string.Format ("[Demo x:{0} y:{1}]", x, y);
If you wish to extend, rather than replace base class functionality, you can reach it through the keyword “base”. The next snippet mimics the same functionality that the Camera component did where it mentions the name of the GameObject it is on, because that is the behavior specified by one of the base classes:
return string.Format ("[{0} x:{1} y:{2}]", base.ToString(), x, y);
How To: Prepare a class for inheritance
Imagine that you want to have a bunch of game objects which are “Selectable”, but when you select them different things happen. For example, selecting a chair might cause the chair’s material to flash indicating that you tapped it. Selecting a character might cause a special animation and sound effect to play. Most of the selection logic itself will be the same, but the specifics of what happens after being selected are different. In this case you will want to reuse as much of the code as possible and then extend it into the variation.
using UnityEngine; using System.Collections; public abstract class Selectable : MonoBehaviour { public bool isSelected { get; protected set; } public virtual void SetSelected (bool value) { if (isSelected == value) return; isSelected = value; if (isSelected) Select(); else Deselect(); } protected abstract void Select (); protected abstract void Deselect (); }
This sample presents several new keywords. In the class definition I included the keyword “abstract”. An abstract base class is a class which can not be instantiated – if you try, you will get an error, ”error CS0144: Cannot create an instance of the abstract class or interface `Selectable’”
The same is true for trying to attach an abstract component to an object in Unity. You will see an error dialog if you try, “Can’t add script behaviour Selectable. The script class can’t be abstract!”
The reason you would want to mark a class abstract, is because when you use this keyword, you are allowed to declare methods without implementing them. You can leave specific implementation details up to sub-classes.
Regarding publicity, I have already spoken about “public” and “private”, but the property in this class uses a new keyword – a “protected” setter. This keyword is similar to “private” except that subclasses are also allowed access. So basically, the “isSelected” property can be read by other classes, but can only be written to by itself or subclasses of itself.
We have one public method, called “SetSelected” which accepts a bool parameter indicating whether or not the object will or will not be selected. I marked this method as “virtual” which indicates that subclasses are allowed to “override” it if they want to modify, replace, or extend the functionality – but they don’t have to. I provided a default implementation which works. The method itself checks to see if we are trying to set the object in a selection state that it is already in, and if so, returns early. Then it marks the isSelected property and calls a method reflecting its new selection state.
Finally I have two “protected” methods, “Select” and “Deselect”. Protected implies the same meaning here, other classes can not see or invoke these methods, unless they are a subclass of “Selectable”. I also marked both of the methods as “abstract” which, unlike using “virtual” means that subclass MUST “override” the method (unless they are also abstract classes).
Note that you can only mark a method as abstract if the class is also abstract or you will get an error, “error CS0513: `Selectable.Select()’ is abstract but it is declared in the non-abstract class `Selectable’”
If you create a non-abstract subclass and don’t implement the abstract methods you will get another error, ”error CS0534: `SelectableDecoration’ does not implement inherited abstract member `Selectable.Select()’”
Constructors and Destructors
Constructors allow you to control what happens when an object is instantiated, or how it is instantiated. Destructors allow you to control what happens when an object is destroyed and provide a way for you to clean up resources, etc.
using UnityEngine; using System.Collections; public class Point { public int x; public int y; public Point () { Debug.Log("I'm alive! ... but I'm all zeroed out."); } public Point (int x, int y) { this.x = x; this.y = y; Debug.Log("Whoa, I must be valuable!"); } ~ Point () { Debug.Log("I'm melting, MELTING!!!"); } } public class Demo : MonoBehaviour { public void Start () { Point p1 = new Point(); Point p2 = new Point(2, 5); } }
This sample defined a class called “Point” which demonstrates two different constructors – a “default” constructor (one without parameters) and a constructor with parameters. You can tell a method is a constructor because it has no return type and because the identifier is the same name as the name of the class.
The Demo class creates two instances of the Point class, one for each type of constructor. Note that you must use the “new” keyword along with the constructor or you will get an error, ”error CS0119: Expression denotes a `type’, where a `variable’, `value’ or `method group’ was expected”
Also note that you should not use constructors with MonoBehaviour based scripts. If you try, you will see a warning in the console, ”You are trying to create a MonoBehaviour using the ‘new’ keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all”. Unity requires you to create objects and attach components in a special way so that it can properly configure and track everything.
Our Point class also defines a “Destructor” which you can identify by the tilde which proceeds an identifier which matches the name of the class. Unlike a constructor, you do not directly invoke a destructor. It will be called automatically when an object goes “out of scope” or is destroyed. In this example, the two point instances are local to the method “Start”, so as soon as the method completes they will already be out of scope.
If you attach this script to a GameObject and run your scene you will see the following output in the console window:
I’m alive! … but I’m all zeroed out.
Whoa, I must be valuable!
I’m melting, MELTING!!!
I’m melting, MELTING!!!
Static Constructors
In the same way that you can specify logic for the initiation of an instance of a class, you can also specify logic for the initiation of the class itself. A static constructor is not invoked directly, but will be called immediately (and once only) as soon as the class would be referenced by anything else. A static constructor looks like a normal constructor but is preceded by the keyword “static”.
static Point () { Debug.Log("Hello World!"); }
If you added this to the Point class in the previous sample, you would see “Hello World!” printed to the console before the first Point was instantiated.
If your class makes use of static variables, the static constructor is a good place to initialize them.
Static Classes
A class which is marked as static cannot be instantiated. All variables and methods it contains must also be marked static.
using UnityEngine; using System.Collections; public static class Global { public static int count; public static void DoStuff () { count++; Debug.Log("I have " + count); } }
If you try to instantiate a static class you will get an error, ”error CS0723: `test’: cannot declare variables of static types”
If you try to subclass a static class you will also get an error, ”error CS0709: `Foo’: Cannot derive from static class `Global’”
You work with a static class by referencing the name of the class and use dot notation such as Global.DoStuff();
Static classes can be convenient ways to store information that anything can access from any point, because you do not need to keep track of or obtain an instance of the class. Note that the static fields will live as long as the class does – which is the duration of the program.
You can have similar functionality to a static class, but also be able to use inheritance etc, by using the Singleton design pattern. https://msdn.microsoft.com/en-us/library/ff650316.aspx
Summary
In this lesson, we covered a whole lot of features of classes including normal and static constructors, destructors, inheritance and polymorphism. I introduced new keywords such as abstract, virtual, override, and protected. I also showed how a class itself can be abstract or static. Although many of these features are easy to show, they can be considered advanced material. Architecture which takes advantage of these features can be tricky to understand fully.