Hello,
I’m trying to implement a generic notification system in my game.
When creating a new notification I pass an UnityAction as parameter that will be called when the player click the notification.
Now I’d like to be able to save and load my notification list, but I can’t find a way to save the UnityActions.
Note that UnityAction may have a parameter (e.g. DisplayMonsterInfo(ulong monsterId))
If any one has some tips or a way to achieve the same result I’d be very grateful.
You probably talking about a UnityEvent, right? What exactly do you mean by
Can you share some of your code? Do you mean that you have a closure with a captured parameter / variable? Do you assign the methods and parameters of a UnityEvent in the inspector or do you just talk about delegates / closures created in code?
Serializing closures is generally not that easy. Unity’s UnityEvent class can do this within Unity’s serialitation system but only supports a single serialized parameter and only a handful of suported types-
The next question is when you say you want to save / load those, do you mean saving to disk? If you have callbacks that point to some concrete objects in your application the real issue is when you reload them how you restore those references.
We definitely need more context here ^^.
Thank you for taking the time to answer me, I’ll try to provide more concrete info:
I have a notification class, I display the notification text in game and if player click on it it calls the Callback method:
public class Notification
{
public string Text;
public UnityAction<ulong> Callback;
}
The callback will be for example a method in an other class
public void DisplayMonsterInfo(ulong monsterId)
{
foreach(var monster in Monsters)
{
if (monster.id == monsterId)
DisplayMonsterInfo(monster);
}
}
I want to be able to save my list of notifications in a file, and once I load it the Callback method will be there.
The callback methods are in singletons and should be there after loading. I’m aware this can create some issues too but this is something I can handle.
The only way I see to achieve that at the moment would be to create some kind of URL parser, pass an internal URL instead of a UnityAction and parse it to call the right method
Notification.Callback = "DisplayMonsterInfo?Id=123";
That will be parsed in a dedicated method to call the proper DisplayMonsterInfo method.
public static void OpenInternalURL(string callbackURL)
{
var urlAddress = GetUrlAddress(callbackURL);
var urlParameter = GetUrlParameter(callbackURL);
switch(urlAddress)
{
case "DisplayMonsterInfo":
MonsterManager.Instance.DisplayMonsterInfo(urlParameter);
break;
}
}
But I lose the flexibility I have to pass any method I want at the moment.
I’m sorry I’m not familiar with the concept of “closure”, but I hope my issue is a bit more clear now.
I think I’m more confused than before 
The code you presented at the end looks more like a web interface for calling arbitrary methods while your first part with the notification class indeed looks like a plain callback delegate. What’s still not clear is how and where do you actually setup those callbacks in the first place. Is this a question about saving an already setup delegate so it can be reloaded later, or is it about specifying the method call as a string in the first place. Because that’s what you did here:
So where and how is the actual link which callback should be called defined? As a string, like in this snippet of code, or is the call specified otherwise?
Your notification class has a text and a callback that receives a ulong value. The ulong value has to be given by the caller when the callback is called if you have an UnityAction<ulong>
are you sure you want an action with this signature? So your notification class callback always receives a single ulong value? Because nothing else would be supported by this. Maybe you wanted to use a general UnityAction which can simply be called and have the parameters in a closure / lambda expression. Though this comes back to the question where and how you specify / setup which method should be executed and also how you want to call the delegate from the calling side.
A closure is an automatically generated hidden class that can “capture” variables of the enclosing scope. That might sound even more confusing ^^ However here’s an example
UnityAction callback;
ulong someVariable = 5;
void SomeMethod()
{
callback = ()=>MyCallback(someVariable);
}
void MyCallback(ulong aValue)
{
Debug.Log("callback with value: " + aValue);
}
Here inside SomeMethod we create a closure that is assigned to our callback. Here the anonymously generated delegate is a closure because it captures the variable “someVariable”. When the “callback” is executed, the MyCallback method will receive the current value of the “someVariable” variable.
That’s what I meant be closure and this is how you would assign a callback with a implicit parameter to a delegate which hasn’t a parameter. However saving and loading such a closure is very difficult.
That’s why it’s important to know where you want to define which method should be called.
To clarify, the second part of my message was a possible solution I had in mind for my problem. Using custom urls. But I do not use this solution at the moment and would prefer not to.
I understand now what you mean by closure. Indeed it would be even harder to save. I’m using an ulong as a parameter as it’s the type I use to identify most objects in my game.
public class Monster
{
public ulong Id;
}
so my Notification class including the target ID looks like:
public class Notification
{
public string Text;
public UnityAction<ulong> Callback;
public ulong TargetId;
}
and here is an example of how I would create a new notification
public class MonstersManager
{
public List<Monster> Monsters;
public void SpawnNewMonster()
{
Monster newMonster = new Monster();
Monsters.Add(newMonster);
Notification newMonsterNotification = new Notification();
newMonsterNotification.Text = "A new monster has spawned";
newMonsterNotification.Callback = DisplayMonterInfo;
newMonsterNotification.TargetId = newMonster.Id;
NotificationsManager.ShowNotification(newMonsterNotification);
}
public void DisplayMonsterInfo(ulong monsterId)
{
foreach (var monster in Monsters)
{
if (monster.id == monsterId)
DisplayMonsterInfo(monster);
}
}
}
And what will happen when the player click on the notification in game
public Notification notification;
public void NotificationClick()
{
this.notification.CallBack(this.notification.TargetId);
}
Ahh ok, this clears up alot. So you do create your notification instances in code, you do assign an actual method / delegate to your callback and the parameter is already part of the calling logic. So the only thing left is to store and restore this method.
Though this is the next issue ^^. As we can see the method you assigned as callback is a member method of the MonstersManager. So when you assign the callback you’re actually doing
newMonsterNotification.Callback = this.DisplayMonterInfo;
So the delegate has a reference to the MonstersManager instance. If you want to restore this delegate you have to know this reference as well because just knowing the method and which class it belongs to is not enough. If all the possible methods you want to assign are part of the same MonstersManager instance this would simplify the issue. However if you could assign methods from different objects it becomes a problem.
You can use reflection to get the MethodInfo from the delegate and extract the class name as well as the method name. With this information you can again get access to the MethodInfo after reloading. What’s still missing is the instance reference. Again if all methods you want to support are all part of the same class instance, this is not an issue.
I currently don’t have much time left to give you a code example. Maybe you can already figure out a solution or you have more details about where the potential methods are located. If I find the time I could give you an example.
Thank you once again for your answer
Reflection always sound like dark magic to me
but I guess this could be the way to go indeed! I’ll dig in that direction.