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?
(Note: I deleted my reply and added it to the original post so that the code would display properly)
Answer by shelljump · Oct 13, 2015 at 07:38 PM
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.
There is some debate about that version of GetComponent. It was actually removed from Unity at one point but was put back.
Answer by Dave-Carlile · Oct 13, 2015 at 04:02 PM
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.
The reason I didn't want to use tags is because I wanted this class to be fairly generic. If I used tags I would either have to have all instances of ObjectSpawner react to the same types of catylists, or I would have to create a separate tag for each different catylist type.
The second solution is really interesting, and will definitely come in handy for future problems. However, if ObjectSpawner is the only class taking advantage of Category, it seems like kind of violates encapsulation principles, since its basically a public piece of ObjectSpawner's functionality that sits around in other classes. That being said, it does do exactly what I asked for, so thanks :)
Hmmm, I don't see that as a public piece of ObjectSpawner's functionality... it's just categorization data stored on an object - an attribute of the object if you will. How is that any different from exa$$anonymous$$ing the data type of the object - which is just another attribute of it.
ObjectSpawner's functionality is to exa$$anonymous$$e attributes of an object to decide if it should act on it. $$anonymous$$akes no difference if that attribute happens to be built into the compiler or an attribute that you created. Another argument against your logic is that you go on to check for another specific component - Damagable - and go on to do things based on the existence of that.
Your answer
![](https://koobas.hobune.stream/wayback/20220612041114im_/https://answers.unity.com/themes/thub/images/avi.jpg)