Unity Development — Abstract Classes
In the Unity Developer Journey, you have already seen the use of the Prefab. A way to make a specific object only once and then be able to Instantiate that same object whenever and wherever you want. With Abstract Classes, we are going to see how to make numerous classes that follow a pattern or category.
In the example we’re going to look at, we are going to make a few enemies. Each of these enemies are going to share a number of different variables. In our case, they are all going to need a some sort of health, speed, SpriteRenderer, Animator, and an algorithm for them to move around and idle at different waypoints.
I am just going to quickly go over the initial setup of the enemies’ visual components, as they are not the main focus of this article. Each enemy should have an animation for ‘Walk’ and ‘Idle.’ Their animators should be set up to transition from Idle to Walk when the Idle animation ends. Make a trigger called ‘Idle’ in the parameters. This will be triggered when we want the enemies to go from walking to idling.
Our enemies are going to walk back and forth and idle at specific waypoints. In the Hierarchy we have each enemy set up as an empty gameObject, a child object holding the sprite and Animator, and two separate objects to act as their waypoints. These waypoint objects are NOT children as well. This is the end of our initial setup.
So now we know that we want a Moss Giant, a Spider, and a Skeleton to all do the same thing. This is true for the most part, but they will not share waypoints, and we want them to have separate speeds and eventually differ in other ways. Instead of writing out three separate scripts doing the same thing, we are going to write one and branch from there.
Our script is simply going to be called ‘Enemy’ and it will be set up as an abstract class. We want each enemy to have speed, a reference to their SpriteRenderers and Animators, a currentTarget for their movement, and reference to their waypoints.
Make a note that the speed and waypoints are serialized and that all of our variable are protected. Protected means that they are not public for any class to have access, but able to be accessed by those that inherit the Enemy class.
The chunk of code in this script is going to have the enemies move back and forth between waypoints, go idle at the waypoints, and then continue moving. Again, this is not the main focus of the article, but basically the currentTarget is going to change when reaching a waypoint position, the enemy will stop walk to go idle, then it will flip the sprite and walk to the next waypoint. The code looks like this.
Likely that top line in Update() is the most confusing. It is function as part of the Animator to say, if we are running the Idle animation, return and do nothing. So it will not go on to the Movement() code. You may also notice that the functions are all called ‘virtual.’ This is the syntax used to make sure they are also run by the inheriting classes.
Normally when setting up the SpriteRenderer, Animator, and other components, we would do it in Start() or Awake(). Like the Movement() function, we are going to separate it for cleanliness. They will be placed into another function called Init() that is called by Start().
Now we have the entire Enemy script done, but we are not going to place it on enemy objects. We are now going to very quickly make three more scripts and have them inherit this Enemy script. Just replace ‘MonoBehavior’ with ‘Enemy’ at the top.
Place these scripts on their proper object and you are technically done. I would like to point out one more thing though. You could have simply put the ‘Enemy’ script on each object and it would do the same, but the reason why this is such a powerful tool is that you are now prepared to create their differences later.
Any one of these scripts can make their own methods or override the methods taken from the parent class. When you override them you can also take the behavior of the original method and add to it. This is done with the ‘base’ call. It looks like this and it is what we are doing for the Init() method.
When you add the base to any method you override, it was have the same affect, but then allow you to do more that is specific to this class. It is the ease of only having to write some complicated code once, but then having the freedom to change things around for the specific object.
The possibilities here are higher than you’ve seen up to this point. Good luck and make something great.