Unity Events, passing multiple arguments

I’ve been using Unity Events for a few weeks now, and whilst they are useful I was finding it restrictive not having an obvious way to pass arguments and not being able to tell which object sent the event. I knew there must be a way around the first problem as the UI system has events with a single parameter, but I to achieve both my goals I need to be able to pass two parameters.

After a bit of playing around I came up with this solution which seems to do the job perfectly, and as far as I can see allows the passing of an arbitrary amount of data.

To run this example you will need to set up a scene with a GameObject for the player, (I chose a capsule), and attach the following script to it.

using UnityEngine;
using System.Collections;
using UnityEngine.Events;

public class Player : MonoBehaviour {
    public HealthChangedEvent healthChanged;

    public int health {
        get;
        private set;
    }

    // Use this for initialization
    void Start () {
        SetHealth(100);
    }

    // Update is called once per frame
    void Update () {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            SetHealth(health-10);
        }
    }

    void SetHealth(int amount)
    {
        health = amount;
        healthChanged.Invoke(new HealthChangedEventArgs(gameObject,health));
    }
}

[System.Serializable]
public class HealthChangedEvent : UnityEvent<HealthChangedEventArgs> {}

public class HealthChangedEventArgs
{
    public object sender;
    public int health;

    public HealthChangedEventArgs (object sender, int health)
    {
        this.sender = (object)sender;
        this.health = health;
    }
}

All this does is set the player’s health to 100 when it starts, and then reduces it by 10 every time you press space. It also sends an event whenever the health value changes.

To test the event, you can add a canvas with a text component to the scene, and add the following script to the canvas.

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class HUDCanvas : MonoBehaviour {
    [SerializeField] Text healthText;

    public void OnPlayerHealthChanged(HealthChangedEventArgs e)
    {
        healthText.text = "GameObject " + ((GameObject)e.sender).name + " updated it's health to " + e.health.ToString();
    }
}

In the inspector you’ll need to drag your Text component onto the healthText field.

Lastly you need to hook up your Player HealthChanged event, by selecting the Player gameobject in the inspector, clicking the + button on the HealthChangedEvent, then drag your canvas into the new event and select OnPlayerHealthChanged from the very top of the list of function available from the HUDCanvas script.

When this runs it will change the text to reflect the player’s health whenever it changes and also display the name of the gameobject that invoked the event.

The EventArgs class can be customised to pass whatever data you want when the event is invoked. I hope somebody else finds this at least a bit useful.

Let me know if you have any questions or need help getting it working.

1 Like

It may make more sense to make event args a struct instead of a class since its just there to pass data so dosnt need to be mutable, having it immutable might even cause bugs down the road. Not to mention if its a struct its allocated on the stack and not the heap which is also a benefit.

I used a class for the event args, because that’s how .net does things, and there is a potential performance hit if you pass a large amount of data in a struct. There’s a discussion here, Any advantage of EventArg as class or struct? - C# / C Sharp regarding this, but there are plenty others to be found.

I also prefer using classes over structures as a general rule, as it allows for inheritance. I’m also struggling to see how using a class could introduce bugs if used in the way my example demonstrates.

But using classes over structures seems to come down to a matter of personal choice in the end; I prefer classes, but it could easily be converted to use a structure and should work the same.

Have you considered using the 1, 2, 3, or 4-argument versions of UnityEvent?

6 Likes

I wasn’t actually aware of those variants, documentation of the Unity Event system being somewhat sparse currently. Obviously that would be the best way to approach things unless you need to pass more than 4 parameters, which is fairly unlikely.

However if I hadn’t made my original post I doubt I would have gained the information you provided so soon, therefore it wasn’t a wasted effort, and the info is here for anyone else to find too now. :slight_smile:

2 Likes

Here’s a trivial example, 2018, for anyone googling here

using UnityEngine;
using UnityEngine.Events;

using System.Collections;
using System.Collections.Generic;


// step 1, you have to make a public class, for the whole project
// which expresses the style of arguments you want
// in this example, two floats.
// this is trivial to do - just this line of code:

