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?
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:
public class Foo : MonoBehaviour
{
public void DoStuff()
{
}
}
You create a Proxy like this:
public class FooProxy : MonoBehaviour
{
public void DoStuff()
{
GameObject.Find("FooContainer").GetComponent<Foo>().DoStuff();
}
}
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.
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?
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?
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
AbstractEventData
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
public abstract class AbstractEventData: ScriptableObject
{
/// <summary>
/// Enables Event Calling. If false it prevents any Invokes. does not prevent Add/Remove Listener calls
/// </summary>
[SerializeField]protected bool m_invokable = true; //m_Enable is already in use by the scriptableObject
/// <summary>
/// Should this Event Log to console when its Used?
/// </summary>
[SerializeField]protected bool m_Log = false;
public bool Invokeable { get { return m_invokable; } set { m_invokable = value; } }
public bool Log { get { return m_Log; } set { m_Log = value; } }
}
ActionEventData
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu(menuName = "ScriptableObject Asset/Events/Action")]
public class ActionEventData : AbstractEventData
{
protected UnityEvent actionEvent = new UnityEvent();
virtual protected void OnDisable()
{
actionEvent.RemoveAllListeners();
if (localEvents != null)
localEvents.Clear();
}
#region Global Events
virtual public void AddListener(UnityAction call)
{
#if UNITY_EDITOR
if(Log) Debug.LogFormat(this, "{2}.AddListener({0}.{1})",call.Target.ToString(), call.Method.Name,name);
#endif
actionEvent.AddListener(call);
}
virtual public void RemoveListener(UnityAction call)
{
#if UNITY_EDITOR
if(Log) Debug.LogFormat(this, "{2}.RemoveListener({0}.{1})",call.Target.ToString(), call.Method.Name,name);
#endif
actionEvent.RemoveListener(call);
}
virtual public void Invoke()
{
#if UNITY_EDITOR
if(Log) Debug.LogFormat(this, "{1}.Invoke()\n Enabled:{0}", Invokeable,name);
#endif
if(!Invokeable) return;
actionEvent.Invoke();
}
#endregion
#region LocalEvents
protected Dictionary<GameObject,UnityEvent> localEvents;//lazy initialized
virtual public void AddListener(GameObject target, UnityAction call)
{
if (localEvents == null)
localEvents = new Dictionary<GameObject, UnityEvent>();
if(!target)
{
//assume target was destroyed, clean local event incase it has any null refs
Clean();
#if UNITY_EDITOR
//target invalid, don't add listener
if(Log) Debug.LogWarningFormat(this,"{0}.AddListener(): Target passed was null or Destroyed",name);
#endif
return;
}
UnityEvent localEvent;
if(!localEvents.TryGetValue(target,out localEvent))
{
localEvent = new ActionEvent();
localEvents[target] = localEvent;
}
#if UNITY_EDITOR
if(Log) Debug.LogFormat(this,"{0}.AddListener(<{1}>{2}.{3})",name,target.name,call.Target.ToString(),call.Method.Name);
#endif
localEvent.AddListener(call);
}
virtual public void RemoveListener(GameObject target,UnityAction call)
{
if (localEvents == null) return;// local events wasn't initalized meaning nothing was added yet, do nothing
if(!target)
{
//assume target was destroyed, clean local event incase it has any null refs
Clean();
//Clean should have removed any of Targets limbo bindings
return;
}
UnityEvent localEvent;
if(localEvents.TryGetValue(target,out localEvent))
{
#if UNITY_EDITOR
if(Log) Debug.LogFormat(this,"{0}.RemoveListener(<{1}>{2}.{3})",name,target.name,call.Target.ToString(),call.Method.Name);
#endif
localEvent.RemoveListener(call);
}
}
virtual public void Invoke(GameObject target)
{
if (localEvents == null) return;// local events wasn't initalized meaning nothing was added yet, nothing to invoke on
if(!target)
{
//assume target was destroyed, clean local event incase it has any null refs
Clean();
//Clean should have removed any of Targets limbo bindings
return;
}
if(!Invokeable) return;
UnityEvent localEvent;
if(localEvents.TryGetValue(target,out localEvent))
{
#if UNITY_EDITOR
if(Log) Debug.LogFormat(this,"{0}.Invoke()",name);
#endif
localEvent.Invoke();
}
}
virtual protected void Clean()
{
if (localEvents == null)
localEvents = new Dictionary<GameObject, ActionEvent>();
else
localEvents = localEvents
.Where(l=>(bool)l.Key)
.ToDictionary(l=>l.Key,l=>l.Value);
}
#endregion
}
Example MonoBehaviour
public class ExampleScript: MonoBehaviour
{
[SerializeField]private ActionEvenData OnAction;
private void OnEnable()
{
if((bool)OnAction)
OnAction.AddListener(SayHelloWorld);
}
private void OnDisable()
{
if((bool)OnAction)
OnAction.RemoveListener(SayHelloWorld);
}
private void SayHelloWorld()
{
Debug.Log("Hello World!");
}
}
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.
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
public abstract class AbstractEventData<T,U>:AbstractEventData where U : UnityEvent<T>
{
// blah blah blah nearly identical code as ActionEventData
}
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.
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.
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?