Cannot pass UnityEvent<string> parameter value when invoking the callback

Hi,

I am trying to set an UnityEvent to invoke a function with a parameter of type string, which is in a script attached to another game object, but the value of the string that reaches the invoked function OnPlayerName is null:
(I am using unity 2021.3.24f1 on ubuntu 22.04)

//Attached to GameObject: UI
public class StartScreen : MonoBehaviour {
public UnityEvent<string> playerName;

    private void Start() {
        playerName?.Invoke("aName");
    }
}

//Attached to GameObject GameManager
public class SetupPlayer : MonoBehaviour {

    public void OnPlayerName(string s) {
        Debug.Log(s);   //s="" rather than "aName"
    }
}

In the inspector the UnityEvent StartScreen.playerName is initialized to the object GameManager script SetupPlayer.OnPlayerName(string s).

OnPlayerName function is called but the value of the string is lost, s is empty s=“” rather than s=“aName”.

However moving the OnPlayerName(string s) function to the same class StartScreen it gets the correct value.

//Attached to GameObject: UI
public class StartScreen : MonoBehaviour {
public UnityEvent<string> playerName;

    private void Start() {
        playerName?.Invoke("aName");
    }

    public void OnPlayerName(string s) {
        Debug.Log(s);   // s = "aName"
    }

}

It is NOT possible to pass parameter values using UnityEvents from one game objects to another?

I did some debugging.
If the invoker and the invoked are in the same class, the string argument is passed fine, because it invokes using lib function:

internal class InvokableCall<T1> : BaseInvokableCall
    //...
    Invoke(T1 args0){
      if (!BaseInvokableCall.AllowInvoke((System.Delegate) this.Delegate))
        return;
      this.Delegate(args0);
    }
}

But if they are in different game objects it uses another lib function to invoke, which overrides the one above, and does not pass the right arg0 argument but this.m_Arg1:

internal class CachedInvokableCall<T> : InvokableCall<T> {
    //...
    public override void Invoke(T arg0) => base.Invoke(this.m_Arg1);
}

Note: The same happens when the OnEndEdit event of an InputField is set to the SetupPlayer.OnPlayerName(string s) method. The value of the text in the input field does not reaches the event function OnPlayerName(string s), whether it is attached to the InputField or to another game object.

You need to use a class for this

UnityEvents set in the inspector make a distinction between Dynamic and Static function calls. When selecting a target function of an event, the drop-down menu is split in two, Dynamic target functions at the top, Static target functions at the bottom. Note that some functions appear in both the Dynamic and Static section.

This lets you choose if you want the receiving function to get the dynamic argument passed to Invoke, or to set a static argument in the inspector, which will override the argument passed to Invoke. Basically, when you choose a target function in the inspector, you need to make sure you pick the right variant of that method. Only when there isn’t an input field below the function drop-down will the dynamic argument be used, if there’s a input below it, that value (even if empty / none) will override the dynamic argument.

Behind the scenes, the value of PersistentListenerMode determines wether the dynamic or static argument is used. If it’s EventDefined, the BaseInvokableCall implementation will be used, if it’s any of the static argument values, CachedInvokableCall will be used instead.

This hasn’t been true in a while. I don’t remember exactly when it changed, but at least from 2021.3, fields with a generic type serialize just fine.

Owh weird, for me it did need the class in 2021.3.

Thanks @Adrian , the issue was indeed caused by calling the static function, rather than the dynamic.
@DevDunk I can confirm that a generic type is serializable at least from version 2021.3.25.

For anyone else who might be also struggling with Unity events, here is a great tutorial:

https://gamedevbeginner.com/events-and-delegates-in-unity

I am sorry for this late update, but at I hadn’t thought about this issue for a while.
At the time I solved it by using Action instead of UnityEvent, and the GOF Observer pattern to create an Event Manager.

What a confusing UI mechanism, listing the functions twice in the list and doing something completely different depending on which item in the list you choose. (To pass the original parameter you choose the first one).

It does kinda beg for a better UI. I’m not sure what or how, but it took me a long time to really “get” what was going on here as well.

Here are my cribsheets on this stuff:

Unity button onclick function script callback gameobject notes:

https://discussions.unity.com/t/741309/6

For sliders and dropdowns and other value-returning UI elements:

https://discussions.unity.com/t/855879/2

And passing “more interesting” things when buttons are pressed:

https://discussions.unity.com/t/883461/8

@ChrisP999 It is even more confusing that the Dynamic has just the name of the method, without the arguments list, looking like a property, while the Static has the argument list, looking like the method we are trying to reference.
That parameter list led me to wrongly choose static the first time I used this.

