Event and message dispatching is a critical part of any game. Message dispatching ensures that game objects are able to communicate in a consistent, reliable, and efficient way. The Event System - Dispatcher (dispatcher for short) does exactly this.
Iâm considering buying Event System. Looks solid and great reviews! I have a question about performance. Is Event System performant enough to use for a situation where I want to dispatch a message once every few frames? For instance, spawn controllers that listen to a âheartbeatâ and need to spawn every 10 frames or so?
The real factor will be the computers youâre running on. For me, the web demo shows 500 messages in 1.3 milliseconds. Since each frame typically takes 16.6 milliseconds (60 FPS), you should be fine.
MD = Message Dispatcher (this asset)
SMess = Unityâs native SendMessage call
Direct = Directly calling the function instead of using the message system.
What would be the best way to listen/send messages between Components on the same GameObject? Iâm building some reusable Components that work together when they share the same GameObject. For simplicityâs sake, letâs say I have a component that sends values from a Sine wave. Then you can choose to add one or more listening components that use the Sine data to either change the GameObjectâs color or move it etc., these being modular and separate classes.
So you could have a Cube that has the Sine component attached, as well as the ColorChanger and the MoveMe component also attached listening to the Sine component. You could then have a Sphere elsewhere in the scene graph that has a similar setup but with differing color and position values.
The Sine component would send a message VALUE_CHANGED, and the other components attached to the same GameObject would respond, but only to that instance of the Sine component and not others elsewhere in the scene.
Page 15 of the Userâs Guide is an example of sending a message to yourself. It doesnât matter that the ColorChanger or MoveMe are different components on the same GO⌠they are still listening for the GO (based on the GOâs nameâŚso that needs to be unique).
If you need something more granular, Iâd just use the standard message filter. Since the filter is a string, you can use a unique GO value and some additional qualifier.
HoweverâŚ
If I follow your example, the receivers know there is only one sender and they are tightly connected to him. If thatâs right, you could also just use a delegate. So at Start() your ColorChanger and MoveMe would look for the SineWave on the gameObject property. If found, they would register with the SineWaveâs delegate. When SineWave calls its delegate only the ColorChanger or MoveMe on that GO is called. Thatâs the cleaner approach.
The MD becomes more helpful when the receivers donât know who the senders are or donât have access to them. In this case, they are actually tightly bound by the same âgameObjectâ property.
I just got this asset on lv11 and still a Unity beginner (even after several years) so Iâm not familiar with the built-in messaging system. What are the actual use cases for this? Do you have any demonstration videos?
It really is as simple as I show in the documentation examples.
Since itâs a messaging system, itâs really up to you how you use it. Its a way of letting objects communicate even if they really donât know who they are communicating to. You could use it for combat or health changes, trap resolution, entity state notifications, etc.
If you google for âmessage handlingâ or âmessage dispatchingâ, youâll find lots more info on the subject.
One comment Iâd make is never force yourself to use something just because itâs there. If your game has tightly coupled objects, you may not need message dispatching. However, if you find your objects have references to lots of other objects all over the place⌠message dispatching is a good design.
I am currently building a custom message class and instead of using MyCustomMessage.cs, I created one that I named sbx_MouseEvent.cs, copy pasted the code and changed all references in the script to the new class. Everything works well, and by looking at the code I assume that a list of mouse event messages is created as new ones are instantiated, and it is up to me to properly use OnRelease() to properly dispose of each messages when I am done. This function, amongst other things, sets the IsSent and IsHandled flags to false, so in theory any other listener still waiting for the message after OnRelease has been called should NOT receive it. This part doesnât work, nor directly setting the IsHandled() flag in a listener to achieve the same effect. But in the documentation, you clearly state that it should be the case.
I think Iâm following. If youâre wanting to pool your custom messages (like I do with MyCustomMessage.cs), you will have to call Release() in order to release your instance back to the pool for re-use. In the case of MyCustomMessage, thereâs a custom pool for that message type.
If youâve got a lot of messages flying around, using a pool is way more efficient than creating a new instance each time.
The UI.cs file shows an example of my message handler doing this⌠see OnCustomMessageReceived().
Actually, it sets them to âtrueâ
See Message.cs lines 145 and 166,
See MyCustomMessage.cs lines 60 and 81
I think the thing you missed is that your listener has to respect the âIsHandledâ flag. In the documentation (page 12), I wrote: âThis kind of âIsHandledâ logic is for you to code as needed.â
What I mean is that all listeners will get the message (based on filters and such). You need logic in your listeners to watch the IsHandled flag and not process if itâs set to true. This gives you the ability to ignore the IsHandled flag if you need. You have complete control.
Yeah, totally right about the flags, my bad⌠Thanks for the quick reply and the email as well. Just to clarify: is one pool created for each each custom message I create (i.e. replacing MyCustomMessage.cs by MyClass.cs and modifying the MyCustomMessage script so that MyClass is used in all its methods instead) or is there a common pool that regroups all custom messages deriving from the message class?
I like the system so far. Will leave positive review.
Technically, you could use a single base-class pool. However, when you call Allocate() you might not get the exact type back that youâd expect. So, itâs better that each type have their own pool.
Actually, I do have another question. When working with multiple listeners, how do I know when to call the release() function? There should be some way to query if all listeners have processed the message? If I call it too early, it sets my eventâs variables and properties to 0, which can potentially lead to all kinds of problems for subsequent listeners (for that same event). And if I never call it at all, does this simply mean that the eventâs properties are never reinitialized? There should be an option to automatically release a message once all listeners have processed it? Am I making any sense? Or wouldnât we be better off creating a new object from a custom class and passing that on instead?
Just a bit hesitant here as to the proper bullet proof way to properly release messages.
For delayed messages, you could add a semaphore or counter. But, I think I can implement a more elegant way.
Iâve added a âRelease()â method to the IMessage interface. So, now after I send all delayed messages, Iâll check the âIsHandledâ flag. If set, Iâll call the Release() method of your message. In there, you can clean up and release as needed.
In the case where you allocate an instant message (like above), youâll still need to release it. However, you can replace line #8 with:
Great idea about adding it to the interface, this way I can just know delayed messages will be automatically reinitialized based on what I put it my Release() method.
But Iâm not quite sure about calling Release() during the SendMessage(), since during the release all variables would be reset to set to (as Clear() is called as part of the Release() function). So wouldnât all variables contained in the custom message class be set to 0 before any listener would get a chance to get the real value?
Thanks for your answers.
Edit: ok nevermind about the first part, just did your example and the sequencing works, although Iâm guessing in some edge cases there will still be listeners waiting to process the message. Iâll let you know if that happens, not quite sure how multiple listeners are handled in the stack and in terms of order of execution.
It would be fine. For this case, weâre really only talking about messages with a delayâŚ
When the delay expires the message is sent to all listeners. After all listeners have processed, I look to see if the âIsHandledâ flag is true. If so, then Iâll call Release() on the message.
So, the listeners will all have a chance to respond before I clear any values.