Search Unity

UnityEvents in a Prefab (via Script?)

Discussion in 'Scripting' started by Cutty_Flam, May 23, 2017.

  1. Cutty_Flam

    Cutty_Flam

    Joined:
    Jun 9, 2016
    Posts:
    15
    Right now, I'm instantiating a UI Button, and when it's clicked, it needs to call a function in another script. Usually I would just set the callback in the inspector, but I can't do that since the Button is a prefab. How in the world would I do this?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    So I don't use UnityEvent, but instead my own Trigger system. But it's essentially the same thing as a UnityEvent.

    When we need such things... we create what we call 'ProxyScripts'. Ours are done with reflection, but you can just adhoc it.

    Basically the Proxy has the same methods as the script you're attempting to access, but just forwards them along.

    So say you have a script like this:
    Code (csharp):
    1.  
    2. public class Foo : MonoBehaviour
    3. {
    4.    
    5.     public void DoStuff()
    6.     {
    7.        
    8.     }
    9.    
    10. }
    11.  
    You create a Proxy like this:
    Code (csharp):
    1.  
    2. public class FooProxy : MonoBehaviour
    3. {
    4.    
    5.     public void DoStuff()
    6.     {
    7.         GameObject.Find("FooContainer").GetComponent<Foo>().DoStuff();
    8.     }
    9.    
    10. }
    11.  
    Note the forwarding hinges on how 'Foo' is implemented. If for instance it's a singleton, you access it as such. In this example I pretend it's got a specific name. You could also find it by tag, or any other method you use.

    Just attach a FooProxy to your prefab somewhere, and have the UnityEvent target that.
     
  3. Cutty_Flam

    Cutty_Flam

    Joined:
    Jun 9, 2016
    Posts:
    15
    Thanks, I thought about using Find(), and I might just do that. But to be sure, is there no way to do this with UnityEvents?
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Well UnityEvent needs to have a reference to something.

    Since the prefab exists outside of the scene, it can't have a reference to it.

    So no... it's just like how you couldn't have a 'GameObject' field on a script in a prefab, and reference something in the scene. What would it be referencing?
     
    Cutty_Flam likes this.
  5. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Personally, I would use an "event" ScriptableObject Asset. A scriptableObject that has a private event with functions that let you pass Add/RemoveListeners and Invoke. since this event is a project Asset the button prefab can simply reference it and call invoke. then inScene gameobjects have some monobehaviour that add/removes listeners to that event asset in OnEnable/OnDisable.

    Spawn the button and click it. and it'll invoke the event on the Asset. which some other object is listening to and that object then does it's actions.

    this makes it easy to simply drag and drop which scripts listen to which events

    Code (CSharp):
    1.  
    2.     using System.Collections.Generic;
    3.     using System.Linq;
    4.  
    5.     using UnityEngine;
    6.     using UnityEngine.Events;
    7.  
    8.     public abstract class AbstractEventData: ScriptableObject
    9.     {
    10.         /// <summary>
    11.         /// Enables Event Calling. If false it prevents any Invokes. does not prevent Add/Remove Listener calls
    12.         /// </summary>
    13.         [SerializeField]protected bool m_invokable = true; //m_Enable is already in use by the scriptableObject
    14.         /// <summary>
    15.         /// Should this Event Log to console when its Used?
    16.         /// </summary>
    17.         [SerializeField]protected bool m_Log = false;
    18.  
    19.         public bool Invokeable { get { return m_invokable; } set { m_invokable = value; } }
    20.         public bool Log        { get { return m_Log;       } set { m_Log       = value; } }
    21.     }
    Code (CSharp):
    1.  
    2.     using System.Collections.Generic;
    3.     using System.Linq;
    4.  
    5.     using UnityEngine;
    6.     using UnityEngine.Events;
    7.  
    8.     [CreateAssetMenu(menuName = "ScriptableObject Asset/Events/Action")]
    9.     public class ActionEventData : AbstractEventData
    10.     {
    11.         protected UnityEvent actionEvent = new UnityEvent();
    12.  
    13.         virtual protected void OnDisable()
    14.         {
    15.             actionEvent.RemoveAllListeners();
    16.  
    17.             if (localEvents != null)
    18.                 localEvents.Clear();
    19.         }
    20.  
    21.         #region Global Events
    22.         virtual public void AddListener(UnityAction call)
    23.         {
    24.             #if UNITY_EDITOR
    25.             if(Log) Debug.LogFormat(this, "{2}.AddListener({0}.{1})",call.Target.ToString(), call.Method.Name,name);
    26.             #endif
    27.  
    28.             actionEvent.AddListener(call);
    29.         }
    30.         virtual public void RemoveListener(UnityAction call)
    31.         {
    32.             #if UNITY_EDITOR
    33.             if(Log) Debug.LogFormat(this, "{2}.RemoveListener({0}.{1})",call.Target.ToString(), call.Method.Name,name);
    34.             #endif
    35.  
    36.             actionEvent.RemoveListener(call);
    37.         }
    38.         virtual public void Invoke()
    39.         {
    40.             #if UNITY_EDITOR
    41.             if(Log) Debug.LogFormat(this, "{1}.Invoke()\n Enabled:{0}", Invokeable,name);
    42.             #endif
    43.  
    44.  
    45.             if(!Invokeable) return;
    46.  
    47.             actionEvent.Invoke();
    48.         }
    49.         #endregion
    50.    
    51.         #region LocalEvents
    52.         protected Dictionary<GameObject,UnityEvent> localEvents;//lazy initialized
    53.  
    54.         virtual public void AddListener(GameObject target, UnityAction call)
    55.         {
    56.             if (localEvents == null)
    57.                 localEvents = new Dictionary<GameObject, UnityEvent>();
    58.  
    59.             if(!target)
    60.             {
    61.                 //assume target was destroyed, clean local event incase it has any null refs
    62.                 Clean();
    63.                 #if UNITY_EDITOR
    64.                 //target invalid, don't add listener
    65.                 if(Log) Debug.LogWarningFormat(this,"{0}.AddListener(): Target passed was null or Destroyed",name);
    66.                 #endif
    67.                 return;
    68.             }
    69.  
    70.             UnityEvent localEvent;
    71.             if(!localEvents.TryGetValue(target,out localEvent))
    72.             {
    73.                 localEvent = new ActionEvent();
    74.                 localEvents[target] = localEvent;
    75.             }
    76.  
    77.             #if UNITY_EDITOR
    78.             if(Log) Debug.LogFormat(this,"{0}.AddListener(<{1}>{2}.{3})",name,target.name,call.Target.ToString(),call.Method.Name);
    79.             #endif
    80.             localEvent.AddListener(call);
    81.         }
    82.  
    83.         virtual public void RemoveListener(GameObject target,UnityAction call)
    84.         {
    85.  
    86.             if (localEvents == null) return;// local events wasn't initalized meaning nothing was added yet, do nothing
    87.  
    88.             if(!target)
    89.             {
    90.                 //assume target was destroyed, clean local event incase it has any null refs
    91.                 Clean();
    92.                 //Clean should have removed any of Targets limbo bindings
    93.                 return;
    94.             }
    95.  
    96.             UnityEvent localEvent;
    97.             if(localEvents.TryGetValue(target,out localEvent))
    98.             {
    99.                 #if UNITY_EDITOR
    100.                 if(Log) Debug.LogFormat(this,"{0}.RemoveListener(<{1}>{2}.{3})",name,target.name,call.Target.ToString(),call.Method.Name);
    101.                 #endif
    102.  
    103.                 localEvent.RemoveListener(call);
    104.             }
    105.  
    106.         }
    107.  
    108.         virtual public void Invoke(GameObject target)
    109.         {
    110.  
    111.             if (localEvents == null) return;// local events wasn't initalized meaning nothing was added yet, nothing to invoke on
    112.  
    113.             if(!target)
    114.             {
    115.                 //assume target was destroyed, clean local event incase it has any null refs
    116.                 Clean();
    117.                 //Clean should have removed any of Targets limbo bindings
    118.                 return;
    119.             }
    120.  
    121.             if(!Invokeable) return;
    122.  
    123.             UnityEvent localEvent;
    124.             if(localEvents.TryGetValue(target,out localEvent))
    125.             {
    126.                 #if UNITY_EDITOR
    127.                 if(Log) Debug.LogFormat(this,"{0}.Invoke()",name);
    128.                 #endif
    129.                 localEvent.Invoke();
    130.             }
    131.         }
    132.  
    133.         virtual protected void Clean()
    134.         {
    135.             if (localEvents == null)
    136.                 localEvents = new Dictionary<GameObject, ActionEvent>();
    137.  
    138.             else
    139.                 localEvents = localEvents
    140.                     .Where(l=>(bool)l.Key)
    141.                     .ToDictionary(l=>l.Key,l=>l.Value);
    142.  
    143.         }
    144.         #endregion
    145.  
    146.     }
    Code (CSharp):
    1.  
    2. public class ExampleScript: MonoBehaviour
    3. {
    4.     [SerializeField]private ActionEvenData OnAction;
    5.  
    6.     private void OnEnable()
    7.     {
    8.         if((bool)OnAction)
    9.             OnAction.AddListener(SayHelloWorld);
    10.     }
    11.  
    12.     private void OnDisable()
    13.     {
    14.         if((bool)OnAction)
    15.             OnAction.RemoveListener(SayHelloWorld);
    16.     }
    17.  
    18.     private void SayHelloWorld()
    19.     {
    20.         Debug.Log("Hello World!");
    21.     }
    22. }
     
    Last edited: May 24, 2017
    lordofduct likes this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Hrmmm, I only quickly scanned your description, but that sounds interesting.

    May look into it further when I get back from my dinner party. See how well a design like you describe could fit for my designer. He's.... special.... when it comes to code.
     
    JoshuaMcKenzie likes this.
  7. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Yeah I realize that there can be more encapsulated ways of doing things, but I've recently gotten into the habit of writing classes in a way that gives non-programmer designers more toys to play with, and this is one of them. Its also why I recently gotten big into working with editor scripting.

    I also have a generic version for the generic UnityEvent types, the code really isn't that much different

    Code (CSharp):
    1. public abstract class AbstractEventData<T,U>:AbstractEventData where U : UnityEvent<T>
    2. {
    3. // blah blah blah nearly identical code as ActionEventData
    4. }
    well... its different in that I don't actually use UnityEvent but my own type of custom event classes, but the interaction is completely the same, and UnityEvent's API is far more universally recognized.
     
    Alvarezmd90 likes this.
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Yeah, same here. I wrote my own UnityEvent thing a long while back before UnityEvent came out. Been using it ever since, since it technically has a few bells and whistles that UnityEvent does not.
     
    Alvarezmd90 likes this.
  9. Alvarezmd90

    Alvarezmd90

    Joined:
    Jul 21, 2016
    Posts:
    151
    So.. I know this thread is old.. but I'm trying to get Unity Event initialized through the start of a script.
    Isn't this possible with the default Unity system to this date?