2025-05-09 15:40:34 +08:00

321 lines
13 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Animancer
{
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerState
partial class AnimancerState
{
/************************************************************************************************************************/
/// <summary>The system which manages the <see cref="SharedEvents"/>.</summary>
private AnimancerEvent.Dispatcher _EventDispatcher;
/************************************************************************************************************************/
/// <summary>
/// Events which will be triggered while this state plays
/// based on its <see cref="NormalizedTime"/>.
/// </summary>
///
/// <remarks>
/// This property tries to ensure that the event sequence is only referenced by this state.
/// <list type="bullet">
/// <item>
/// If the reference was <c>null</c>,
/// a new sequence will be created.
/// </item>
/// <item>
/// If a reference was assigned to <see cref="SharedEvents"/>,
/// it will be cloned so this state owns the clone.
/// </item>
/// </list>
/// <para></para>
/// Using <see cref="Events(object)"/> or <see cref="Events(object, out AnimancerEvent.Sequence)"/>
/// is often safer than this property since they help detect if multiple scripts are using the same
/// state which could lead to unexpected bugs if they each assign conflicting callbacks.
/// <para></para>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// </remarks>
public AnimancerEvent.Sequence OwnedEvents
{
get
{
_EventDispatcher ??= new(this);
_EventDispatcher.InitializeEvents(out var events);
return events;
}
set
{
if (value != null)
(_EventDispatcher ??= new(this)).SetEvents(value, true);
else
_EventDispatcher = null;
}
}
/************************************************************************************************************************/
/// <summary>
/// Events which will be triggered while this state plays
/// based on its <see cref="NormalizedTime"/>.
/// </summary>
///
/// <remarks>
/// This reference is <c>null</c> by default and once assigned it may be shared by multiple states.
/// <para></para>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// </remarks>
public AnimancerEvent.Sequence SharedEvents
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _EventDispatcher?.Events;
set
{
if (value != null)
(_EventDispatcher ??= new(this)).SetEvents(value, false);
else
_EventDispatcher = null;
}
}
/************************************************************************************************************************/
/// <summary>Have the <see cref="SharedEvents"/> or <see cref="OwnedEvents"/> been initialized?</summary>
/// <remarks>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// </remarks>
public bool HasEvents
=> _EventDispatcher != null;
/************************************************************************************************************************/
/// <summary>Have the <see cref="OwnedEvents"/> been initialized?</summary>
/// <remarks>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// </remarks>
public bool HasOwnedEvents
=> _EventDispatcher != null
&& _EventDispatcher.HasOwnEvents;
/************************************************************************************************************************/
/// <summary>
/// If the <see cref="OwnedEvents"/> haven't been initialized yet,
/// this method gets them and returns <c>true</c>.
/// </summary>
///
/// <remarks>
/// This method tries to ensure that the event sequence is only referenced by this state.
/// <list type="bullet">
/// <item>
/// If the reference was <c>null</c>,
/// a new sequence will be created.
/// </item>
/// <item>
/// If a reference was assigned to <see cref="SharedEvents"/>,
/// it will be cloned so this state owns the clone.
/// </item>
/// </list>
/// In both of those cases, this method returns <c>true</c>
/// to indicate that the caller should initialize their event callbacks.
/// <para></para>
/// Also calls <see cref="AssertOwnership"/>.
/// <para></para>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// <para></para>
/// <strong>Example:</strong>
/// <code>
/// public static readonly StringReference EventName = "Event Name";
///
/// ...
///
/// AnimancerState state = animancerComponent.Play(animation);
/// if (state.Events(this, out AnimancerEvent.Sequence events))
/// {
/// events.SetCallback(EventName, OnAnimationEvent);
/// events.OnEnd = OnAnimationEnded;
/// }
/// </code>
/// If you only need to initialize the End Event,
/// consider using <see cref="Events(object)"/> instead.
/// </remarks>
public bool Events(object owner, out AnimancerEvent.Sequence events)
{
AssertOwnership(owner);
_EventDispatcher ??= new(this);
return _EventDispatcher.InitializeEvents(out events);
}
/************************************************************************************************************************/
/// <summary>
/// If the <see cref="OwnedEvents"/> haven't been initialized yet,
/// this method gets them and returns <c>true</c>.
/// </summary>
///
/// <remarks>
/// This method tries to ensure that the event sequence is only referenced by this state.
/// <list type="bullet">
/// <item>
/// If the reference was <c>null</c>,
/// a new sequence will be created.
/// </item>
/// <item>
/// If a reference was assigned to <see cref="SharedEvents"/>,
/// it will be cloned so this state owns the clone.
/// </item>
/// </list>
/// <para></para>
/// Also calls <see cref="AssertOwnership"/>.
/// <para></para>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// <para></para>
/// <strong>Example:</strong>
/// <code>
/// AnimancerState state = animancerComponent.Play(animation);
/// state.Events(this).OnEnd ??= OnAnimationEnded;
/// </code>
/// If you need to initialize more than just the End Event,
/// consider using <see cref="Events(object, out AnimancerEvent.Sequence)"/> instead.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AnimancerEvent.Sequence Events(object owner)
{
Events(owner, out var events);
return events;
}
/************************************************************************************************************************/
/// <summary>Copies the contents of the <see cref="_EventDispatcher"/>.</summary>
private void CopyEvents(AnimancerState copyFrom, CloneContext context)
{
if (copyFrom._EventDispatcher != null)
{
var original = copyFrom._EventDispatcher.Events;
var events = context.GetCloneOrOriginal(original);
if (events != null)
{
_EventDispatcher ??= new(this);
_EventDispatcher.SetEvents(events, false);
if (events == original)
copyFrom._EventDispatcher.DismissEventOwnership();
return;
}
}
_EventDispatcher = null;
}
/************************************************************************************************************************/
/// <summary>Should events be raised on a state which is currently fading out?</summary>
/// <remarks>
/// Default <c>false</c>.
/// <para></para>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
/// Animancer Events</see>
/// </remarks>
public static bool RaiseEventsDuringFadeOut { get; set; }
/// <summary>[Internal] Should this state check for events to invoke?</summary>
internal bool ShouldRaiseEvents
=> TargetWeight > 0
|| RaiseEventsDuringFadeOut;
/************************************************************************************************************************/
/// <summary>
/// Checks if any events should be invoked based on the current time of this state.
/// </summary>
protected internal virtual void UpdateEvents()
=> _EventDispatcher?.UpdateEvents(ShouldRaiseEvents);
/// <summary>
/// Checks if any events should be invoked on the `parent` and its children recursively.
/// </summary>
public static void UpdateEventsRecursive(AnimancerState parent)
=> UpdateEventsRecursive(
parent,
parent.ShouldRaiseEvents);
/// <summary>
/// Checks if any events should be invoked on the `parent` and its children recursively.
/// </summary>
public static void UpdateEventsRecursive(AnimancerState parent, bool raiseEvents)
{
parent._EventDispatcher?.UpdateEvents(raiseEvents);
for (int i = parent.ChildCount - 1; i >= 0; i--)
UpdateEventsRecursive(parent.GetChild(i), raiseEvents);
}
/************************************************************************************************************************/
#if UNITY_ASSERTIONS
/************************************************************************************************************************/
/// <summary>[Assert-Only]
/// Returns <c>null</c> if Animancer Events will work properly on this type of state,
/// or a message explaining why they might not work.
/// </summary>
protected internal virtual string UnsupportedEventsMessage
=> null;
/************************************************************************************************************************/
/// <summary>[Assert-Only] An optional reference to the object that owns this state.</summary>
public object Owner { get; private set; }
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
/// <summary>[Assert-Conditional]
/// Sets the <see cref="Owner"/> and asserts that it wasn't already set to a different object.
/// </summary>
/// <remarks>This helps detect if multiple scripts attempt to manage the same state.</remarks>
[System.Diagnostics.Conditional(Strings.Assertions)]
public void AssertOwnership(object owner)
{
#if UNITY_ASSERTIONS
if (Owner == owner)
return;
if (Owner != null)
{
Debug.LogError(
$"Multiple objects have asserted ownership over the state '{ToString()}':" +
$"\n• Old Owner: {AnimancerUtilities.ToStringOrNull(Owner)}" +
$"\n• New Owner: {AnimancerUtilities.ToStringOrNull(owner)}" +
$"\n• State: {GetPath()}" +
$"\n• Graph: {Graph?.GetDescription("\n ")}",
Graph?.Component as Object);
}
Owner = owner;
#endif
}
/************************************************************************************************************************/
}
}