Using GetComponent with interfaces in Unity
In my new game, I have a GameManager that instantiates enemies. I'd like my enemy prefabs to all share a common IEnemy interface. How do I make my GameManager use the interface instead of the concrete Enemy type?
My starting code is this:
public class Enemy : MonoBehaviour
{
...
}
class GameManager {
public Enemy enemyPrefab;
private void Start()
{
InstantiateEnemy();
}
private void InstantiateEnemy() {
var enemy = Instantiate(enemyPrefab);
enemy.OnStart += (object sender, System.EventArgs e) => (sender as Enemy).AddHp(enemyHealth);
enemy.OnDeath += (object sender, System.EventArgs e) => StartCoroutine(InstantiateEnemy());
}
}
I would like to introduce a new type of enemy, say UndeadEnemy, so I can't explicitly refer to Enemy in GameManager anymore. I want to introduce this IEnemy interface:
using System;
public interface IEnemy
{
public event EventHandler OnStart;
public event EventHandler OnDeath;
public void AddHp(int hitPoints);
}
And make my Enemy implement it:
public class Enemy : MonoBehaviour, IEnemy
{
...
}
Finally, the hard part is to make GameManager use it.
- If I change the prefab type to IEnemy (
public IEnemy enemyPrefab;
), Instantiate won't work anymore.
- If I make the prefab a GameObject, and Instantiate<IEnemy>, it won't compile either:
- Casting the GameObject to an IEnemy doesn't either:
CS0039: Cannot convert type 'UnityEngine.GameObject' to 'IEnemy' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion
- Nor does checking for an interface after it has been instantiated:
CS0184: The given expression is never of the provided ('IEnemy') type
- GetComponent<IEnemy> (
var enemy = enemyGameObject.GetComponent<IEnemy>();
) seems to compile, but on runtime, it actually resolves to null, and it throws an exception. - MonoBehavior and GameObject are a concrete classes, so you can't make IEnemy implement them.
After all this failed, I googled for an answer and found this: https://www.cjonesdev.com/blog/unity-getcomponentlttgt-and-interfaces.
The author of the post recommends using Abstract classes instead. But I don't like it since it will force me to create an Abstract class for every Interface that I make, and every implementation will have to inherit that Abstract class. I really want to keep interface and implementation separate.
At the bottom fo the post, a certain Paul White shared his solution:
IWeapon weaponComponent = (IWeapon)GetComponent(typeof(IWeapon));
And indeed, it compiles and works as you'd expect at runtime. You can cast with (IWeapon) component
or component as IWeapon
.
In the end, the final version of my GameManager is this:
class GameManager {
public GameObject enemyPrefab;
private void Start()
{
InstantiateEnemy();
}
private void InstantiateEnemy() {
var enemyGameObject = Instantiate(enemyPrefab);
var enemy = enemyGameObject.GetComponent(typeof(IEnemy)) as IEnemy;
enemy.OnStart += (object sender, System.EventArgs e) => (sender as IEnemy).AddHp(enemyHealth);
enemy.OnDeath += (object sender, System.EventArgs e) => StartCoroutine(InstantiateEnemy(2));
}
}
One last thing. One advantage I could see in using Abstract classes is that I can force the prefab to be of the type that I want. Right now, it's set to a GameObject, so one could accidentally drag soemthing else than an IEnemy.