[System.Serializable] public class XYEvent : UnityEvent<float, float> { }

public class Drawing : MonoBehaviour // for example, some sort of drawing class
{
    // step two, have an inspector variable like this:
   
    public XYEvent pointUpdates;
   
    // in your OTHER class, which RECEIVES the information,
    // simply have a class which takes two floats.
    // drag that other object to this inspector variable.
   
   
    void Update()
    {
       
        // for example, some sort of drawing code ..
       
        // step 3, call the event when you want to:
           
        // send the latest position to the other class:
        pointUpdates.Invoke(currentPosition.x, currentPosition.y);
    }
}

and in the other class,

    public void AnotherPointTouched(float x, float y) {
     
        Debug.Log("example " + x + " " + y);
    }

It’s that easy.

2 Likes

Fattie not sure how this works
I tried to create and run the codes you posted but is not working
pointUpdates.Invoke(currentPosition.x, currentPosition.y);
I don’t get it to show the inputs in the inspector (could you pls insert an example code that works ?and pls name them is is important like the Drawing.cs)

hi @mexicano

drag that other object to this inspector variable.

that’s the secret !

Hey @Fattie ,

As @mexicano said it’s a bit weird that I can’t set the properties inside the event statically. I tried doing this without creating a new class by just using UnityEvent<int,int> as a public datatype, but it yielded nothing.
How would I be able to set these two integers from the inspector?


As you can see that when I reference the script that has a public void <int, int> it only sees it as a dynamic function. I want to use this as a static set parameter from the inspector.

1 Like

hi @CoderNation1 - I may misunderstand you, but, unity events are not for that purpose and it would make no sense to do so.

What you drag there is another script which will get “contacted” when the event fires. Does that explain it?

Quick tutorial! → c# - Simple event system in Unity - Stack Overflow

Hi!
Is it possible to mix dynamic with static parameters?
Let’s suppose I would like to make a generic script that handles “OnTriggerEnter” and calls SetAnimationTrigger on the object that entered the trigger. That’s all doable via hard-coded scripts, but what about:

  • “For any object that enters THIS trigger, Invoke the unity event set on the inspector that targets (dynamically) an AnimationController and calls SetTrigger(animationName) (statically, i.e. the name is set on the inspector) on it”

Hi @Alejandro-Martinez-Chacin , sure no problem (unless I misunderstand you)

TriggerArea.cs … it contains a Unity Event which

you set in the Inspector to call your “tank” game object, which has a script

FancyAnimationsWhenCrossingBorders.cs

and that one has a string inspector variable which you can set to “spin” “dance” “leap” and so on, which becomes the animations.

FancyAnimationsWhenCrossingBorders would then do that animation.

Is that what you mean?

If not just describe an example :slight_smile:

2 Likes

Hi, I totally missed the reply to this. Wow, more than a year later. Excuse me that…
Yeah, that brings the intended outcome but it lacks inspector flexibility. Expanding upon your same example:

  • TriggerArea contains an UnityEvent that gets called whenever an object enters it.
  • Any object that enters, if it has an animator, gets called SetTrigger with the animationTriggerName set on the UnityEvent inspector. So a tank, a trooper, a car, etc that gets inside the trigger will get called (if it has an animator that is).
  • It would be the equivalent of:
string animationTriggerName = "Jump";
void OnTriggerEnter(GameObject other)
{
    var anim = other.GetComponent<Animator>();
    if (anim)
         anim.SetTrigger(animationTriggerName);
}

Thinking more about this, one way maybe could be to have a ‘middle man’ proxy, that’s the one that we setup on the UnityEvent inspector inside TriggerArea.cs but I don’t know if it is possible to change the event’s target at runtime, it’s possible maybe with “BetterEvents” though.

The idea would be to not need many middle classes, there wouldn’t be a need for FancyAnimationsWhenCrossingBorders nor targetting the tank itself on the inspector.