The static version gets the argument value from the inspector, rather than from the runtime code. This is noticeable from the additional field in the Inspector:

9544789--1348231--upload_2023-12-23_6-10-10.png

As shown by @Kurt-Dekker here:

We need to reference the GameObject where the script is attached to, rather than the script itself.
And again confusingly, when the gameobject is defined initially it shows the cube icon for an object, but after choosing the callback method it changes to the c# script icon, as in the above figure.

Edit: It also accepts a ScriptableObject, and a Prefab. Since GameObject and ScriptableObject are subclasses of System.Object to accept both the type of this field must be a System.Object. But this way it accepts anything.

If willing to change this behaviour I guess we could rewrite UnityEventDrawer code:


9544789--1348225--upload_2023-12-23_5-58-56.png
9544789--1348225--upload_2023-12-23_5-58-56.png

I did some digging in the source code:

It accepts a GameObject and also a ScriptableObject. Since GameObject and ScriptableObject are subclasses of System.Object, to accept both, the type of this field must be System.Object.
But this way it accepts anything.

The base classes can be found in the source code, files:

I don’t think that it’s especially confusing. It’s just something you have to wrap your head around.

The UnityEvent has a particular signature and the usual assignable methods have to match that signature. Displaying all the argument types wouldn’t make much sense since unity events could have many arguments.

The static section completely ignores the arguments of the event and treats it like a parameterless event. Here you can only select methods that have no arguments or exactly one that unity can serialize in the inspector. So here it is necessary to know the argument type.

I’ve explained most of that over here

@Bunny83 patterns matter. When we define a method with an int parameter, we will look for that in the list. If there are 2 versions, one with the int parameter and the other without it we will probably go to the former.

I did have to work my head around it. The question is that I should not have to do it.

Computers were cretead to simplify our lives, not to make my head spin around whatever another head thought while devekoping the UI.

I really don’t understant why the static version lists methods with the parameters and why the dynamic don’t, just showing the parameters at the head of the list.

Because the dynamic list only allows methods with the matching type of the event (for example int in the screenshot above) but the static list allows any type (which is supported in the editor). You could give 2 methods the same name, but different parameter types, it’s valid C# code, so the static list has to show the parameter type otherwise you wouln’t know which version of the method you call.

The reason why it would not make sense to show the parameter types for the normal / dynamic methods is that the UnityEvent supports up to 4 arguments. In that case the name / menu item could get way too long.

The first dynamic section are simply all methods that match the signature of the event. The second static section are just the additionally supported methods which can be hooked up to any UnityEvent if they only have none or one argument. Since unity supports several argument types that can be serialized, it is necessary to show the argument type to uniquely assign a certain method.

Note that you could have a UnityEvent like this:

public UnityEvent<Dictionary<string,List<Player>>, string, NetworkMessage<PlayerJoinedEvent>, uint> onPlayerJoined;

Imagine how the selection dropdown would look like if unity would list all the arguments. The dynamic section only lists merhods that are compatible with this event.

@R1PFake @Bunny83
ok, a generic parameter list can be too long to show in the dynamic parameter list section,
and the static parameter list helps choosing the right polymorphic method.

However the use for static parameters in the inspector is puzzling me.
If it is needed to pass some info along the event, hence the parameters, why would it be useful to define a static value to pass all the times the event is triggered in the inspector?
Well maybe for testing purposes.

But even more puzzling is why we would need to hook a callback with a different parameter type?
if there is a definition of a UnityEvent why would we need to hook it to callbacks that take an int or a float?

9547192--1348825--upload_2023-12-25_10-44-16.png

I do agree that I don’t see the point of passing a static value instead of using the actual value which was passed for this event, but I have a guess why they implemented it this way:

If you use a simple event, with no event args, then you can still use this feature to call a method, with arguments and pass some static values to this method.
Here is a random example, let’s say you have 3 buttons, they have a click event, which has no arguments, but you can still call methods with a static value, so you could for example select a method with an int parameter and then “hard code” the button index in the editor, then the calling mehod would “know” which of thse buttons was clicked, by checking the passed int value in code.

So I guess they just added the same feature to the generic event versions, because it was already implemented for the non generic versions. But that’s just a guess. I personally always use the dynamic feature in this case, to get the actual values and not some static editor value.

I could maybe also be useful if you add multiple methods to the event, so for example first you want to call a method which uses the actual event parameter and then you want to disable a object, so you could add “enabled” to the list and select false as static parameter

@R1PFake yeah maybe there are some use case scenarios.

Anyway, I am noving out of the inspector as much as I can. I am expecting to use mostly Actions in the near future

Well I just looked at the screenshot and solved my problem. I was scratching my head for more than an hour and finally found this answer. Thanks a ton for the answer