An Interface is much like an Abstract Class in C#. It is a way to implement abstraction in C#. Interfaces are used to implement multiple Inheritance in a Class. A class can only inherit from one base class but it can implement multiple interfaces. Interfaces can not be used to create other objects, no constructors. Interfaces can contain bodiless Methods and Properties, but no fields or variables. For example I have a sword that is a MonoBehavior. The sword will Implement Inventory Item Interface and Weapon Interface.
I am going to use an interface for my spawners. None of my spawners will be inheriting from a MonoBehavior they will be ScriptableObjects, I need an init method that takes the Transform to spawn the objects in and a MonoBehavior in order to start and stop the Corutines. They need to have a Start, Stop, and Destroy All Spawned Objects.
Spawner Implementing ISpawner
I added ISpawner to the Spawner Behavior. I then used my IED to auto implement ISpawner in my Behavior.
I Changed the Coroutine to private. I added a MonoBehavior and a Transform variable.
I moved all of the Init Spawner code to Init and deleted the Init Spawner Method. I set the MonoBehavior and Transform variables to the variables passed in to the Init Method.
In the Start Method I check to see if the Spawner has been initialized. I then stop the Spawner from spawning. I then start the spawning Coroutine.
In the Stop Method I check to see if the Spawner has been initialized. I then Stop the spawning Coroutine.
In the Destroy all Spawned Objects I loop through all of the spawned objects and destroy them.
In the Spawn Routine I removed the Transform parameter and use the local Transform for the container.
Spawn Manager Implementing ISpawner
I changed my Enemy Spawner List and Power Up Spawner List to a List of ScriptableObjects. I did not Change them to a List of ISpawner because Unity does not support assigning Interface types in the inspector out of the box, you have to code them in your own Editor classes. All of my spawners will be ScriptableObjects.
There is a simple hack (work around) that can be used to get around making sure that only an ISpawner is assignable in the Inspector by using the On Validate Method. This Method is called every time a Behavior is loaded or a value is changed in the Inspector, this Method is called in Unity Editor only. I loop through each list, if the item in the list is not empty I check to see if it is an ISpawner. If it is not then I make the item empty (set it to null).
In the Awake Method I use the Remove All Method on the list to remove anything that is not an ISpawner, I could have just removed all the items that are null, but doing it this way ensures that when I use them later the list only contains ISpawner types.
In the Start Method I loop through the lists Casting the items to ISpawner then call the new Init Method.
In the Start Spawning Method I loop through the lists Casting the items to ISpawner then call the new Start Method.
The For Each Method I use a LINQ expression to cast the items to ISpawner, this is just easier to read. I also do not need to do any null checking because in the Awake method I already removed everything that was not an ISpawner.
While I was making code changes I went ahead and change all of the
public variables to
[SeializeField] private variables. The only reason they were public was so that they would show in the inspector. I do not want them to be accessed or modified from outside the class.
By using interfaces I can add a layer of abstraction to my code, any Class that implements an Interface is guaranteed to have certain functionality. By using the Interface to call the Methods from another class if I can easily add another Class that Implements that Interface and use that Class without any code changes, adding the new class or adding additional checks. This follows the SOLID or the 5 principles of object oriented design.
- Single-responsibility Principle — The Spawners are responsible only for spawning game object in the game. The Spawn Manager is responsible for Keeping track of all of the spawners in the game.
- Liskov Substitution Principle — Every class that Implements ISpawner can be substituted for ISpawner.
- Interface segregation principle — There are no methods in ISpawner that the spawners do not use and the Spawner uses ISpawner Interface. The game is not currently using the Destroy all Spawned Objects it is there so I can add a power up that the player can collect that will destroy all of the enemies and also give the enemy a way that they can hinder the player by getting tide of all the currently spawned power ups.
- Open-closed principle — I can easily extend the spawn manager by creating new Spawner types and I do not have to make any modifications to the Spawn Manager in order to implement them.
- Dependency inversion principle — The Spawn Manager is depending on the abstraction provided by the ISpawner.