6
\$\begingroup\$

This question came from a fellow developer on Twitter, and I figured StackExchange would be a better format for explaining & sharing the answer. To paraphrase the question:


I'm setting up an in-game dev console in my Unity game, where a user should be able to call any method on any MonoBehaviour in the scene.

I want to use something like FindObjectsOfType<>, but because it's coming from a string input by the user, the class name is unknown at compile time.

I also want this to work universally, on any MonoBehaviour, not just ones I've specially instrumented or registered with the console system.

Is there a way to find instances of a type with a particular name, and call methods on them, using something like reflection?

\$\endgroup\$

1 Answer 1

8
\$\begingroup\$

Yes, this is possible.

Because it's making heavy use of reflection and uncontrolled strings, it can be both slow and unsafe, so I would not recommend it for regular game behaviour. For a console use case though, it's probably OK. If you intend to ship the console with a released game, I'd recommend adding some extra error-checking and sanitization of allowed commands that I've elided here.

// References to the Unity Engine types need an assembly qualified name,
// so we cache that here. Repeat for any 3rd-party assemblies you use.
static readonly string engineAssemblyName =
     System.Reflection.Assembly.GetAssembly(typeof(GameObject)).FullName;

void InvokeAll(string componentName, string methodName, System.Object[] arguments)
{
    // We'll search for a type matching the given component name.
    System.Type type;

    // First, check our own CSharp assembly (no extra qualification needed).
    type = System.Type.GetType(componentName);

    // If not found there, then check the UnityEngine assembly.
    if (type == null) {
        string qualifiedName = string.Format("UnityEngine.{0}", 
            System.Reflection.Assembly.CreateQualifiedName
            (engineAssemblyName, componentName));
        type = System.Type.GetType(qualifiedName);
    }

    if(type == null) {
        Debug.LogErrorFormat(
              "Could not find type {0} in Assembly-CSharp or UnityEngine.",
              componentName);
        return;
    }

    // We've found a valid type.
    // Use the Unity method to retrieve all active instances in the scene.
    var components = FindObjectsOfType(type);

    // Note: this currently works only for methods with a single definition.
    // More information is needed to disambiguate which method you want when
    // it has multiple overloads (same name with different signatures).
    var method = type.GetMethod(methodName);

    // If you need to access private/protected methods too,
    // use this version that peeks into non-public areas...
    //var method = type.GetMethod(methodName, 
    //      System.Reflection.BindingFlags.Public 
    //    | System.Reflection.BindingFlags.NonPublic 
    //    | System.Reflection.BindingFlags.Instance);

    // You could also search through the array to select only 
    // certain instances to invoke...
    foreach(var component in components)
    {
        method.Invoke(component, arguments);
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .