Using reflection to have a variable template parameter

Hi all,

I am trying to write a simple script that shoots monsters/items whenever an object of some specified class gets close. However, I am having some trouble getting my code to compile.

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (CircleCollider2D))]
public class ObjectSpawner : MonoBehaviour {

	public Spawnable objToSpawn;
	public Vector2 spawnVelocity = Vector2.zero;
	public float spawnPeriodicity = 1;
	public System.Type catylist = typeof (Player);
	public bool useGlobalTimer = false;
	public bool useExternalActivator = false;
	[HideInInspector]
	public bool isActivated = false;

	float timer = 0;
	Collider2D other;

	void Start () {
		if (useExternalActivator) {
			//no need for collider since activation happens elsewhere
			CircleCollider2D trigger = this.GetComponent<CircleCollider2D>();
			trigger.enabled = false;
		}
	}
	
	void Update () {
		if (!useGlobalTimer && !isActivated) {
			return;
		}
		timer += Time.deltaTime;
		if (timer > spawnPeriodicity && isActivated) {
		    Spawnable obj = Instantiate (objToSpawn, transform.position, transform.rotation) as Spawnable;
			obj.Spawn (spawnVelocity, other);
		}
		timer %= spawnPeriodicity;
	}

	void OnTriggerEnter2D (Collider2D coll) {
		//only activate if the detected object is of type catylist
		if (coll.GetComponent<catylyst> () != null) { //ERROR: CATYLIST DOES NOT EXIST IN CURRENT CONTEXT
			//if the object can be destroyed, we should deactivate if that happens
			Damagable dmg = coll.GetComponent<Damagable>();
			if (dmg != null) {
				dmg.OnDeath += Deactivate;
			}
			isActivated = true;
			other = coll;
		}
	}

	void OnTriggerExit2D (Collider2D coll) {
		//deactivate when the same instance that activated us exits the trigger
		if (coll.gameObject == other.gameObject) {
			Deactivate ();
		}
	}

	void Deactivate () {
		isActivated = false;
		other = null;
	}
}

The issue is here: coll.GetComponent<catylist> (). I get an error that says catylist doesn’t exist in the current context, but I don’t understand why. Catylist is of type System.Type, so shouldn’t this work?

EDIT:

So after looking around a bit I found out that template parameters have to be known at compile time, so the way I was trying to do this doesn’t work. The solution, from what I saw, is to use “reflection”, which is a concept I have never seen before.

I replaced line 41 with the following code:

System.Reflection.MethodInfo refl = GetType().GetMethod ("GetComponent").
                                    MakeGenericMethod (new System.Type[] {catylist});
if (refl.Invoke (coll, new Object[] {}) != null) {

The code compiles, but I get a runtime error that says “Ambiguous Match Exception: Ambiguous match in method resolution”. Does anyone have an idea on how to fix that?

Reflection probably isn’t your best solution here. It’s slow, and I believe not entirely platform independent. It’s often a bad choice.

Is there a reason you wouldn’t just use a tag? Either that or create new component that contains categorization properties. Add that component to every object you care about examining this way, get a reference to it much like you’re getting the Damagable component, then examine the properties.

One of the properties could be the type, which you can then compare to catylyst.

class Category : Monobehaviour
{
    public Type CatylistType;
}

void OnTriggerEnter2D (Collider2D coll)
{
    ...
    Category category = GetComponent<Category>();
    if (category.CatylistType == catylist)
    {
        ...
    }
}

Or something like that. Hopefully that gets my point across enough to adapt it to your needs.

Thanks for the help everyone! However, I think I found a way that does what I want without creating a bunch of tags or components.

It turns out there is a generic version of GetComponent which uses a string to determine the component type. I changed the type of catylist to a string and change line 41 to if (coll.GetComponent(catylist) != null) { and it works perfectly.

One thing I will have to do, however, is check in the Start() function that the catylist names an existing type. There won’t be an error if catylist doesn’t name a valid type, but it should probably still be logged. Also, I should check that if catylist does name a valid type, then that type has attribute [RequireComponent (typeof (Collider2D))]. Otherwise OnTriggerEnter2D will never be called, which may be confusing. Adding that all in should be relatively straightforward, though.