Search Unity

Is there a way to make an UnityEvent have a return value?

Discussion in 'Scripting' started by VXPuddiM, Jan 20, 2018.

  1. VXPuddiM

    VXPuddiM

    Joined:
    Jan 23, 2017
    Posts:
    10
    I want to be able to choose a condition in the inspector, just like a delegate (i'm not using delegates because they don't show up in the inspector).
    Something like this would be perfect:
    Code (CSharp):
    1. public UnityEvent someEvent; // Event is choosen in inspector
    2.  
    3. public void Start()
    4. {
    5.     int someVariable = someEvent.Invoke(); // Doesn't needs to be an int especifically
    6. }
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    That's not really how events work.

    Consider if you attached multiple listeners to said UnityEvent... which one's result would you be returning?

    I guess you could say the last one attached (which is how delegates work in C#). But nope, that's not how UnityEvent works, they just forego that oddity all together.

    Events as a design are really just a way for one object to say "I did something". So other objects can react to that thing it had done. The alerting object isn't really supposed to change its behaviour based on who happened to listen to it. It creates a really weird dependency with a loose link between them.

    If you need that sort of dependency. Instead just have a direct reference to that listener. Just have a field of the type of the listener, and attach it directly. Then call the method of importance on it.
     
  3. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Yep, in general, though I'm reminded that I do have a specific class in my General assembly that functions as a priority queue version of an event, and the listeners can consume the event and keep the remainder of the queue from being called afterward. That's not returning a value to the event invoker, but to the event handler itself though (and not returning anything so much as just changing a value in the eventargs).

    If you need a value sent back to the invoker, you should pass in a callback delegate as a parameter to the invoke method. This enables all listeners to "return a value" to wherever the invoker specifies, but it doesn't demand it the way a return type does.
     
    Last edited: Jan 20, 2018
    lordofduct likes this.
  4. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    if you need to implement conditions, you can define an interface ICondition and classes implemented ICondition like below:
    Code (CSharp):
    1. public interface ICondition{
    2.    bool CheckCondition();
    3. }
    4. public class CCondition:Monobehaviour,ICondition{
    5.    [SerializeField]
    6.    float disTH;
    7.   [SerializeField]
    8.    Transform target;
    9.    public bool CheckCondition(){
    10.         float dis;
    11.         //Compute the distance between the gameobject and the target
    12.         return dis<disTH;
    13.    }
    14. }
    Therefore you can call GetComponent<ICondition>().CheckCondition()
    you can define an array of conditions with operators as well
    Also you can write your own custom editor
     
  5. Siccity

    Siccity

    Joined:
    Dec 7, 2013
    Posts:
    255
    For future googlers

    I created an asset similar to UnityEvent but has return values. Usage is exactly the same as UnityEvent, and performance is similar as well. Comes with a default "Condition" that returns a bool. No interfaces or inheritance required.
    34294989-46de127e-e70b-11e7-84f0-99bc4525a8f5.png

    Check out the github repo: https://github.com/Siccity/SerializableCallback
     
  6. Samacvuk

    Samacvuk

    Joined:
    Jan 8, 2017
    Posts:
    30
    Man this is AMAZING, this should be part of unity.
     
    mpolach95, lyzard and Siccity like this.
  7. Drago1

    Drago1

    Joined:
    Jul 29, 2015
    Posts:
    2
    Great decision, but I have problems after building my pc app. When I used yor asset, it didn't launch and I found it was because of more than one instances of serialized class or classes, who's parent is SerializableCallback<>, for example:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Example : MonoBehaviour
    4. {
    5.     [System.Serializable]
    6.     public class IntReturnIntEvent : SerializableCallback<int, int> { }
    7.  
    8.     public IntReturnIntEvent getPower;
    9.     public IntReturnIntEvent getVelocity;
    10. }
    By the way, in Editor ewerything works great. Have any idea, how to fix it?
     
  8. Siccity

    Siccity

    Joined:
    Dec 7, 2013
    Posts:
    255
    Hmm, I've never had a problem with it on mobile platforms, but I'm getting issues on pc build too. Not sure what's causing it though. I'll have a look at it sometime soon.
     
  9. Drago1

    Drago1

    Joined:
    Jul 29, 2015
    Posts:
    2
    Is there any result in problem solving?:)
     
  10. Siccity

    Siccity

    Joined:
    Dec 7, 2013
    Posts:
    255
    No, sorry, i forgot all about it. And now i just tried and can't reproduce it. Can you send your Unity version and possibly the error/line you're getting?

    Edit: Problem should be fixed by now
     
    Last edited: Aug 11, 2020
  11. SlapworthFH

    SlapworthFH

    Joined:
    Jun 16, 2017
    Posts:
    3
    This looks awesome, and is similar to something I've started searching for in Unity. I wonder if you have any thoughts about it?

    I've started wondering if there was a way to have an inspector UI that lets you choose specific VARIABLES on a target property. For example, say I wanted to build a bit of UI that was designed to display an arbitrary float variable, and intended to be cloned in multiple locations on a canvas to show different floats with identical presentation.

    The dream is that I could take one of those UI components, and via an Inspector interface point it towards a float variable on some other component on some other object. That would let the UI be reusable without having to write custom script to reference specific source floats to drive it.

    The SerializableCallback is the closest thing I've found, but works with methods. The dream version of this would use a similar interface to define a reference to variables.

    Does anything like that exist already in Unity? Is there a conceivable path to having such a thing? This seems like it mighe be a breadcrumb on that path: https://forum.unity.com/threads/access-variable-by-string-name.42487/

    Thanks - SlapworthFH
     
  12. cicchi

    cicchi

    Joined:
    Jan 12, 2017
    Posts:
    1
    Hi Siccity,
    thanks four your work.
    I downloaded SerializableCallback code from the link you provided and I followed the instructions to create a serializable condition. Like this:

    Code (CSharp):
    1. [Serializable]
    2. public class SerializableCondition : SerializableCallback<bool> { }
    It works perfectly in Unity, but if I make a build and install it on an Android phone, the game crashes when it tries to load the prefab that contains a SerializableCondition member.

    It's pretty easy to reproduce the issue.
    1) Create a game object
    2) Create a MonoBehavior script having the previous SerializableCondition as a field
    3) Assign a boolean function to the SerializableCondition in the Inspector
    4) Make that game object a prefab
    5) Create an instance of that prefab when loading your Main Scene...and Boom. Game crashes.

    So if everyone is interested, be aware that at the moment it does not work on a mobile device.
     
  13. Jiaquarium

    Jiaquarium

    Joined:
    Mar 22, 2020
    Posts:
    45
    You could create an event object that gets passed around and modify its properties via your event handlers/subscribers, and then it could get read by your invoker for changed values.

    Code (CSharp):
    1. myObj modifiableObj = new myObj();
    2. // allow eventHandlers to modify the object
    3. someEvent.Invoke(modifiableObj);
    4. int i = modifiableObj.publicProp;
    5.  
    6. // define custom Unity Event
    7. public class MyEvent : UnityEvent<myObj>
    8. { }
    9.  
    10. // in the eventHandler which you choose from the inspector
    11. public void OnSomeEvent(myObj modifiableObj)
    12. {
    13.     modifiableObj.publicProp = 1; // or whatever int you need
    14. }
    Although probably not the cleanest way, since you still run into the problem if you have multiple event handlers for the event.
     
    Last edited: Jul 14, 2020
  14. msue2

    msue2

    Joined:
    Jun 2, 2017
    Posts:
    9
    For a slightly different use case, a simple C# property can be hooked up to a UnityEvent.

    Declare, for example, a float UnityEvent. That allows to use the setter aspect of a property assigned as event target.
    Code (CSharp):
    1. public class UnityEventFloat : UnityEvent<float> { }
    2. public UnityEventFloat floatEvent;
    But how to also access the getter of the target ?

    Solution:
    Code (CSharp):
    1. System.Reflection.PropertyInfo targetProperty = null;
    2. object target = valueCallback.GetPersistentTarget(0); // returns Monobehaviour
    3. string method = valueCallback.GetPersistentMethodName(0);
    4. if (target!=null && method!= null)
    5. {
    6.   targetProperty = target.GetType().GetProperty(method.Replace("set_",""));
    7.  
    8.    if (targetProperty != null)
    9.      float value = (float) targetProperty.GetValue(target);
    10. }
    EDIT: Added call to method.Replace ("set_","")
     
    Last edited: Aug 10, 2020
    StellarVeil and ratking like this.
  15. treborguy

    treborguy

    Joined:
    Apr 17, 2014
    Posts:
    30

    I am SO glad I spent all day searching for this. I think you just changed my life. THANK YOU!
     
    Siccity likes this.
  16. Radivarig

    Radivarig

    Joined:
    May 15, 2013
    Posts:
    121
    Thank you! Now I can finally decouple components properly. Also, thanks for making it a package dependency, very easy to add to a project.
     
  17. Wilhelm_LAS

    Wilhelm_LAS

    Joined:
    Aug 18, 2020
    Posts:
    55
    The callback is not showing in Editor. Whats the wrong i did?
     
  18. travismillerdev

    travismillerdev

    Joined:
    Feb 28, 2022
    Posts:
    3
    Bump with a solution that doesn't involve reflection or anything nasty.


    Code (CSharp):
    1.  [Serializable]
    2. public class GetDepth : UnityEvent<GameObject, Action<int>> { }
    3.  
    4.  
    5. //Here is where the unity event would trigger the method (probably in a different script! same name error in same script):
    6. public void GetDepth(GameObject target, Action<int> depthCallback)
    7. {
    8.      var host = FindNode(target.transform);
    9.  
    10.      if (host.parent == null)
    11.      {
    12.          depthCallback(0); //this is the callback
    13.      }
    14.      else
    15.      {
    16.          depthCallback(host.depth); //this is the callback
    17.      }
    18. }
    19.  
    20.  
    21. //finally this is where you want the callback value when used:
    22. void Blah() {
    23. int depth = 0;
    24. GetDepthOfTreeObject.Invoke(m.gameObject, (val) => { depth = val; });
    25.  
    26. //depth will be set appropriately after it is invoked (assuming the event actually ran the action)
    27. }
    28.  
     
    Ryiah and CodeRonnie like this.
  19. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    That's a clever way to avoid reflection. Points for that, but I would probably still use this approach with a custom type and property drawer like Siccity did that only supports a single callback. An event can have any number of observers, so you can get any number of return callbacks invoked in response to firing an event. I see a single return value from a single callback as generally more useful. However, there are cases where having all observers return a callback response could be useful.

    Also, you created a closure with the lambda sent out with the event invocation. That's going to have bad performance and allocate extra garbage memory every time you invoke the event. It would be better to just explicitly create a method, probably a private method so you know it's always in response to your event invocation. Then, explicitly cache an Action<int> instance pointing to that method, stored as a member of the class that is invoking the event. Then pass the cached Action<int> instance to the event so it's not generating a new one and creating garbage every time the event fires.

    I think your example could also be simplified to not include any of that GameObject target, node, host, parent, depth stuff so that the important idea is more clearly conveyed. All of the other stuff seems really specific to something you were working on and makes the important idea less clear.
     
    Last edited: Jan 9, 2024
  20. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    Ahh, but now that I'm thinking out loud, I realize that creating a custom type and property drawer is where people turn to reflection and custom serialization.

    Maybe you could make a custom type that still uses UnityEvent under the hood for it's nice editor integration and serialization capabilities, but with a custom property drawer that limits to one observer. Of course, custom property drawers don't 100% guarantee that multiple observers will never be assigned. I suppose you could just set a member bool before invoking the event, then toggle it back in response to the first return callback, and after the event in case of no observers. Then only the first responder is considered valid, but it still seems a bit unclean. I like anything simple that avoids performance costs though.

    It would be neat if Unity would just integrate a type like UnityEvent that only accepts a single observer so you could attach a Func with a return value.
     
    Last edited: Jan 9, 2024