Unity: Strongly Typed Game Object Fields — How and Why

Often in Unity one game object in a scene must reference another. Unity lets us do this by adding a public field to a script, which we can then set within Unity's inspector to another object. For example, let's say we have an enemy object that needs a reference to a weapon object.

Our hypothetical enemy object has a script named EnemyController, with a public field named Weapon. Our weapon object is a game object, so it makes sense to declare Weapon as type GameObject:

EnemyController's Weapon field with type GameObject

This lets us drag our weapon object into the Weapon field and be on our way:

A weapon object assigned to an Enemy's Weapon field

But there's a bug here waiting for an opportunity to strike. What if we drag in a different object, that's not a weapon? Either by accident, or because it's six months after we added the Weapon field and don't remember what it requires. As things stand in our example, Unity doesn't mind:

A non-weapon object assigned to an Enemy's Weapon field

Everything will build and run fine, until your enemy tries to invoke UseWeapon on the Weapon field's WeaponController component. The NotAWeapon object doesn't have a WeaponController component, so it'll throw an exception.

I always prefer to catch errors at build time (or even better, design time) rather than run time whenever possible. Run time testing is tedious and error-prone, and later modifications can cause regression errors that require additional (endless?) testing to catch. It'd be great if Unity could catch errors like this for us as soon as we make them, so we know as fast as possible that something is wrong.

Thankfully, Unity will catch these types of error at design time, if we use strongly typed fields. That is, instead of making a field type GameObject, which could be anything, make the field the same type as a component we need the object to have.

In our example we expect the weapon object to have a WeaponController component. Therefore we can make our Weapon field type WeaponController:

EnemyController's Weapon field as type WeaponController

This will prevent us from setting the Weapon field to any object that doesn't have a WeaponController component:

Unity not allowing assignment of a non-weapon object to the Weapon field typed as WeaponController

It also has the bonus of simplifying our EnemyController code. Instead of getting the WeaponController component from the Weapon object to call UseWeapon, we can call UseWeapon directly on the Weapon object. This is because Weapon here is a direct reference to the WeaponController component, rather than to the game object itself. (If you need to access its game object, you can do so via Weapon.gameObject.)

Strongly typed field references also work polymorphically. That is, if the field type is a base class, you can assign any subclass to it. For example, let's say WeaponController is an abstract base class:

WeaponController as an abstract class

And SwordController is a concrete subclass of WeaponController:

A concrete subclass of WeaponController named SwordController

Unity won't let us add a WeaponController component to a game object, because it's an abstract class. So to create a sword game object, we need to add the concrete SwordController component to it. But we want our enemy to be capable of using any type of weapon, not just swords, so we leave EnemyController.Weapon as type WeaponController. Since SwordController extends WeaponController, we can still make the assignment:

Setting a SwordController object in a WeaponController field

This gives us flexibility (the ability to use weapons interchangeably) and loose coupling (not designing weapons to be used by specific types of enemy, and vice versa) without sacrificing type safety.

Of course, there will be cases where typing a field like this won't work. You may expect a given game object to have multiple components. Or no necessary components. But a lot of the time, using strongly typed fields is appropriate, and will grant peace of mind and less time spent fixing this class of bug.