Too many Events/Delegates for script communication?

Hi all,

I have been learning Unity and C# for little under a year now and am now “pretty good” at making things work (game prototypes). However, now I am focusing on improving my ‘architecture’ and setting out my code correctly and efficiently. I am moving towards implementing more scripts on each object to handle separate functionality, interfacing etc. I have seen improvements in my code in terms of efficiency and readability.

However, I still cant make a decision on the best way for scripts to communicate with each other(references, send message, event delegates etc). I am determined to make as many of the scripts independent as possible but I cant seem to come to a decision with so many different opinions out there.

For example, a situation I’m sure most games have: If there is a button that when pressed, blows up object A, instantiates a particle system, increments your score, disables another button etc. All these actions are probably going to be on different scripts so I assume the best way to enable all this is using a delegate event? How about if the button just creates a response in one script, does that still require a delegate event?

My concern is that then surely your code is going to be full of event delegates (which I hear can be a problem when it comes to GC) every time you need communication, but from what I see on others peoples scripts that isn’t the case. When your scripts absolutely have to talk to another script on another object is delegate/events the way to go?

Thanks for any help!

Separation of concerns is definitely a good thing.
Imagine if your object which gets blown up directly increments your score.

public void Explode()
{
    // Show visual effects.
    ScoreKeeper.IncrementScore();
}

This object now has a direct dependency on the existence of a ScoreKeeper object existing somewhere else.
If for some reason you decide that you don’t want to increment the score anymore when this object explodes, or perhaps you don’t have a ScoreKeeper anymore at all, you will have to go through each script that directly references this class and remove that line. This can cause more side effects, because perhaps another part of your code depends on your ScoreKeeper’s score incrementing to do some of its behaviour.

In this case an event definitely makes sense. Like this:

public void Explode()
{
    // Show visual effects.
    if (OnExploded != null) OnExploded();
}

Where OnExploded is an event. Your ScoreKeeper could listen to the events of all things that could possibly increment the score, and other behaviours can now react the the explosion of this object as well, without this object having to even know of the existence of these other behaviours.

Events are often a clean approach to separate your logic, and I wouldn’t worry too much about the minimal garbage that delegates create (this really won’t be your bottleneck, even if you have thousands of events). That doesn’t mean events are always the way to go. Sometimes you will want to use an interface, a base class, “Manager” classes, etc. And you will almost always use all of these, in combination. Use whatever gives you the most freedom and flexibility as a developer, and evaluate each problem on a case-by-case basis. Don’t create too many layers of abstraction if you don’t have to. If you have a button whose sole purpose is to blow up a certain object, it is absolutely fine for that button to have a direct reference to the object it wants to blow up. Only when you want greater flexibility and scalability should you think about refactoring this into something more separated or generic.

Side note: please avoid using SendMessage. It has some benefits, but the negatives outweigh those. Whenever you want to use SendMessage, it can be substituted by using interfaces.

2 Likes

I’ll second what Kwinten said about separation of concerns. Then I’ll take it a step further and suggest it might be one of the most important fundamental rules of good architecture :wink:

In this case, you might want to consider building a proper message dispatcher. There are many ways of doing it but they all tend to have the same basic concepts and usually allow posting both global and targeted messages. Typically, they rely on delegates and events in C#.

I wouldn’t worry about performance or garbage too much. At least not until you know it is an issue. I use a dispatcher for all intercommunication between entities and often between GameObjects or even components of a single GameObject. Never felt the effects of it once.

1 Like

Thanks very much for the feedback guys. Yeh I definitely need to improve my separation of concerns. As I add more features to my prototypes I find myself spending ages following trials through my code to make the correct additions.

  • When it comes to Delegates/Events is it still worth using them for single interactions? For example if I have a button that just affects one other script in the project is it worth using a delegate/event as opposed to a send message? Is it very okay to just directly call a public method on another script? I am trying to look up articles on Actions/func etc but not many around specifically for Unity

  • Can I ask what kind of dispatcher you use Sluggy? Is it a specific script/interface you have created?

In this case, it is almost definitely not worth it to try to genericize this workflow. If you have a button whose only purpose is to trigger an action on one specific object, let your button store a reference to that object and call the associated public function. When you come across the situation where you want to have a generic button that activates any action on any object, look into more separation of concerns and try to make the code more generic. If you won’t need it, then don’t make it harder for yourself.

An alternative to hooking up delegates in code is to use UnityEvents or similar systems to hook things up in the inspector.

@Kwinten 's example would then be:

[SerializeField] private UnityEvent OnExploded ;

void Explode() {
    OnExploded.Invoke();
}

Then you can assign what’s assigned to the OnExploded event in the inspector rather than in code. If you feel that gives you a better design, then that’s the way to go.

Yes, I use a custom rolled one with lots of bells and whistles but a simple one will suffice for most things. As a simple example, typically you’ll have a base class or interface that all message classes derive from. as well, listeners add or remove themselves from a central dispatcher by supplying a handler method and the type of message they are listening for. Their handler method tends to take at least a single parameter that is the message itself. The dispatcher can store all of this in a single dictionary that maps each message type to a delegate. When someone posts a message to dispatch they will supply an instance of the message and the dispatcher will match it up with the appropriate type in dictionary, if any, and invoke the associated methods.