// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik // using Animancer.Units; using System; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer { /// /// A serializable which can create a particular type of /// when passed into . /// /// /// Documentation: /// /// Transitions /// /// https://kybernetik.com.au/animancer/api/Animancer/Transition_1 [Serializable] public abstract class Transition : IPolymorphic, ITransition, ITransition, ICloneable>, ICopyable> where TState : AnimancerState { /************************************************************************************************************************/ [SerializeField] [Tooltip(Strings.Tooltips.FadeDuration)] [AnimationTime(AnimationTimeAttribute.Units.Seconds, Rule = Validate.Value.IsNotNegative)] [DefaultFadeValue] private float _FadeDuration = AnimancerGraph.DefaultFadeDuration; /// /// [] /// Thrown when setting the value to a negative number. public float FadeDuration { get => _FadeDuration; set { if (value < 0) throw new ArgumentOutOfRangeException( nameof(value), $"{nameof(FadeDuration)} must not be negative"); _FadeDuration = value; } } /************************************************************************************************************************/ [SerializeField] [Tooltip(Strings.Tooltips.OptionalSpeed)] [AnimationSpeed(DisabledText = Strings.Tooltips.SpeedDisabled)] [DefaultValue(1f, -1f)] private float _Speed = 1; /// [] /// Determines how fast the animation plays (1x = normal speed, 2x = double speed). /// /// /// This sets the when this transition is played. /// public float Speed { get => _Speed; set => _Speed = value; } /************************************************************************************************************************/ /// /// Returns false unless overridden. public virtual bool IsLooping => false; /// public virtual float NormalizedStartTime { get => float.NaN; set { } } /// public abstract float MaximumLength { get; } /************************************************************************************************************************/ [SerializeField, Tooltip(Strings.ProOnlyTag + "Events which will be triggered as the animation plays")] private AnimancerEvent.Sequence.Serializable _Events; /// /// This property returns the . public virtual AnimancerEvent.Sequence Events { get => (_Events ??= new()).Events; set => (_Events ??= new()).Events = value; } /// public AnimancerEvent.Sequence.Serializable SerializedEvents { get => _Events; set => _Events = value; } /************************************************************************************************************************/ /// /// The state that was created by this object. Specifically, this is the state that was most recently /// passed into (usually by ). /// /// You can use or /// to get or create the state for a /// specific object. /// /// is simply a shorthand for casting this to . /// public AnimancerState BaseState { get; private set; } /************************************************************************************************************************/ private TState _State; /// /// The state that was created by this object. Specifically, this is the state that was most recently /// passed into (usually by ). /// /// /// /// You can use or /// /// to get or create the state for a specific object. /// /// This property is shorthand for casting the to . /// /// /// /// The is not actually a . /// This should only happen if a different type of state was created by something else /// and registered using the , /// causing this to pass that state into /// instead of calling /// to make the correct type of state. /// public TState State { get => _State ??= (TState)BaseState; protected set => BaseState = _State = value; } /************************************************************************************************************************/ /// /// Returns true unless overridden. public virtual bool IsValid => true; /// The which the created state will be registered with. /// Returns this unless overridden. public virtual object Key => this; /// /// Returns unless overridden. public virtual FadeMode FadeMode => default; /************************************************************************************************************************/ /// public abstract TState CreateState(); /// AnimancerState ITransition.CreateState() => CreateAndInitializeState(); /// Calls and assigns the to the state. public TState CreateAndInitializeState() { var state = CreateState(); AnimancerState.SetExpectFade(state, _FadeDuration); State = state; state.SharedEvents = _Events; return state; } /************************************************************************************************************************/ /// public virtual void Apply(AnimancerState state) { #if UNITY_ASSERTIONS if (state.MainObject != MainObject) { OptionalWarning.MainObjectMismatch.Log( $"A state.{nameof(MainObject)} doesn't match the" + $" transition.{nameof(MainObject)} being applied to it." + $" transition.{nameof(ReconcileMainObject)} must be called" + $" for every state created by the transition" + $" after its {nameof(MainObject)} is changed." + $" This includes {nameof(ClipTransition)}.{nameof(ClipTransition.Clip)}," + $" {nameof(ControllerTransition)}.{nameof(ControllerTransition.Controller)}, and" + $" {nameof(PlayableAssetTransition)}.{nameof(PlayableAssetTransition.Asset)}" + $"\n• State: {state}" + $"\n• State.{nameof(MainObject)}: {state.MainObject}" + $"\n• Transition.{nameof(MainObject)}: {MainObject}" + $"\n• Component: {state.Graph?.Component}", state.Graph?.Component); } #endif if (_State != state) { _State = null; BaseState = state; } if (!float.IsNaN(_Speed)) state.Speed = _Speed; } /************************************************************************************************************************/ /// Applies the `normalizedStartTime` to the `state`. public static void ApplyNormalizedStartTime(AnimancerState state, float normalizedStartTime) { if (!float.IsNaN(normalizedStartTime)) state.NormalizedTime = normalizedStartTime; else if (!state.IsActive) state.NormalizedTime = AnimancerEvent.Sequence.GetDefaultNormalizedStartTime(state.Speed); } /************************************************************************************************************************/ /// The that the created state will have. public virtual Object MainObject { get; } /// The display name of this transition. public virtual string Name { get { var mainObject = MainObject; return mainObject != null ? mainObject.name : null; } } /// Returns the and type of this transition. public override string ToString() { var type = GetType().FullName; var name = Name; return name is null ? type : $"{name} ({type})"; } /************************************************************************************************************************/ /// /// If a state exists with its not matching the /// , this method returns a new state for the correct object. /// /// /// This method only applies to the state registered with the so /// if this transition is played on multiple different characters or used to create /// multiple states for the same character, this method must be called for each state. /// public AnimancerState ReconcileMainObject(AnimancerGraph animancer) => animancer.States.TryGet(this, out var state) ? ReconcileMainObject(state) : null; /************************************************************************************************************************/ /// /// If the doesn't match the , /// this method returns a new state for the correct object. /// /// /// If this transition is played on multiple different characters or used to create /// multiple states for the same character, this method must be called for each state. /// public AnimancerState ReconcileMainObject(AnimancerState state) { var newMainObject = MainObject; if (newMainObject == null) return state; var oldMainObject = state.MainObject; if (oldMainObject == newMainObject) return state; #if UNITY_ASSERTIONS if (oldMainObject == null) Debug.LogError( $"{state} had no {nameof(state.MainObject)} to change from.", state.Graph?.Component as Object); if (newMainObject == null) Debug.LogError( $"{this} has no {nameof(MainObject)} to change to.", state.Graph?.Component as Object); #endif // Change the old state's key to its object so we can get it back later. state.Key = oldMainObject; // If there was already a state for the new object, give it the correct key. if (state.Graph.States.TryGet(newMainObject, out var existingState)) { existingState.Key = Key; state = existingState; } else// Otherwise, create a state for the new object. { var layer = state.Layer; state = CreateState(); state._Key = Key; state.SetParent(layer); } _State = null; BaseState = state; return state; } /************************************************************************************************************************/ /// public abstract Transition Clone(CloneContext context); /// public virtual void CopyFrom(Transition copyFrom, CloneContext context) { _FadeDuration = copyFrom._FadeDuration; _Speed = copyFrom._Speed; _Events = copyFrom._Events.Clone(); } /************************************************************************************************************************/ } }