// 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 } /************************************************************************************************************************/ } }