#if INVECTOR_PLAYABLESANIMATOR using System.Collections.Generic; using InvectorCore.Playables; using UnityEngine; namespace Invector.vEventSystems { /// /// PlayablesAnimator counterpart of . Fires named events at a /// normalized time, on state enter, or on state exit. /// /// /// Where the Animator version keeps its own subscriber list per behaviour instance, this one /// routes through the PDM's named state event bus (PlayablesAnimator.TriggerStateEvent), /// so a listener subscribes to the animator once instead of to each behaviour. Use the /// RegisterEvent / RemoveEvent extensions below, which mirror the Animator API. /// public class vAnimatorEventPDM : PlayablesStateBehaviour { [System.Serializable] public class vAnimatorEventTrigger { public enum vAnimatorEventTriggerType { NormalizedTime, EnterState, ExitState } public string eventName = "New Event"; public vAnimatorEventTriggerType eventTriggerType = vAnimatorEventTriggerType.NormalizedTime; public float normalizedTime; [System.NonSerialized] private int loopCount; public void Init() { loopCount = 0; } /// True when playback has just crossed this trigger's point in the current loop. public bool ShouldFire(float stateNormalizedTime) { float clamped = Mathf.Clamp(stateNormalizedTime, 0f, loopCount + 1f); if (clamped < loopCount + normalizedTime) return false; loopCount++; return true; } } public List eventTriggers = new List(); [System.NonSerialized] private bool hasNormalizedEvents; public bool HasEvent(string eventName) { return eventTriggers != null && eventTriggers.Exists(e => e.eventName.Equals(eventName)); } public override void OnStateEnter(in PlayablesStateContext context) { if (eventTriggers == null) return; hasNormalizedEvents = false; for (int i = 0; i < eventTriggers.Count; i++) { vAnimatorEventTrigger trigger = eventTriggers[i]; if (trigger == null) continue; if (trigger.eventTriggerType == vAnimatorEventTrigger.vAnimatorEventTriggerType.EnterState) { Fire(context, trigger.eventName); } else if (trigger.eventTriggerType == vAnimatorEventTrigger.vAnimatorEventTriggerType.NormalizedTime) { hasNormalizedEvents = true; trigger.Init(); if (trigger.ShouldFire(context.normalizedTime)) Fire(context, trigger.eventName); } } } public override void OnStateUpdate(in PlayablesStateContext context) { if (!hasNormalizedEvents || eventTriggers == null) return; if (!context.IsLooping && context.normalizedTime > 1f) return; for (int i = 0; i < eventTriggers.Count; i++) { vAnimatorEventTrigger trigger = eventTriggers[i]; if (trigger == null || trigger.eventTriggerType != vAnimatorEventTrigger.vAnimatorEventTriggerType.NormalizedTime) continue; if (trigger.ShouldFire(context.normalizedTime)) Fire(context, trigger.eventName); } } public override void OnStateExit(in PlayablesStateContext context) { if (eventTriggers == null) return; for (int i = 0; i < eventTriggers.Count; i++) { vAnimatorEventTrigger trigger = eventTriggers[i]; if (trigger != null && trigger.eventTriggerType == vAnimatorEventTrigger.vAnimatorEventTriggerType.ExitState) Fire(context, trigger.eventName); } } private static void Fire(in PlayablesStateContext context, string eventName) { if (context.Animator != null && !string.IsNullOrEmpty(eventName)) context.Animator.TriggerStateEvent(eventName, in context); } } } namespace Invector { using System; using PlayablesStateContext = InvectorCore.Playables.PlayablesStateContext; /// /// equivalent for a . /// public static class vPlayablesAnimatorEventExtension { // One relay per (animator, callback) pair so RemoveEvent can unsubscribe the exact delegate // that RegisterEvent added — the animator's event carries the name as an argument, so the // name filter lives in the relay rather than in the subscription. private static readonly Dictionary< PlayablesAnimator, Dictionary> relays = new(); /// /// Add an event listener for fired by /// or by an authored PDM state event. /// public static void RegisterEvent( this PlayablesAnimator animator, string eventName, vEventSystems.vAnimatorEvent.OnTriggerEvent onTriggerEventAction) { if (animator == null || onTriggerEventAction == null || string.IsNullOrEmpty(eventName)) return; RemoveEvent(animator, eventName, onTriggerEventAction); PlayablesAnimator.PlayablesNamedStateEventHandler relay = (string firedName, in PlayablesStateContext context) => { if (string.Equals(firedName, eventName, StringComparison.Ordinal)) onTriggerEventAction(firedName); }; if (!relays.TryGetValue(animator, out var byCallback)) { byCallback = new Dictionary< vEventSystems.vAnimatorEvent.OnTriggerEvent, PlayablesAnimator.PlayablesNamedStateEventHandler>(); relays[animator] = byCallback; } byCallback[onTriggerEventAction] = relay; animator.OnPlayablesStateEvent += relay; } /// Remove a listener added by . public static void RemoveEvent( this PlayablesAnimator animator, string eventName, vEventSystems.vAnimatorEvent.OnTriggerEvent onTriggerEventAction) { if (animator == null || onTriggerEventAction == null) return; if (!relays.TryGetValue(animator, out var byCallback) || !byCallback.TryGetValue(onTriggerEventAction, out var relay)) return; animator.OnPlayablesStateEvent -= relay; byCallback.Remove(onTriggerEventAction); if (byCallback.Count == 0) relays.Remove(animator); } } } #endif