Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Unity Events, passing multiple arguments

Discussion in 'Scripting' started by Munchy2007, Apr 30, 2015.

  1. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    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.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using UnityEngine.Events;
    5.  
    6. public class Player : MonoBehaviour {
    7.     public HealthChangedEvent healthChanged;
    8.  
    9.     public int health {
    10.         get;
    11.         private set;
    12.     }
    13.  
    14.     // Use this for initialization
    15.     void Start () {
    16.         SetHealth(100);
    17.     }
    18.  
    19.     // Update is called once per frame
    20.     void Update () {
    21.         if(Input.GetKeyDown(KeyCode.Space))
    22.         {
    23.             SetHealth(health-10);
    24.         }
    25.     }
    26.  
    27.     void SetHealth(int amount)
    28.     {
    29.         health = amount;
    30.         healthChanged.Invoke(new HealthChangedEventArgs(gameObject,health));
    31.     }
    32. }
    33.  
    34. [System.Serializable]
    35. public class HealthChangedEvent : UnityEvent<HealthChangedEventArgs> {}
    36.  
    37. public class HealthChangedEventArgs
    38. {
    39.     public object sender;
    40.     public int health;
    41.  
    42.     public HealthChangedEventArgs (object sender, int health)
    43.     {
    44.         this.sender = (object)sender;
    45.         this.health = health;
    46.     }
    47. }
    48.  
    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.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4.  
    5. public class HUDCanvas : MonoBehaviour {
    6.     [SerializeField] Text healthText;
    7.  
    8.     public void OnPlayerHealthChanged(HealthChangedEventArgs e)
    9.     {
    10.         healthText.text = "GameObject " + ((GameObject)e.sender).name + " updated it's health to " + e.health.ToString();
    11.     }
    12. }
    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.
     
    Last edited: Apr 30, 2015
    kobyle likes this.
  2. passerbycmc

    passerbycmc

    Joined:
    Feb 12, 2015
    Posts:
    1,739
    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.
     
  3. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    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, http://bytes.com/topic/c-sharp/answers/230905-any-advantage-eventarg-class-struct 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.
     
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,528
    Have you considered using the 1, 2, 3, or 4-argument versions of UnityEvent?
     
    Digineaux, mopthrow, Bunny83 and 3 others like this.
  5. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    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. :)
     
    TaleOf4Gamers and ecarbin like this.
  6. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
    Here's a trivial example, 2018, for anyone googling here

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7.  
    8. // step 1, you have to make a public class, for the whole project
    9. // which expresses the style of arguments you want
    10. // in this example, two floats.
    11. // this is trivial to do - just this line of code:
    12.  
    13. [System.Serializable] public class XYEvent : UnityEvent<float, float> { }
    14.  
    15. public class Drawing : MonoBehaviour // for example, some sort of drawing class
    16. {
    17.     // step two, have an inspector variable like this:
    18.    
    19.     public XYEvent pointUpdates;
    20.    
    21.     // in your OTHER class, which RECEIVES the information,
    22.     // simply have a class which takes two floats.
    23.     // drag that other object to this inspector variable.
    24.    
    25.    
    26.     void Update()
    27.     {
    28.        
    29.         // for example, some sort of drawing code ..
    30.        
    31.         // step 3, call the event when you want to:
    32.            
    33.         // send the latest position to the other class:
    34.         pointUpdates.Invoke(currentPosition.x, currentPosition.y);
    35.     }
    36. }
    37.  
    38.  
    and in the other class,

    Code (CSharp):
    1.     public void AnotherPointTouched(float x, float y) {
    2.      
    3.         Debug.Log("example " + x + " " + y);
    4.     }

    It's that easy.
     
    teairis and matthewawilliams366 like this.
  7. mexicano

    mexicano

    Joined:
    Nov 12, 2013
    Posts:
    23
    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)
     
  8. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
    hi @mexicano ..

    >drag that other object to this inspector variable.

    that's the secret !
     
  9. CoderNation1

    CoderNation1

    Joined:
    May 23, 2016
    Posts:
    2
    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.
     
  10. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
    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! -> https://stackoverflow.com/a/36249404/294884
     
  11. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    144
    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"
     
  12. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
    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 :)
     
  13. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    144
    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:

    Code (CSharp):
    1. string animationTriggerName = "Jump";
    2. void OnTriggerEnter(GameObject other)
    3. {
    4.     var anim = other.GetComponent<Animator>();
    5.     if (anim)
    6.          anim.SetTrigger(animationTriggerName);
    7. }
    8.  
    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...
     
  14. ggpereira

    ggpereira

    Joined:
    Mar 30, 2015
    Posts:
    9
    Last edited: Jun 19, 2020
  15. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    548
    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?
     
  16. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
    @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!
     
  17. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,528
    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.
     
    CaseyHofland likes this.
  18. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    548
    @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.
     
  19. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,528
    If you assign UnityEvent handlers in code, you can pass multiple parameters (or do whatever you want, really) with lambda. Example:

    Code (csharp):
    1. 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.
     
    Munchy2007 likes this.
  20. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    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.
     
    Dominus12, CaseyHofland and TonyLi like this.
  21. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    548
    I've watched the whole thing and am extremely glad that I did. I've been looking at architectures for Unity a lot lately and 'was' going to place my bets on dependency injection, but it always seemed like this "we are not a Unity solution" solution. I will definitely look into this architecture and watch a whole lot of talks more on it, thank you sir for spinning this forum the way you did!