This is gonna be a heavy programming post.
TL;DR: I made a Unity debug tool that can call functions from MonoBehaviours directly!
On the edge of starting a new project, I stopped myself and thought about testing debug behaviors. I thought, "Gee, I sure don't like hooking up a bunch of random debug keys to update loops that I'll delete anyway. Wouldn't it be nice to have a debug system that you can type in a gameObject, component, and function name with arguments and just call it?"
Sounds simple... right? :))))))))))))
Well obviously not really.
The first step was to see what unity already had in terms of N E A R E F F O R T L E S S F U N C T I O N C A L L I N G .
They have a couple options.
There's the MonoBehaviour Invoke. You just find a MonoBehaviour, and pass in a string what function you want. It takes in NO arguments, so it's not 100% ideal.
monoBehaviour.Invoke("Foo", 0); // This calls "Foo()" with a 0 second delay
The next best thing is BroadcastMessage. We actually get an argument to pass in, but only one... and there's a second caveat -- If the gameObject has two of the same MonoBehaviour, or the same MonoBehaviour in its children, it will call this on ALL of them. Not ideal for testing!
monoBehaviour.BroadcastMessage("Foo", 1); // Gettin' there... This calls Foo(int i) and passes in one argument... BUT ONLY ONE
So, what else is there? Well, there is the System.Reflection namespace!
Reflection is a program's ability to observe itself at runtime. It's what Unity and Unreal Engine do to allow your classes' fields to be shown in the editor.
We can use the API in this namespace to get information about our own classes, and call methods from them!
Here's what we need:
using System.Reflection; // Gives us the "Info" classes MethodInfo method = monoBehaviour.GetType().GetMethod("Foo"); // Gets a "MethodInfo" from this mb's type with the name "Foo" ParameterInfo ParameterInfos = method.GetParameters(); // Returns an array of "ParameterInfo"s -- this includes their types. object parameters = new object[ParameterInfos.Length]; // We don't know *what* we'll be passing in yet, but we do know how many things we'll want. // If we have parameters to pass in, we call this: method.Invoke(monoBehaviour, parameters); // It doesn't like being passed in an empty object array if there's no parameters, so in this case we use the standard invoke instead. monoBehaviour.Invoke(method.Name, 0);
To implement this, I got a list of the Scene's MonoBehaviours, and for each one that had the GameObject's name, I searched for a matching component. Parameterizing was tricky -- for now I just stuck with intrinsic types. Passing in classes is going to be a whole other rabbit hole. Using this, I was able to create a debug tool that can call functions off of MonoBehaviours! Despite its current limitations to intrinsic types, I am very happy with it so far.
The Caller in action:
Calling from GameObject1:
Calling from GameObject2 with parameters:
- This is a tricky one... You would have to get all of the methods with the name you are querying, and then be able to check if the string arguments you passed in can be cast to any of the possibilities... Its a much longer one.
- Not sure how to start with this one... Perhaps it could be easy to pass in a newly constructed object in the case of things like Vector3... but what about Transforms? They don't have a name and are attached to gameObjects. I would have to, ideally, parse the gameObject's name and its component... determining which component exactly that is ... that doesn't sound fun.
- My system currently looks for the first GameObject that contains the given MonoBehaviour, meaning child objects that share similar names are not distinct from each other. A solution would be to have the user type something like "ParentObject.ChildObject", recognize that there is a child to search for, and continue that way.
- Soooo I know being able to access anything private kind of defeats the point of it being private ... but what if I could? Is it even possible? I don't know, I need to look more into it. What I do know is that this is a debugging tool that uses reflection so I would not feel so bad if it touches something private (probably not so nice words, however.)