// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Audio; using UnityEngine.Playables; using Object = UnityEngine.Object; namespace Animancer { /// [Pro-Only] An which plays a . /// /// Documentation: Timeline /// /// https://kybernetik.com.au/animancer/api/Animancer/PlayableAssetState /// public class PlayableAssetState : AnimancerState, ICopyable { /************************************************************************************************************************/ /// An that creates a . public interface ITransition : ITransition { } /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ /// The which this state plays. private PlayableAsset _Asset; /// The which this state plays. public PlayableAsset Asset { get => _Asset; set => ChangeMainObject(ref _Asset, value); } /// The which this state plays. public override Object MainObject { get => _Asset; set => _Asset = (PlayableAsset)value; } /************************************************************************************************************************/ private float _Length; /// The . public override float Length => _Length; /************************************************************************************************************************/ /// protected override void OnSetIsPlaying() { var inputCount = _Playable.GetInputCount(); for (int i = 0; i < inputCount; i++) { var playable = _Playable.GetInput(i); if (!playable.IsValid()) continue; if (IsPlaying) playable.Play(); else playable.Pause(); } } /************************************************************************************************************************/ /// IK cannot be dynamically enabled on a . public override void CopyIKFlags(AnimancerNode copyFrom) { } /************************************************************************************************************************/ /// IK cannot be dynamically enabled on a . public override bool ApplyAnimatorIK { get => false; set { #if UNITY_ASSERTIONS if (value) OptionalWarning.UnsupportedIK.Log( $"IK cannot be dynamically enabled on a {nameof(PlayableAssetState)}.", Root?.Component); #endif } } /************************************************************************************************************************/ /// IK cannot be dynamically enabled on a . public override bool ApplyFootIK { get => false; set { #if UNITY_ASSERTIONS if (value) OptionalWarning.UnsupportedIK.Log( $"IK cannot be dynamically enabled on a {nameof(PlayableAssetState)}.", Root?.Component); #endif } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Methods /************************************************************************************************************************/ /// Creates a new to play the `asset`. /// The `asset` is null. public PlayableAssetState(PlayableAsset asset) { if (asset == null) throw new ArgumentNullException(nameof(asset)); _Asset = asset; } /************************************************************************************************************************/ /// protected override void CreatePlayable(out Playable playable) { playable = _Asset.CreatePlayable(Root._Graph, Root.Component.gameObject); playable.SetDuration(9223372.03685477);// https://github.com/KybernetikGames/animancer/issues/111 _Length = (float)_Asset.duration; if (!_HasInitializedBindings) InitializeBindings(); } /************************************************************************************************************************/ private IList _Bindings; private bool _HasInitializedBindings; /************************************************************************************************************************/ /// The objects controlled by each track in the asset. public IList Bindings { get => _Bindings; set { _Bindings = value; InitializeBindings(); } } /************************************************************************************************************************/ /// Sets the . public void SetBindings(params Object[] bindings) { Bindings = bindings; } /************************************************************************************************************************/ private void InitializeBindings() { if (Root == null) return; _HasInitializedBindings = true; Validate.AssertPlayable(this); var graph = Root._Graph; var bindableIndex = 0; var bindableCount = _Bindings != null ? _Bindings.Count : 0; foreach (var binding in _Asset.outputs) { GetBindingDetails(binding, out var trackName, out var trackType, out var isMarkers); var bindable = bindableIndex < bindableCount ? _Bindings[bindableIndex] : null; #if UNITY_ASSERTIONS if (!isMarkers && trackType != null && bindable != null && !trackType.IsAssignableFrom(bindable.GetType())) { Debug.LogError( $"Binding Type Mismatch: bindings[{bindableIndex}] is '{bindable}'" + $" but should be a {trackType.FullName} for {trackName}", Root.Component as Object); bindableIndex++; continue; } #endif var playable = _Playable.GetInput(bindableIndex); if (trackType == typeof(Animator))// AnimationTrack. { if (bindable != null) { #if UNITY_ASSERTIONS if (bindable == Root.Component?.Animator) Debug.LogError( $"{nameof(PlayableAsset)} tracks should not be bound to the same {nameof(Animator)} as" + $" Animancer. Leaving the binding of the first Animation Track empty will automatically" + $" apply its animation to the object being controlled by Animancer.", Root.Component as Object); #endif var playableOutput = AnimationPlayableOutput.Create(graph, trackName, (Animator)bindable); playableOutput.SetReferenceObject(binding.sourceObject); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); } } else if (trackType == typeof(AudioSource))// AudioTrack. { if (bindable != null) { var playableOutput = AudioPlayableOutput.Create(graph, trackName, (AudioSource)bindable); playableOutput.SetReferenceObject(binding.sourceObject); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); } } else if (isMarkers)// Markers. { var animancer = Root.Component as Component; var playableOutput = ScriptPlayableOutput.Create(graph, trackName); playableOutput.SetReferenceObject(binding.sourceObject); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); playableOutput.SetUserData(animancer); var receivers = ObjectPool.AcquireList(); animancer.GetComponents(receivers); for (int i = 0; i < receivers.Count; i++) playableOutput.AddNotificationReceiver(receivers[i]); ObjectPool.Release(receivers); continue;// Don't increment the bindingIndex. } else// ActivationTrack, ControlTrack, PlayableTrack, SignalTrack. { var playableOutput = ScriptPlayableOutput.Create(graph, trackName); playableOutput.SetReferenceObject(binding.sourceObject); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); playableOutput.SetUserData(bindable); if (bindable is INotificationReceiver receiver) playableOutput.AddNotificationReceiver(receiver); } bindableIndex++; } } /************************************************************************************************************************/ /// Should the `binding` be skipped when determining how to map the ? public static void GetBindingDetails(PlayableBinding binding, out string name, out Type type, out bool isMarkers) { name = binding.streamName; type = binding.outputTargetType; isMarkers = type == typeof(GameObject) && name == "Markers"; } /************************************************************************************************************************/ /// public override void Destroy() { _Asset = null; base.Destroy(); } /************************************************************************************************************************/ /// public override AnimancerState Clone(AnimancerPlayable root) { var clone = new PlayableAssetState(_Asset); clone.SetNewCloneRoot(root); ((ICopyable)clone).CopyFrom(this); return clone; } /// void ICopyable.CopyFrom(PlayableAssetState copyFrom) { _Length = copyFrom._Length; ((ICopyable)this).CopyFrom(copyFrom); } /************************************************************************************************************************/ /// protected override void AppendDetails(StringBuilder text, string separator) { base.AppendDetails(text, separator); text.Append(separator).Append($"{nameof(Bindings)}: "); int count; if (_Bindings == null) { text.Append("Null"); count = 0; } else { count = _Bindings.Count; text.Append('[') .Append(count) .Append(']'); } text.Append(_HasInitializedBindings ? " (Initialized)" : " (Not Initialized)"); for (int i = 0; i < count; i++) { text.Append(separator) .Append($"{nameof(Bindings)}[") .Append(i) .Append("] = ") .Append(AnimancerUtilities.ToStringOrNull(_Bindings[i])); } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }