Christopher Jones, Games Development
  • Portfolio
    • Unity
    • 2D Games
    • Old Work
    • Source Engine
  • About Me / Contact
  • Blog

Unity, GetComponent(T) and Interfaces

11/9/2013

11 Comments

 
A/N: This is actually a post discussing GetComponent<T>, with angled brackets, but putting those in title made Weebly cry. Just to be clear though, this is about the Generic GetComponent function.

Here's a fun little quibble I've run into whilst mucking about with learning to code. I've seen it trip up a few other people on UnityAnswers and there's a pretty simple solution, thus; TIME TO DUST OFF THE BLOG.

Polymorphism is an exceptionally powerful concept. It's also the reason I will - and always - recommend people script in C# rather than Boo or Unity's weird hybrid Not-Quite-Java. In my current case, I found a situation wherein I needed to use an Interface with Unity's GetComponent<T>() function; it relates to making a 'IWeapons' interface for... well, components designed as Weapons to be 'fired' (which in said game can do anything to launching a single projectile to starting up a grappling beam with which you smash spaceships into spaceships and presumably investigate what maniacal laughter sounds like from behind an oxygen mask). As these 'weapon' components would be performing a very wide range of tasks, I felt the typical inheritance tree model would be too constricting and that an interface would be best.

Except, of course, for the GetComponent<T>() function. Which can, as you might imagine, only search for and return Components, aka 'Objects that Derive From the MonoBehaviour Class', of which an interface is no guarantee.

The solution? It's actually hilariously simple. There is a way to implement interface-like behaviour whilst still assuring you a derived from a certain base class. Namely, it's the concept of the abstract class.

Firstly, lets review. The purpose of an interface is purely to provide an agreed public contract; namely, any other class working with something that implements a specific interface is guaranteed to hold the methods and properties defined within that interface. So for example:

public interface IWeapon {
    void Fire();
    void StartFiring();
    void StopFiring();
}
If my weapons components all implemented that interface, then in all AI logic, player input handling and so forth, all that code would require is:
IWeapon weapon = GetComponent<IWeapon>();
weapon.Fire();
And the weapon component - be it a simple projectile launcher, raytracer or magic cat missile spewer - would fire. In short, the input / AI code doesn't need to know how the specific weapon fires, it just needs to know it is a weapon, and be able to tell it to fire. In this way, the weapon components themselves can handle their firing behaviour any way they like, by all the myriad ways.

And this is what interfaces allow you; it is a simple guarantee that any class that implements a given interface (and, most beautiful of all, a single class can implement more than one interface) will contain the functions the interface specifies. For instance, that IWeapon interface guarantees that any implementing class will have a public Fire() methods with zero arguments, returning void. Which is all anything interacting with a weapon component will need to know.

However, the catch rolls in here; all my weapon components also need to be components (classes deriving from MonoBehaviour that you can slap onto gameobjects from the inspector) and an interface does not guarantee this. It just declares what functions and properties will be publicly available. Thus, GetComponent<T>(), which can only search for and return MonoBehaviour objects, fails when it comes to searching for interfaces. If, as above, I wrote:
IWeapon weaponComponent = GetComponent<IWeapon>();
It would fail. Compiler error.
error CS0309: The type `IWeapon' must be convertible to `UnityEngine.Component' in order to use it as parameter `T' in the generic type or method `UnityEngine.Component.GetComponent<T>()'
So how can we save this? We want a public contract; that a Weapon Component will have the method Fire() but without defining what that method does and hampering what we can do with it. Well, mercifully, there's a way around this:

Enter, the abstract class.
public abstract class AbstractWeapon : Monobehaviour, IWeapon {
    public abstract void Fire();
    public abstract void StartFiring();
    public abstract void StopFiring();
}
(note how I still have it implementing IWeapon, given that it's true and I might as well, though you could probably remove the IWeapon interface at this point)
Now, if I have my weapon components all derive from AbstractWeapon, we ensure they implement IWeapon and we ensure they are a derived class from Monobehaviour, which GetComponent<T>() can work with:
AbstractWeapon weaponComponent = GetComponent<AbstractWeapon>();
An abstract class and any declared abstract methods are basically a way of saying "There will be a function/property here, and my derived classes will implement it". Much like an interface, it declares properties and functions without declaring how they work, leaving that up to the derived classes (which, like an interface, will have to implement said code by overriding the abstract functions or there'll be a compile error), but it also guarantees the base class (Monobehaviour) and all the code that comes with that.

There is a downside of course; a class can implement any number of interfaces, but can only have one base class; it's still an inheritance tree and thus there will naturally be a 'split' involved somewhere. Fortunately Unity's component based design lets you side-step this a little bit (whilst there will be splits, there's nothing to say you can't just have two different components).

Obviously you can still have some behaviour and code defined in the above abstract class example (which will probably prove advantageous; ie providing protected helper functions derived classes can call on rather that waste time writing the same snippet of code multiple times) - something you would not be able to do with an interface - but in this case, with all the methods marked abstract, it provides exactly the same functionality as an interface with a pre-defined base class. Which is exactly what we want.
11 Comments
Michael
5/1/2014 11:24:59 am

Nice! This is exactly an issue that I was having. Good article!

Reply
Paul White link
5/18/2014 12:22:28 pm

Hi!

There are solutions if you'd like to retrieve interfaces using GetComponent, I'll list the easiest one first:

IWeapon weaponComponent = (IWeapon)GetComponent(typeof(IWeapon));

Note the cast to IWeapon...very important!

The first one I learned is a bit strange, but still works:

MonoBehaviour[] mbs = gameObject.GetComponents<MonoBehaviour>();

foreach( MonoBehaviour m in mbs ){

if ( m is IWeapon ){

weaponComponent = (IWeapon)m;
break;

}

}


Another good trick I use along with the first example is to put [RequireComponent(typeof(IWeapon))] just above the class declaration. Then you'll know what's required on that GameObject. Good luck using these!

Reply
Kurt M
6/21/2014 07:50:36 am

With Paul's method, how can we access a GameObject/Component that has that interface? And also access another Interface?

Example:

ICarryable item = collider.GetComponent(typeof(ICarryable)) as ICarryable;

if (item is IWeapon)
{
// It's also a weapon, so run a method from IWeapon
// item.Reload() for example. How do we do this? Do we just do another GetComponent instead of the "item is IWeapon"?
}

If we didn't have the collider to work with, how would be do get the GameObject e.g.

List<ICarryable> carryables;

Debug.Log(carryables[0].gameObject.name); // This is impossible of course. It seems the best way is just to have all your lists be GameObjects, and check they are ICarryable before adding.

Reply
Paul White link
6/21/2014 10:12:34 am

Kurt,

After getting type ICarryable from the GameObject into 'item', knowing that we've not derived from a MonoBehaviour I would create this dictionary:

using System.Collections.Generic;

public Dictionary<ICarryable,GameObject> carryables = new Dictionary<ICarryable,GameObject>();

in another class, then to assign the ICarryable's GameObject (accessed by instance ref or static):

carryables[item] = collider.gameObject;

Now you've got access to each ICarryable's GameObject that was retrieved from the initial collider. So if need to access the ICarryable's collider again after it becomes out of scope, I retrieve it like so:

carryables[item].collider; (carryables[item] returns it's GameObject)

Good luck!

Paul White link
6/21/2014 10:12:58 am

Kurt,

After getting type ICarryable from the GameObject into 'item', knowing that we've not derived from a MonoBehaviour I would create this dictionary:

using System.Collections.Generic;

public Dictionary<ICarryable,GameObject> carryables = new Dictionary<ICarryable,GameObject>();

in another class, then to assign the ICarryable's GameObject (accessed by instance ref or static):

carryables[item] = collider.gameObject;

Now you've got access to each ICarryable's GameObject that was retrieved from the initial collider. So if need to access the ICarryable's collider again after it becomes out of scope, I retrieve it like so:

carryables[item].collider; (carryables[item] returns it's GameObject)

Good luck!

Christopher
6/21/2014 07:08:55 pm

The simpler alternative would be to add a gameobject property to the ICarryable interface.

As for conversion to another interface (ie IWeapon in your example):

ICarryable item = collider.GetComponent(typeof(ICarryable)) as ICarryable;

IWeapon weapon = (IWeapon)item;
if(weapon != null){
//IWeapon calls here
}

Henry link
1/6/2021 12:07:30 am

Thanks greaat blog

Reply
MckinneyVia link
11/5/2021 03:51:27 am

Very much appreciated. Thank you for this excellent article. Keep posting!

Reply
Brett White link
3/21/2022 09:15:22 pm

What an exquisite article! Your post is very helpful right now. Thank you for sharing this informative one.

Reply
James Johnson link
10/13/2022 11:18:52 am

Ok notice room off what word art. Leave join miss itself. Around name cause value PM some west change.

Reply
Peter Patterson link
10/13/2022 03:17:22 pm

Necessary movie natural section dog. Attention money nothing simply result chance back. Popular leader performance set. Gun worry want crime short possible democratic.

Reply



Leave a Reply.

    Author

    A UK-based amateur game developer.

    Archives

    May 2017
    May 2016
    April 2016
    October 2014
    December 2013
    November 2013

    Categories

    All
    Scripting And Theory
    Shaders Are Fun
    Shaders Are Hilarious
    Unity 4

    RSS Feed

Powered by Create your own unique website with customizable templates.