Designing Enemies Using Abstract Classes and Access Modifiers in Unity 2021
In this article we’ll take another look at how we can organize our Classes around common functionality.
For a more abstract (pun intended) article on classic inheritance, please see my previous article:
Classic Inheritance Demonstration with Shape Classes in Unity 2021
In this article, we’ll go over the main aspects of the Object-Oriented-Programming (OOP) style with an emphasis on…
The Enemy Parent Class
Above we have a simple Enemy class we can use as a template for our various enemy classes.
Since enemy classes are all likely to share health, speed, and gems values, we can put these class variables in their Parent class, “Enemy”.
The MossGiant Class
One enemy type is the Moss Giant.
For now, all we need to do is to tell it to inherit from the enemy class rather than Monobehavior.
Note that since the Enemy class itself inherits from Monobehavior, the MossGiant class will as well despite not having the explicit reference.
When we attached the MossGiant class as a component to a game object in the Unity Editor, we can see that despite it having no values within it’s own class, it does indeed inherit the Enemy class’ variables.
Add a call to the Attack() method in the MossGiant class during the Start() method.
Note, there is no Attack() class explicitly stated in the MossGiant class itself.
In the Enemy class, add a debug.log statement that displays “this.gameObject.name”.
Note that there is no call to this method from a Start() method in the Enemy class.
Hit play in Unity and we can see that the MossGiant class gets credit for calling the Attack() method in its parent class on Start() method execution.
The Spider Class
We can create a new script/class called “Spider” that also inherits from Enemy and calls Attack() on Start() method execution.
Make sure to attach this to an active game object in the scene before hitting play.
If we hit play, we can see that both classes inheriting from Enemy are correctly claiming credit for calling the Attack() method inherited from the Parent class.
Encapsulation and Access Modifiers
So far, we’ve only used public access modifiers for our variables.
But what happens when we make a Class variable in Enemy private?
As we can see above, child classes cannot see private class variables in their parent classes!
As the chart from Technopedia above points out, we can use the “protected” access modifier to prevent other classes from accessing the Gems variable in the Enemy class, UNLESS they are a child class of the Enemy class.
Now, our child Spider class can access it’s own version of the Gems class variable.
This will not assign the same value to all child classes, it just inherits the same variable name in all child classes for each to use.
The Virtual and Override Keywords
We can create some diverse functionality in child classes using the parent class methods.
To do so, we need to mark those methods that will have differing implementations in the child classes.
We do this by adding the “virtual” keyword as shown above.
I’ve also changed the access modifier to protected rather than public.
Note that child classes most use the same access modifier.
In the Spider class, we add the override keyword to the virtualized Attack() method being inherited.
If we run this, we can see that the Spider class no longer implements the Debug.Log message in the Enemy class which the MossGiant class is still doing.
Instead, it has been entirely replaced by the override Attack() method in the Spider class.
The Best of Both Worlds
But what if you want the parent class’ method to fire AND the child class’ implementation?
Simply use the base keyword to trigger this behavior.
Now if we run our game, we can see that both the Parent class version of the Attack() method and the Spider class version of the Attack() method have run their Debug.Log messages.
Forcing Unique Child Class Implementations
If we want the Parent class to force child classes to implement a unique version of a method, we need to first make the parent class an abstract class.
Then we need to use the “abstract” keyword again in our forced unique method.
In the example above, we’ll be forcing each child class to implement it’s own unique Update() method.
If we look at our child classes, we can see they are now throwing an error because they have no implementations for Update()!
For each child class, we’ll use the “override” keyword on a new Update() method.
Squiggly lines be gone!