Hi, trying to make an event happen in my game, the title says my problem. This is my EventManager script:
public static void TriggerEventIntPar(string eventName, int par) {
Action<int> evt = null;
if (instance.eventsIntPar.TryGetValue(eventName, out evt))
{
instance.PrintKeysInEventsIntPar();
Debug.Log("eventName: " + eventName);
evt?.Invoke(par);
}
else { Debug.Log("Did not trigger eventIntPar successfully"); }
}
The console prints out an identical match for both the string eventName and the first (and only) key in the dictionary. Aside from returning true, this is also how I know that it should have gotten the correct key. The most confusing part of this is that it was working when I left Unity the other day and now that I came back it no longer works as the evt is null.
If I can clarify anything or provide more code/screenshots I can, but I’ve been trying to solve this mystery for a few hours and I need help.
Assuming eventsIntPar is a Dictionary<string, Action> then be aware that values in the dictionary are allowed to be null, these are legal assignments:
eventsIntPar.Add(“some key”, null);
eventsIntPar[“some key”] = null;
Meaning: it’s null because a null value has been stored in the dictionary at some point. So you should be looking at (logging) the code that adds or updates event values to figure out when, where and why a null value was added/assigned.
Thanks for the response, eventsIntPar is a Dictionary<string, Actions>. I am aware that the Action part can be null. I should have provided the rest of that class from the start. Maybe I have added to the dictionary incorrectly?
using System;
using System.Collections.Generic;
using UnityEngine;
public class EventManager : MonoBehaviour
{
// eventsIntPar
private Dictionary<string, Action<int>> eventsIntPar;
public static event Action<int> CollectTorch;
public static EventManager instance;
private void Awake()
{
if (instance == null)
instance = this;
else
{
Destroy(gameObject);
return;
}
eventsIntPar = new Dictionary<string, Action<int>>();
DontDestroyOnLoad(gameObject);
}
private void Start()
{
eventsIntPar.Add("CollectTorch", CollectTorch);
}
// oversees triggering events that will take one integer as a parameter
public static void TriggerEventIntPar(string eventName, int par) {
Action<int> evt = null;
if (instance.eventsIntPar.TryGetValue(eventName, out evt))
{
instance.PrintKeysInEventsIntPar();
Debug.Log("eventName: " + eventName);
evt?.Invoke(par);
}
else { Debug.Log("Did not trigger eventIntPar successfully"); }
}
public void PrintKeysInEventsIntPar()
{
Dictionary<string, Action<int>>.KeyCollection keys = eventsIntPar.Keys;
foreach (string key in keys)
{
Debug.Log("Key: " + key);
}
}
}
Well, at the time you add CollectTorch it probably hasn’t been assigned an Action delegate yet, thus you are adding null to the dictionary. Any other class registering a method for CollectTorch must do so during Awake because that gets called before Start.
What’s the point of adding a static event delegate to a dictionary anyway? Unless you have other plans for this code you could use switch(eventName) { case "CollectTorch": CollectTorch?.Invoke(par); break; } to pick the correct delegate to invoke.
It’s working! Moving when other scripts subscribed to the delegate to Awake fixed it right up. I’m new to Dictionaries and delegates, events, actions, etc., so I had basically adapted some code I found on the internet to work for me. I tried your suggestion with the switch statement and it works perfectly! Thank you so much, this was a huge headache for me the last 24 hours!!
Be careful here. C# events are just delegates with some additional restrictions on top. You’re NOT storing the “event” in your dictionary but just the currently assigned delegate. If any thing subscribes or unsubscribes from the event, the delegate in your dictionary would not change as delegates are immutable.
Subscribing / unsubscribing from an event / delegate means that the old delegate is replaced by a new one that contains the combined or reduced invokation list of the two provided delegates. So your setup is not very flexible. What you can do is provide a separate method that actually invokes the event or use a lambda expression that invokes the event (essentially the same thing)
eventsIntPar.Add("CollectTorch", i=>CollectTorch?.Invoke(i));
or
eventsIntPar.Add("CollectTorch", InvokeCollectTorch);
// [ ... ]
private void InvokeCollectTorch(int par)
{
CollectTorch?.Invoke(par)
}
C# events are notoriously bad as they produce a lot of garbage when you subscribe / unsubscribe. A UnityEvent is actually a bit better in that regards. Though if you want to minimize garbage, nothing would beat a List of your subscriber action delegates which you manually invoke one-by-one when you want to raise the event. That’s what a multicast delegate does as well. Of course adding / removing would need to be done manually as well which can get a bit tricky because subscribing a “method” to an event means you implicitly create a new delegate for that method. The same thing happens when you unsubscribe. So finding a subscibed method in the list requires to analyze the actual method / target inside the delegate. Again, that’s what the MulticastDelegate does anyways.