I have a script that consist of an event and a function that is triggered when this is called. Now I have another script. How do I check if the event from first script is triggered?
First script
public class MyEventManager: MonoBehaviour
{
private static MyEventManager _instance;
public static MyEventManager Instance
{
get
{
if (_instance == null)
_instance = FindObjectOfType<MyEventManager>();
return _instance;
}
}
public delegate void OpenEventHandler(bool wasOpened);
public event OpenEventHandler Opening;
public void OpeningObj()
{
OpeningObj(true);
}
public void OpeningObj(bool wasOpened)
{
Opening?.Invoke(wasOpened);
}
Second script
private MyEventManager eventMan;
private void Start () {
eventMan= (MyEventManager ) GameObject.FindObjectOfType (typeof (MyEventManager ));
}
private void CalledFromFunction()
{
Debug.Log("Is Called");
}
I get the error - No overload for 'CalledFromFunction' matches delegate 'MyEventManager.OpenEventHandler'
Since you use a singleton approach, you don't actually need the private MyEventManager eventMan; in the other class. You can get access to the manager from anywhere with the public static property you have declared ( public static Instance MyEventManager), so to get acces to the manager just use MyEventManager.Instance, since it's static it will be accessible from anywhere.
To react to some event the listener need to subscribe to the event. It's done like this:
public class ListenerClass : MonoBehaviour
{
void Start()
{
MyEventManager.Instance.Opening += OnOpening;
}
It means whenever the Opening event is triggered in the manager, the OnOpening method of the listener class should be called:
void OnOpening(bool value)
{
// do stuff when the Opening event is triggered
}
}
EDIT: my browser failed to load the second part where you have posted the second script and the error message.
You get the error because you have declared the event delegate as a function that takes 1 boolean argument and has no return value ( void OpenEventHandler(bool wasOpened) but in the second script you set the event handler to the method that doesn't have arguments, therefore it doesn't match the delegate signature.
You need to change
private void CalledFromFunction()
to
private void CalledFromFunction(bool value)
to get the method match the delegate.
You got error No overload for 'CalledFromFunction' matches delegate 'MyEventManager.OpenEventHandler' because OpenEventHandler is a delegate with bool type parameter. So method CalledFromFunction requires a bool type parameter.
Second script:
private MyEventManager eventMan;
private void Start () {
eventMan= (MyEventManager ) GameObject.FindObjectOfType (typeof (MyEventManager ));
browserOverlay.Opening+= this.CalledFromFunction;
}
private void CalledFromFunction(bool value)
{
Debug.Log("Is Called");
}
Related
I need to wait until a scene is fully loaded in order to move a gameObject to it from DontDestroyOnLoad scene. If I do it too soon (just after calling SceneManager.LoadScene()) then the gameObject disappears. Based on this post I implemented a scene loading class to solve this issue:
public static class CustomSceneManager
{
public delegate void SceneChange(string sceneName);
public static event SceneChange LoadScene;
public static event SceneChange UnloadScene;
private static IEnumerator LoadLevel (string sceneName){
var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!asyncLoadLevel.isDone){
Debug.Log("Loading the Scene");
yield return null;
}
}
public static void OnLoadScene(string newSceneName)
{
OnUnloadScene(newSceneName);
LoadLevel(newSceneName);
LoadScene?.Invoke(newSceneName);
}
private static void OnUnloadScene(string newSceneName)
{
UnloadScene?.Invoke(newSceneName);
}
}
I'm calling two events from it (LoadScene and UnloadScene). However the LoadLevel(newSceneName) doesn't work - it simply doesn't load a scene. What am I doing wrong here?
EDIT:
Now I'm passing the MonoBehavior reference of the script calling OnLoadScene methid like this:
public static void OnLoadScene(MonoBehaviour loader, string newSceneName)
{
UnloadScene?.Invoke(newSceneName);
loader.StartCoroutine(LoadLevel(newSceneName));
Debug.Log(SceneManager.GetActiveScene().name); // this line returns previous scene
LoadScene?.Invoke(newSceneName);
}
Now the scene loads, but when I check what scene is currently loaded, it returns the previous scene name.
EDIT 2:
To be more precise I replaced Debug.Log(SceneManager.GetActiveScene().name); with Debug.Log(SceneManager.GetSceneByName(newSceneName).isLoaded); and it returns False.
You have to run coroutienes using StartCoroutine.
You either would need to pass in a reference of a MonoBehaviour that will execute the coroutine or simply make your class a Singleton that is never destroyed
Than actually you will invoke your event too early when it is not yet loaded but you just started to load it so rather do e.g.
public class CustomSceneManager : MonoBehaviour
{
public delegate void SceneChange(string sceneName);
public static event SceneChange LoadScene;
public static event SceneChange UnloadScene;
private CustomNetworkManager singleton;
private void Awake ()
{
if(singleton && singleton != this)
{
Destroy(gameObject);
}
singleton = this;
DontDestroyOnLoad (gameObject);
}
private static IEnumerator LoadLevel (string sceneName){
var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!asyncLoadLevel.isDone){
Debug.Log("Loading the Scene");
yield return null;
}
LoadScene?.Invoke(newSceneName);
}
public static void OnLoadScene(string newSceneName)
{
if(! singleton)
{
singleton = new GameObject("CustomNetworkManager").AddComponent<CustomNetworkManager>();
}
OnUnloadScene(newSceneName);
singleton.StartCoroutine(LoadLevel(newSceneName));
}
Another way you can do this is with an async method.
The catch is that in order to await an AsyncOperation you need a custom awaiter class. There are a few libraries that make it possible, and my favorite one is UniTask.
using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement;
public static class CustomSceneManager
{
public delegate void SceneChange(string sceneName);
public static event SceneChange LoadScene;
public static event SceneChange UnloadScene;
private static async UniTask LoadLevelAsync(string sceneName)
{
await SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
}
public static async UniTask OnLoadSceneAsync(string newSceneName)
{
OnUnloadScene(newSceneName);
await LoadLevelAsync(newSceneName);
LoadScene?.Invoke(newSceneName);
}
private static void OnUnloadScene(string newSceneName)
{
UnloadScene?.Invoke(newSceneName);
}
}
I have class "A", which will send event "a". Classes that are subscribing to event "a" will react to this event. Other classes can subscribe to event "a" without changing anything in class "A";
Now, what is the most reasonable way to do this in unity? Is there some messaging system that can already do that?
If not, should I make something like EventManager class that will store subscriber classes in array and call their methods?
There are probably many ways to do this.
Public static List
public class A : MonoBehaviour
{
public static List<A> AvailableAs = new List<A>();
private void Awake()
{
if(!AvailableAs.Contains(this)) AvailableAs.Add(this);
}
private void OnDestroy()
{
if(AvailableAs.Contains(this)) AvailableAs.Remove(this);
}
public void SomePublicMethod()
{
// magic
}
}
and use it e.g. like
public class B : MonoBehaviour
{
// run the method on all currently registered A instances
public void DoIt()
{
foreach(var a in A.AvailableAs)
{
a.SomePublicMethod();
}
}
}
Global EventHandler
Or if you rather want to go for encapsulation have as you mentioned a global event handler for all A's like
namespace ANamespace
{
public static class AEventHandler
{
internal static event Action OnInvokeA;
public static void InvokeAEvent()
{
if(OnInvokeA != null) OnInvokeA.Invoke();
}
}
}
and in A have
namespace ANamespace
{
public class A : MonoBehaviour {
private void Awake()
{
// it is save to remove a callback first even
// if it was not added yet. This makes sure it is
// added only once always
AEventHandler.OnIvokeA -= SomePrivateMethod;
AEventHandler.OnIvokeA += SomePrivateMethod;
}
private void OnDestroy()
{
AEventHandler.OnIvokeA -= SomePrivateMethod;
}
private void SomePrivateMethod()
{
// magic
}
}
}
Now in B you would rather simply do
// invoke the event and whoever is added as listener will do
// whatever he registered
// in this case all A instances execute their method
AEventHandler.InvokeAEvent();
Unity Event
If you have however only one class A which throws an event and you want others to react to it simply use a UnityEvent like
public class A : MonoBehaviour
{
public UnityEvent OnSomethingHappened = new UnityEvent();
private void SomeMethodIWillRun()
{
//...
OnSomethingHappened.Invoke();
}
}
Now you cann easily add callbacks to that event in the Unity Inspector by dragging in GameObjects/Components and select the method to call. (Exactly the same thing is used for the onClick event of the Button component btw. ;) )
And you could still add callbacks via script on runtime like
public class B : MonoBehaviour
{
public A referenceToA;
private void Start()
{
referenceToA.OnSomethingHappened.AddCallback(OnASomethingHappened);
}
private void OnDestroy()
{
referenceToA.OnSomethingHappened.RemoveCallback(OnASomethingHappened)
}
private void OnASomethingHappened()
{
//
}
}
Note: Typed on smartphone so no warranty but I hope the idea gets clear.
There is a video tutorial on unity official website called:
Events: Creating a simple messaging system.
They create a Event Manager or Messaging System.
I watch it and it was very helpful so I create that system on my game and now decided to instead of using UnityEvent and UnityAction use delegate and event which is better performance and good practice. So here is my code [StopListen() function not included yet]:
public class EventManager : MonoBehaviour
{
public delegate void GameEvents();
public event GameEvents onGameEvent;
private static EventManager _instance = null;
public static EventManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(EventManager)) as EventManager;
}
if(!_instance)
{
Debug.LogError("Put a GameObject with this scrip attach to it on your scene.");
}
else
{
_instance.InitializeEventDictionary();
}
return _instance;
}
}
private Dictionary eventsDictionary;
void InitializeEventDictionary()
{
if (eventsDictionary == null)
eventsDictionary = new Dictionary();
}
public void Listen(string eventName, GameEvents gameEvent)
{
GameEvents thisEvent = null;
if (Instance.eventsDictionary.TryGetValue(eventName, out gameEvent))
{
thisEvent += gameEvent;
}
else
{
thisEvent = new GameEvents();
thisEvent += gameEvent;
Instance.eventsDictionary.Add(eventName, thisEvent);
}
}
public void TriggerEvent(string eventName)
{
GameEvents thisEvent;
if(Instance.eventsDictionary.TryGetValue(eventName, out thisEvent))
thisEvent.Invoke();
}
}
In my Listen() function this line thisEvent = new GameEvents(); gets me in trouble and I don't know how to fix it! (Help!) :-)
[PS] :
Is delegate and event have better performance then UnityEvent and UnityAction ?
What more should be or must be add to this code, to make it more efficient?
You must define what should invoked when the delegate is accessed, otherwise if nothing should be done the delegate is not necessary.
Like a lamda:
thisEvent = new GameEvents(() => { Console.WriteLine("TODO");});
Or a method:
thisEvent = new GameEvents(Target);
private void Target()
{
throw new NotImplementedException();
}
may be have a look at https://www.codeproject.com/Articles/624575/Delegate-Tutorial-for-Beginners
For execution time, I think best is to make a test and see what performs better.
Is it possible to send reference of 'sender' without specifying it explicitly as a parameter in delegate-based event handling?
I have a internal class which raises some events and I want to call the events explicitly for test purposes.
public class Manager {
public class DataStruct {
public int Id { get; private set; }
public event EventHandler Event1; // Can't be called by other classes
public void fireEvent1(Event1();} // So another caller...
// Delegates *can* be called by other classes
public delegate void DelegateHandler(DataStruct sender);
public DelegateHandler NewEvent;
public void DelegateHandler(DataStruct sender) {
MessageBox.Show(string.Format(
"{0} raises event", sender.Id));
}
}
}
// Form1 ///////////////////////////////////////////////////////////////////
partial class Form1 {
Manager.DataStruct dsRaiser, dsListener;
private void Form1_Load(object sender, EventArgs e) {
dsRaiser.Event1 += dsListener.SOME_HANDLER;
dsRaiser.NewEvent += dsListener.DelegateHandler;
}
private void button1_Click(object sender, ...) {
dsRaiser.fireEvent1(); // No argument needed but fireEvent1, not Event1().
}
private void button2_Click(object sender, ...) {
dsRaiser.NewEvent(dsRaiser); // Way to omit parameter dsRaiser?
}
//////////////////////////////////////////////////////////////////////////
If your handler method needs to use the sender's reference, then you HAVE to pass that reference.
If not, just declare a void parameterless delegate, like Action.
But when thinking of events, that parameter should be passed by the class that raises the event itself. (Remember events are not meant to be called from outside).
So, if you really want to use a simple delegate instead of an event, you will have to pass the parameters.
If you need the sender, you will need to do exactly what you did with the event: create a method to "raise" the delegate, and in that method you pass this as the sender.
But considering you have to do exactly the same thing in both cases, I'd surely use the event.
public class DataStruct {
public int Id { get; private set; }
public event EventHandler Event1; // Can't be called by other classes
// you need to pass those parameters to the event when called.
public void fireEvent1{Event1(this, new EventArgs());}
// Delegates *can* be called by other classes, but only with all parameters passed.
public delegate void DelegateHandler(DataStruct sender);
public DelegateHandler NewEvent;
// To avoid passing parameters, you need to do exactly what you did with the event
public void RaiseDelegate() { NewEvent(this); }
public void DelegateHandler(DataStruct sender) {
MessageBox.Show(string.Format(
"{0} raises event", sender.Id));
}
}
Yes, it is possible.
Just store the sender inside the subscription when subscribing to an event.
If we had a delegate declared like this:
public Action NewEvent; // No need to be DelegateHandler
Then we can use c# compiler to generate such a subscription for us using anonymous delegates:
dsRaiser.NewEvent += () =>
{
dsListener.DelegateHandler(dsRaiser);
};
Anything, which we reference inside our anonymous handler is automatically captured for us (both dbListener and dsRaiser in this case).
Or, if we want explicit declaration of the subscription:
class MySubscription
{
private readonly DataStruct _raiser;
private readonly DataStruct _listener;
public MySubscription(DataStruct raiser, DataStruct listener)
{
_raiser = raiser;
_listener = listener;
}
public void HandleTheSubscription()
{
_listener.DelegateHandler(_raiser);
}
}
And this is how we subscribe:
private void Form1_Load(object sender, EventArgs e) {
var mySubscription = new MySubscription(dsRaiser,dsListener);
dsRaiser.NewEvent += mySubscription.HandleTheSubscription;
}
As you can see MySubscription is defined by us and we can store any objects there.
I am making a game and I'm trying to create an way for objects to handle collisions with each other. I want to do something like:
//Imaginary C#
public SomethingThatCollides()
{
CollisionEvent<ObjectA> += CollisionWithObjA;
CollisionEvent<ObjectB> += CollisionWithObjB;
}
void CollisionWithObjA(ObjectA other)
{
//Do something
}
void CollisionWithObjB(ObjectB other)
{
//Do something else
}
When, say, CollisionEvent<ObjectA> is raised (perhaps by some collision checking code), CollisionWithObjA should get called. Same for CollisionWithObjB; when a collision with ObjectB is detected, it will raise the CollisionEvent<ObjectB> event which results in CollisionWithObjB getting called.
Is something like this possible?
Here is the thing, if class is generic and it has static field, it can work like a dictionary with key being type
public class Something {
public class EventsHolder<T>
{
static event Action<T> CollideEvent;
}
public void AddEvent<T>(Action<T> collisionEvent)
{
EventsHolder<T>.CollideEvent = collisionEvent;
}
public void RaiseCollision<T>(T Obj)
{
var Event = EventsHolder<T>.CollideEvent;
if (Event != null) Event(Obj);
}
}
Downside is that it uses static fields which can be inapropriate.
In this case you can use code #Daniel posted.
You can't really create a generic event like that. I suggest you create a special event arguments class that also encapsulates the collided object and check for its type in the event handler method:
public class CollisionEventArgs : EventArgs {
public object Object {
get; private set;
}
// ...
}
You'll need a special dispatcher method to use it:
class SomethingThatCollides {
public SomethingThatCollides(CollisionManager cm) {
cm.CollisionEvent += CollisionWithObj;
}
void CollisionWithObj(object sender, CollisionEventArgs args) {
if (args.Object is ObjectA) {
CollisionWithObjA((ObjectA)args.Object);
}
else if (args.Object is ObjectB) {
CollisionWithObjB((ObjectB)args.Object);
}
}
// ...
}
Or, you can try to solve this with double-dispatching, without using C# events. Look at wikipedia for a collision example.
That's uggly, but...You could have a dicionary of events by type:
Dictionary<Type, object> MyEventsByType;
event Action<A> CollisionEventA;
event Action<B> CollisionEventB;
event Action<C> COllisionEventC;
void Initialize()
{
MyEventsByType = new Dictionary<Type, object>();
MyEventsByType.Add(typeof(A), CollisionEventA);
MyEventsByType.Add(typeof(B), CollisionEventB);
MyEventsByType.Add(typeof(C), CollisionEventC);
}
void RaiseCollision<T>(T Obj)
{
Action<T> Event = (Action<T>)MyEventsByType[typeof(T)];
if (Event != null) Event(Obj);
}