MythicalWar/Assets/Invector-3rdPersonController/Basic Locomotion/Scripts/Generic/PlayablesBehaviours/vAnimatorEventPDM.cs

200 lines
7.5 KiB
C#

#if INVECTOR_PLAYABLESANIMATOR
using System.Collections.Generic;
using InvectorCore.Playables;
using UnityEngine;
namespace Invector.vEventSystems
{
/// <summary>
/// PlayablesAnimator counterpart of <see cref="vAnimatorEvent"/>. Fires named events at a
/// normalized time, on state enter, or on state exit.
/// </summary>
/// <remarks>
/// Where the Animator version keeps its own subscriber list per behaviour instance, this one
/// routes through the PDM's named state event bus (<c>PlayablesAnimator.TriggerStateEvent</c>),
/// so a listener subscribes to the animator once instead of to each behaviour. Use the
/// <c>RegisterEvent</c> / <c>RemoveEvent</c> extensions below, which mirror the Animator API.
/// </remarks>
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;
}
/// <summary>True when playback has just crossed this trigger's point in the current loop.</summary>
public bool ShouldFire(float stateNormalizedTime)
{
float clamped = Mathf.Clamp(stateNormalizedTime, 0f, loopCount + 1f);
if (clamped < loopCount + normalizedTime)
return false;
loopCount++;
return true;
}
}
public List<vAnimatorEventTrigger> eventTriggers = new List<vAnimatorEventTrigger>();
[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;
/// <summary>
/// <see cref="vAnimatorEventExtencion"/> equivalent for a <see cref="PlayablesAnimator"/>.
/// </summary>
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<vEventSystems.vAnimatorEvent.OnTriggerEvent,
PlayablesAnimator.PlayablesNamedStateEventHandler>> relays = new();
/// <summary>
/// Add an event listener for <paramref name="eventName"/> fired by
/// <see cref="vEventSystems.vAnimatorEventPDM"/> or by an authored PDM state event.
/// </summary>
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;
}
/// <summary>Remove a listener added by <see cref="RegisterEvent"/>.</summary>
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