It wouldn’t be that hard to just do utility scripts that do the especific thing: SetAnimationTrigger, change Image, change Scale, etc etc… but sometimes it’s just convenient to just wire functionality on the inspector.

  • UnityEvent => target => Sprite; method to call => image;
  • The above could be used to change the sprite image of any object that comes in the trigger area that has an sprite component.

So on and so forth.
Maybe this is mostly an exercise in curiosity though, as debugging could become potentially painful with too many detached functionality wired around in inspectors.

Thanks a lot for the feedback, sorry again to come back so late…

Hey, everyone… Hope I’m not too late

After suffering with Unity Event and multiple arguments, I’ve made this

Events 2.0 for Unity

Cheers

2 Likes

This looks great! I’m switching to a Component Based Approach in my code, which I find is incredibly fragile if not coupled with events, and lots of 'em. Sometimes, I even want to invoke an event of one component from an event of another component (like a minigame OnEnd event that can either be triggered from a button or from when it is completed). Can Events2 handle that?

@CaseyHofland - hmm, we do the most complicated imaginable stuff with Events, and always just use Unity’s events. Which are 100% reliable. The examples you mention are trivial and no problem. I would use caution in taking any approach other than ordinary old, totally reliable Unity events. Good luck!

One thing to keep an eye on with UnityEvents is garbage allocation. Jackson Dunstan wrote a good article about it. First-time dispatches from UnityEvents generate garbage. This is mainly just a concern if you’re pooling objects that invoke UnityEvents since deactivating and reactivating those objects will cause the UnityEvents to allocate that first-time dispatch garbage.

1 Like

@Fattie but then how do you do a method with multiple parameters? How do you do one with multiple parameters of which you want the first one to be dynamic? What about enums? If you are able to do these things, I’d love to know how!

Btw I hope this doesn’t read as a “proof it” kind of message, you’ve genuinely peaked my curiosity. If the answer is “Interpreter-scripts” that’s ok, but then I’d still like to know how you keep these scripts maintainable.

If you assign UnityEvent handlers in code, you can pass multiple parameters (or do whatever you want, really) with lambda. Example:

GetComponent<Button>().onClick.AddListener(() => { SomeFunction(arg1, arg2, arg3); });

Nowadays I prefer to assign handlers in code whenever possible, leaving Inspector assignments only for tail-end level designers who don’t want to touch any scripts.

2 Likes

I’d like to clear up some confusion about the UnityEvent classes. As it has been mentioned a couple of times in this thread there are two ways how you can link up methods in a UnityEvent. Either as a dynamical method in which case we can use the generic UnityEvent base classes with up to 4 generic arguments. The second way is to use static parameters which you define directly where you hook up the event in the inspector. However when it comes to static parameters UnityEvents only support a single parameter. That’s because those parameters need to be serialized inside the UnityEvent class itself. Unity uses a seperate serializable class called ArgumentCache which is a class that has a seperate member for every possible argument type. So only one of them is actually used.

Of course theoretically it would have been possible to implement several arguments, however it would have made the whole class construct much more complex. The UnityEvent class already has a quite deep nesting depth (and Unity’s serialization system is limited to 7 nesting layers).

To answer the question if dynamic and static arguments can be mixed: No, just no ^^. This would require support for multiple serialized arguments which we don’t have and even if we had them it would complicate the dispatch a lot. They already jumped through several hoops to get the type safe dispatch properly implemented.

Finally a UnityEvent has seperate invokation lists internally. One persistent call list which holds the serialized function calls which you can assign in the inspector and a seperate one for runtime assigned methods.

Of course it would be possible to create a seperate component or ScriptableObject to handle the dispatch with a dynamic UnityEvent. So that seperate component could handle the serialization of the parameters and your own dispatch could even handle mixing of dynamic and static arguments as you wish. So it could serve as an adapter / proxy between UnityEvents. It’s not super elegant but would work.

A somewhat similar approach is proposed by Ryan Hipple in this popular Unite 2017 talk. It’s not about the arguments but the overall concepts of using a proxy component to hook up the events.

5 Likes