- Home /
Suggested workaround for SendMessage bug?
I ran into a problem with SendMessage. I was trying to make a message based targeting system for a question here at answers.unity3d.com. The problem is that when I attempt to pass a null parameter value into SendMessage, I get this quite unexpected message:
Failed to call function Test of class SendMessageReceiver Calling function Test with no parameters but the function requires 1.
- UnityEngine.Component:SendMessage(String, Object)
- SendMessageSender:Main() (at Assets/SendMessageNullTest/SendMessageSender.js:1)
I want to be able to send null values to clear out the current target but obviously this won't work for now. I sent a bug report to Unity3D. For the sake of simplicity, which workaround do you think is best, and why?
- Add a new func called SetTarget that doesn't accept any params and handle null case.
- Add a new func called ClearTarget that doesn't accept any params thats called when target is null.
- Add a new type called TargetEvent that provides the value and pass that to SendMessage instead.
- Other workaround I haven't thought of?
Below is the culprit code.
SendMessageSender.js
SendMessage("Test", null);
SendMessageReceiver.js
// This function isn't even called...
function Test(actual : GameObject) {
if (actual == null)
print ("Test PASSED!");
else
print ("Test FAILED! (Expected null)");
}
By the way, the question I answered is here: http://answers.unity3d.com/questions/47753/choosing-from-multiple-characters/49278#49278
$$anonymous$$ake an actual "null" object and pass that through in place of null itself? =)
Answer by Skjalg · Mar 29, 2011 at 08:28 PM
I reproduced the error with these c# scripts:
using UnityEngine;
public class TestSendMessage : MonoBehaviour { public GameObject SendMessageTarget;
public void Start()
{
if (SendMessageTarget)
{
SendMessageTarget.SendMessage("Test", (GameObject)null);
}
}
}
and
using UnityEngine;
public class SendMessageReceiver : MonoBehaviour { public void Test(GameObject gameObject) { if (gameObject == null) { Debug.Log("passed"); } else { Debug.Log("failed, expected null"); } } }
The workaround I would use is to have two methods that you send messages to:
SetValidTarget(GameObject target);
ClearTarget();
It will make your code easier to read. Consider these two scenarios:
private void ProperWay() { if (SendMessageTarget) { SendMessage(SendMessageTarget, Target); } }
private static void SendMessage(GameObject sendMessageTarget, GameObject target) { if (target) { sendMessageTarget.SendMessage("SetValidTarget", target); } else { sendMessageTarget.SendMessage("ClearTarget"); } }
against
private void ImproperWay() { if (SendMessageTarget) { TargetWrapper wrapper = new TargetWrapper(Target); SendMessageTarget.SendMessage("SetValidTarget", wrapper); } }
public class TargetWrapper { public GameObject Target;
public TargetWrapper(GameObject target)
{
Target = target;
}
}
Try to imagine reading this code in a year or two.
The first way will immediately tell you that if you have the SendMessageTarget you will send a message to it, and on closer inspection you get a glimpse that if the gameobject is null you will clear the target on the other end (readability).
The second way however, will only tell you that if you have the SendMessageTarget you will send a message to it, not knowing if the null possibility is handled on the other side or not, only that you wrap it in a class. (By this point in time you might have even forgotten why you have wrapped it in a class, thinking yourself stupid for not removing it earlier, since it might be some left over code, you will remove that wrapper only to experience the same error you have now and have wasted a good 15 minutes.
*On a side note:
I would also suggest that you make an interface, then have all your classes that has a target implement this interface, then instead of using gameObject.SendMessage you can use gameObject.getComponents< TargetInterface >()
and loop through that list and using the methods provided by the interface to do what you wish, since I have found that provides a significant speed increase to the fps.*
You actually can't do GetComponents(). GetComponents(typeof(Interface)) will work, but it'll spam error logs at you at runtime for objects not implementing the interface
I fully agree and didn't notice at first anyone replied when I came to the same conclusion and actually answered my own question.
Ah yes, you are correct about the interface. I normally just make an abstract class that inherits from monobehaviour and the interface, then do the getcomponents on that abstract class.
Answer by Mike 3 · Mar 29, 2011 at 08:18 PM
Weird hack to get around it:
var go = GameObject.Find("GameObject which doesn't exist");
SendMessage("Test", go);
This works because GameObject.Find will always return an object, it just evaluates to null with the overloaded boolean operators
My bet is that you could store a reference (a static one maybe?) to the above gameobject reference and use that for the calls
Anyway, aside from that, I also like the idea of passing a wrapper object which stores your value
Either way - bug report it, that's fairly bad
I would not recommend using GameObject.Find since it is a fairly slow procedure, also, even though passing a wrapper object is a good idea it will not only be slower than sending a different message (i.e ClearTarget), since it needs to pass a value to the object it will also reduce the readability of the code.
As I said - do it once (in the entire app). Passing a wrapper object will be a LOT faster than calling a second object. Two reflection calls are far slower than one reflection call passing a reference
Funnily enough $$anonymous$$ike, that doesn't work in UNity5 if you're passing an Action. Who knows!
Answer by DaveA · Mar 29, 2011 at 08:34 PM
I would make a ClearTarget() function. Interesting note though from Skjalg, I would have thought that SendMessage did that same thing, and be very optimized about it as well, but if not, that's a good idea.
I think it bares some further research. If someone would take it upon themselves to research it I know I would be very glad to read the results :)
Your answer
Follow this Question
Related Questions
Remmember the spawner 2 Answers
Using Struct to SendMessage - Number of parameters error. 1 Answer
Rigidbody and collider Sendmessage delay error 1 Answer
SendMessage illogical errors 0 Answers