How to make a custom signal receiver with emitter parameter
I need to make as many signals as the parameter I want to call my method. I want the int parameter to stick together with the emitter that also holds the SignalAsset, and call the method with that parameter.
This is a regular SignalReceiver
:
You could see a problem, I need to make as many signals as the parameter I want to call my method. I want the int
parameter to stick together with the emitter that also holds the SignalAsset
, and call this SignalReceived
method with that parameter.
Dynamic UnityEvent : UnityEvent<T>
Unfortunately SignalReceiver
is pairs of SignalAsset
-> UnityEvent
mapping. UnityEvent
could only be invoked parameterless or with parameter stated in the Inspector box. (not dynamic, cannot change at runtime)
This call for a custom SignalReceiver
that uses UnityEvent<T>
instead. You can see what it looks like by trying the component EventTrigger
of uGUI, where it has UnityEvent<BaseEventData>
. For example, in MyScript
, I have defined :
public void ParameterMatches(BaseEventData bed) { }
So it will show up on the "Dynamic" category at top.
When selected, there is no box below to enter any BaseEventData
value. Indicating that it is "dynamic" based on the caller.
However we could not just declare public UnityEvent<BaseEventData> ue;
and make that inspector appear, since Unity serialization didn't play well with generic. Instead, one nested level of generic will work :
public Why events;
[Serializable]
public class Why : UnityEvent<BaseEventData> { }
For simplicity I want to send a bool
with the emitter, to a dynamic UnityEvent<bool>
.
Custom SignalEmitter
Make a new file SignalEmitterWithBool.cs
with this :
public class SignalEmitterWithBool : SignalEmitter
{
public bool parameter;
}
You will notice that it is already working on Timeline GUI.
The option "From Signal Asset" also appears automatically. It knows how to drill down and find your first SignalAsset
field and assign that. It should go to the same place as regular SignalEmitter
.
To make things "smarter" you may do this, so you could make more emitters with other parameter type.
//Put this in its own file
public class SignalEmitterWithBool : ParameterizedEmitter<bool> { }
//Put this in its own file
public class SignalEmitterWithInt : ParameterizedEmitter<int> { }
public class ParameterizedEmitter<T> : SignalEmitter
{
public T parameter;
}
Here's what it looks like. The drawer draws my own parameter
field before drawing remaining SignalEmitter
fields. I have successfully parameterized the emitter with one custom bool
.
Custom SignalReceiver
This is a barebone SignalReceiver
that looks for only ParameterizedEmitter<bool>
notification from the bool emitter, and then match the SignalAsset
to the all pairs, then invoke each one's dynamic event.
Unfortunately the nice editor for SignalReceiver
is internal
and we could not use it on our own custom receiver. I decided to not subclass from SignalReceiver
and create from scratch, and live with uglier inspector.
public class SignalReceiverWithBool : MonoBehaviour, INotificationReceiver
{
public SignalAssetEventPair[] signalAssetEventPairs;
[Serializable]
public class SignalAssetEventPair
{
public SignalAsset signalAsset;
public ParameterizedEvent events;
[Serializable]
public class ParameterizedEvent : UnityEvent<bool> { }
}
public void OnNotify(Playable origin, INotification notification, object context)
{
if(notification is ParameterizedEmitter<bool> boolEmitter)
{
var matches = signalAssetEventPairs.Where(x => ReferenceEquals(x.signalAsset, boolEmitter.asset));
foreach (var m in matches)
{
m.events.Invoke(boolEmitter.parameter);
}
}
}
}
The bool
you see in this source code cannot be replaced with generic T
.
This is an uglier SignalAsset
-> UnityEvent<bool>
I was talking about. But at least it works.
Notice the UnityEvent<bool>
dropdown could successfully select AudioSource
's loop
property, and the same with any of your methods that with 1 bool
argument.
When the timeline run through my custom marker with parameter
set to true
, I could see AudioSource
's loop
goes from unchecked to checked.