// 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
{
/************************************************************************************************************************/
/// The system which manages the .
private AnimancerEvent.Dispatcher _EventDispatcher;
/************************************************************************************************************************/
///
/// Events which will be triggered while this state plays
/// based on its .
///
///
///
/// This property tries to ensure that the event sequence is only referenced by this state.
///
/// -
/// If the reference was null,
/// a new sequence will be created.
///
/// -
/// If a reference was assigned to ,
/// it will be cloned so this state owns the clone.
///
///
///
/// Using or
/// 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.
///
/// Documentation:
///
/// Animancer Events
///
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;
}
}
/************************************************************************************************************************/
///
/// Events which will be triggered while this state plays
/// based on its .
///
///
///
/// This reference is null by default and once assigned it may be shared by multiple states.
///
/// Documentation:
///
/// Animancer Events
///
public AnimancerEvent.Sequence SharedEvents
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _EventDispatcher?.Events;
set
{
if (value != null)
(_EventDispatcher ??= new(this)).SetEvents(value, false);
else
_EventDispatcher = null;
}
}
/************************************************************************************************************************/
/// Have the or been initialized?
///
/// Documentation:
///
/// Animancer Events
///
public bool HasEvents
=> _EventDispatcher != null;
/************************************************************************************************************************/
/// Have the been initialized?
///
/// Documentation:
///
/// Animancer Events
///
public bool HasOwnedEvents
=> _EventDispatcher != null
&& _EventDispatcher.HasOwnEvents;
/************************************************************************************************************************/
///
/// If the haven't been initialized yet,
/// this method gets them and returns true.
///
///
///
/// This method tries to ensure that the event sequence is only referenced by this state.
///
/// -
/// If the reference was null,
/// a new sequence will be created.
///
/// -
/// If a reference was assigned to ,
/// it will be cloned so this state owns the clone.
///
///
/// In both of those cases, this method returns true
/// to indicate that the caller should initialize their event callbacks.
///
/// Also calls .
///
/// Documentation:
///
/// Animancer Events
///
/// Example:
///
/// 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;
/// }
///
/// If you only need to initialize the End Event,
/// consider using instead.
///
public bool Events(object owner, out AnimancerEvent.Sequence events)
{
AssertOwnership(owner);
_EventDispatcher ??= new(this);
return _EventDispatcher.InitializeEvents(out events);
}
/************************************************************************************************************************/
///
/// If the haven't been initialized yet,
/// this method gets them and returns true.
///
///
///
/// This method tries to ensure that the event sequence is only referenced by this state.
///
/// -
/// If the reference was null,
/// a new sequence will be created.
///
/// -
/// If a reference was assigned to ,
/// it will be cloned so this state owns the clone.
///
///
///
/// Also calls .
///
/// Documentation:
///
/// Animancer Events
///
/// Example:
///
/// AnimancerState state = animancerComponent.Play(animation);
/// state.Events(this).OnEnd ??= OnAnimationEnded;
///
/// If you need to initialize more than just the End Event,
/// consider using instead.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AnimancerEvent.Sequence Events(object owner)
{
Events(owner, out var events);
return events;
}
/************************************************************************************************************************/
/// Copies the contents of the .
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;
}
/************************************************************************************************************************/
/// Should events be raised on a state which is currently fading out?
///
/// Default false.
///
/// Documentation:
///
/// Animancer Events
///
public static bool RaiseEventsDuringFadeOut { get; set; }
/// [Internal] Should this state check for events to invoke?
internal bool ShouldRaiseEvents
=> TargetWeight > 0
|| RaiseEventsDuringFadeOut;
/************************************************************************************************************************/
///
/// Checks if any events should be invoked based on the current time of this state.
///
protected internal virtual void UpdateEvents()
=> _EventDispatcher?.UpdateEvents(ShouldRaiseEvents);
///
/// Checks if any events should be invoked on the `parent` and its children recursively.
///
public static void UpdateEventsRecursive(AnimancerState parent)
=> UpdateEventsRecursive(
parent,
parent.ShouldRaiseEvents);
///
/// Checks if any events should be invoked on the `parent` and its children recursively.
///
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
/************************************************************************************************************************/
/// [Assert-Only]
/// Returns null if Animancer Events will work properly on this type of state,
/// or a message explaining why they might not work.
///
protected internal virtual string UnsupportedEventsMessage
=> null;
/************************************************************************************************************************/
/// [Assert-Only] An optional reference to the object that owns this state.
public object Owner { get; private set; }
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
/// [Assert-Conditional]
/// Sets the and asserts that it wasn't already set to a different object.
///
/// This helps detect if multiple scripts attempt to manage the same state.
[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
}
/************************************************************************************************************************/
}
}