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?
Answer
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);
}
}
No comments:
Post a Comment