using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Playables; namespace UnityEngine.Timeline { /// <summary> /// Listens for emitted signals and reacts depending on its defined reactions. /// </summary> /// A SignalReceiver contains a list of reactions. Each reaction is bound to a SignalAsset. /// When a SignalEmitter emits a signal, the SignalReceiver invokes the corresponding reaction. /// <seealso cref="UnityEngine.Timeline.SignalEmitter"/> /// <seealso cref="UnityEngine.Timeline.SignalAsset"/> [TimelineHelpURL(typeof(SignalReceiver))] public class SignalReceiver : MonoBehaviour, INotificationReceiver { [SerializeField] EventKeyValue m_Events = new EventKeyValue(); /// <summary> /// Called when a notification is sent. /// </summary> /// <param name="origin">The playable that sent the notification.</param> /// <param name="notification">The received notification. Only notifications of type <see cref="SignalEmitter"/> will be processed.</param> /// <param name="context">User defined data that depends on the type of notification. Uses this to pass necessary information that can change with each invocation.</param> public void OnNotify(Playable origin, INotification notification, object context) { var signal = notification as SignalEmitter; if (signal != null && signal.asset != null) { UnityEvent evt; if (m_Events.TryGetValue(signal.asset, out evt) && evt != null) { evt.Invoke(); } } } /// <summary> /// Defines a new reaction for a SignalAsset. /// </summary> /// <param name="asset">The SignalAsset for which the reaction is being defined.</param> /// <param name="reaction">The UnityEvent that describes the reaction.</param> /// <exception cref="ArgumentNullException">Thrown when the asset is null.</exception> /// <exception cref="ArgumentException">Thrown when the SignalAsset is already registered with this receiver.</exception> public void AddReaction(SignalAsset asset, UnityEvent reaction) { if (asset == null) throw new ArgumentNullException("asset"); if (m_Events.signals.Contains(asset)) throw new ArgumentException("SignalAsset already used."); m_Events.Append(asset, reaction); } /// <summary> /// Appends a null SignalAsset with a reaction specified by the UnityEvent. /// </summary> /// <param name="reaction">The new reaction to be appended.</param> /// <returns>The index of the appended reaction.</returns> /// <remarks>Multiple null assets are valid.</remarks> public int AddEmptyReaction(UnityEvent reaction) { m_Events.Append(null, reaction); return m_Events.events.Count - 1; } /// <summary> /// Removes the first occurrence of a SignalAsset. /// </summary> /// <param name="asset">The SignalAsset to be removed.</param> public void Remove(SignalAsset asset) { if (!m_Events.signals.Contains(asset)) { throw new ArgumentException("The SignalAsset is not registered with this receiver."); } m_Events.Remove(asset); } /// <summary> /// Gets a list of all registered SignalAssets. /// </summary> /// <returns>Returns a list of SignalAssets.</returns> public IEnumerable<SignalAsset> GetRegisteredSignals() { return m_Events.signals; } /// <summary> /// Gets the first UnityEvent associated with a SignalAsset. /// </summary> /// <param name="key">A SignalAsset defining the signal.</param> /// <returns>Returns the reaction associated with a SignalAsset. Returns null if the signal asset does not exist.</returns> public UnityEvent GetReaction(SignalAsset key) { UnityEvent ret; if (m_Events.TryGetValue(key, out ret)) { return ret; } return null; } /// <summary> /// Returns the count of registered SignalAssets. /// </summary> /// <returns></returns> public int Count() { return m_Events.signals.Count; } /// <summary> /// Replaces the SignalAsset associated with a reaction at a specific index. /// </summary> /// <param name="idx">The index of the reaction.</param> /// <param name="newKey">The replacement SignalAsset.</param> /// <exception cref="ArgumentException">Thrown when the replacement SignalAsset is already registered to this SignalReceiver.</exception> /// <remarks>The new SignalAsset can be null.</remarks> public void ChangeSignalAtIndex(int idx, SignalAsset newKey) { if (idx < 0 || idx > m_Events.signals.Count - 1) throw new IndexOutOfRangeException(); if (m_Events.signals[idx] == newKey) return; var alreadyUsed = m_Events.signals.Contains(newKey); if (newKey == null || m_Events.signals[idx] == null || !alreadyUsed) m_Events.signals[idx] = newKey; if (newKey != null && alreadyUsed) throw new ArgumentException("SignalAsset already used."); } /// <summary> /// Removes the SignalAsset and reaction at a specific index. /// </summary> /// <param name="idx">The index of the SignalAsset to be removed.</param> public void RemoveAtIndex(int idx) { if (idx < 0 || idx > m_Events.signals.Count - 1) throw new IndexOutOfRangeException(); m_Events.Remove(idx); } /// <summary> /// Replaces the reaction at a specific index with a new UnityEvent. /// </summary> /// <param name="idx">The index of the reaction to be replaced.</param> /// <param name="reaction">The replacement reaction.</param> /// <exception cref="ArgumentNullException">Thrown when the replacement reaction is null.</exception> public void ChangeReactionAtIndex(int idx, UnityEvent reaction) { if (idx < 0 || idx > m_Events.events.Count - 1) throw new IndexOutOfRangeException(); m_Events.events[idx] = reaction; } /// <summary> /// Gets the reaction at a specific index. /// </summary> /// <param name="idx">The index of the reaction.</param> /// <returns>Returns a reaction.</returns> public UnityEvent GetReactionAtIndex(int idx) { if (idx < 0 || idx > m_Events.events.Count - 1) throw new IndexOutOfRangeException(); return m_Events.events[idx]; } /// <summary> /// Gets the SignalAsset at a specific index /// </summary> /// <param name="idx">The index of the SignalAsset.</param> /// <returns>Returns a SignalAsset.</returns> public SignalAsset GetSignalAssetAtIndex(int idx) { if (idx < 0 || idx > m_Events.signals.Count - 1) throw new IndexOutOfRangeException(); return m_Events.signals[idx]; } // Required by Unity for the MonoBehaviour to have an enabled state private void OnEnable() { } [Serializable] class EventKeyValue { [SerializeField] List<SignalAsset> m_Signals = new List<SignalAsset>(); [SerializeField, CustomSignalEventDrawer] List<UnityEvent> m_Events = new List<UnityEvent>(); public bool TryGetValue(SignalAsset key, out UnityEvent value) { var index = m_Signals.IndexOf(key); if (index != -1) { value = m_Events[index]; return true; } value = null; return false; } public void Append(SignalAsset key, UnityEvent value) { m_Signals.Add(key); m_Events.Add(value); } public void Remove(int idx) { if (idx != -1) { m_Signals.RemoveAt(idx); m_Events.RemoveAt(idx); } } public void Remove(SignalAsset key) { var idx = m_Signals.IndexOf(key); if (idx != -1) { m_Signals.RemoveAt(idx); m_Events.RemoveAt(idx); } } public List<SignalAsset> signals { get { return m_Signals; } } public List<UnityEvent> events { get { return m_Events; } } } } }