Perhaps it's not the best title but didn't know how else to say it. I have a problem/curiosity and couldn't find a better solution anywhere.
I am working on a Unity game and suppose I have a class where I declare an event and invoke it
ClassA : MonoBehaviour
public static event Action<string> LevelCompletedEvent;
public void LevelCompleted()
{
LevelCompletedEvent?.Invoke("Good job!");
}
I want to use this event in 2 other classes that will listen for it. In ClassB I am using the parameter to output the message in the UI.
ClassB : MonoBehaviour
OnEnable(){
ClassA.LevelCompletedEvent = DisplayMessage;
}
OnDisable(){
ClassA.LevelCompletedEvent -= DisplayMessage;
}
private void DisplayMessage(string message)
{
//display the message in the UI
}
However in ClassC I don't need the incoming parameter "message". I only need the event to know that it was completed.
ClassC : MonoBehaviour
OnEnable(){
ClassA.LevelCompletedEvent = PlaySound;
}
OnDisable(){
ClassA.LevelCompletedEvent -= PlaySound;
}
private void PlaySound()
{
//play a sound
}
The problem is that it won't let me subscribe to the event because the handler doesn't have the same signature. So I tried to subscribe to it using an anonymous delegate.
ClassC
OnEnable(){
ClassA.LevelCompletedEvent = delegate { PlaySound(); };
}
OnDisable(){
ClassA.LevelCompletedEvent -= delegate { PlaySound(); };
}
Seeing that this works, I have used this method a lot but recently learned that it doesn't actually unsubscribe from the anonymous delegate and that I should keep a reference to it.
So I changed it to
ClassC
private Action<string> LevelCompletedNewDelegate;
OnEnable(){
LevelCompletedNewDelegate = delegate { PlaySound(); };
ClassA.LevelCompletedEvent = LevelCompletedNewDelegate;
}
OnDisable(){
ClassA.LevelCompletedEvent -= LevelCompletedNewDelegate;
}
Now this works but I am not sure if this is the best way to do it. I have a lot of events and I don't really like that I have to declare a new Action delegate on the listener's side.
Is there a better way to do this ?
The easiest way that I found is to just create a new Action event and invoke that for ClassC, like:
ClassA : MonoBehaviour
public static event Action<string> LevelCompletedEvent;
public static event Action LevelCompletedPlaySound;
public void LevelCompleted()
{
LevelCompletedEvent?.Invoke("Good job!"); //used by ClassB
LevelCompletedPlaySound?.Invoke(); //used by ClassC
}
This way I could have an event for each class but again, I'm not sure if this way is better. Feels a bit redundant. What do you think?
Sorry for the long message but perhaps it could help others struggling with this to also understand.
Thank you!
CodePudding user response:
You can subscribe to an event action anonymously like
ClassA.LevelCompletedEvent = () => { PlaySound(); };
And when you do this you don't have to unsubscribe to that event OnDisable()
it will be Garbage Collected
by Unity when scene change.
But you have to keep in mind when subscribing anonymously that you cannot Unsubscibe
to an event if you have to do it else where in you code. If you must unsubscribe you have to follow your approach in Class C
Official documentation says
You cannot easily unsubscribe from an event if you used an anonymous function to subscribe to it. To unsubscribe in this scenario, go back to the code where you subscribe to the event, store the anonymous function in a delegate variable, and then add the delegate to the event. We recommend that you don't use anonymous functions to subscribe to events if you have to unsubscribe from the event at some later point in your code. For more information about anonymous functions
CodePudding user response:
I think the most optimal and readable way is using a discard parameter
public class ClassC : MonoBehaviour
{
private void OnEnable()
{
ClassA.LevelCompletedEvent = PlaySound;
}
private void OnDisable()
{
ClassA.LevelCompletedEvent -= PlaySound;
}
private void PlaySound(string _)
{
//play a sound
}
}