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)");
}

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 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.*

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.