// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 Kybernetik // using System; using System.Collections.Generic; 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 sealed class PlayableAssetState : AnimancerState { /************************************************************************************************************************/ /// 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 node) { } /************************************************************************************************************************/ /// 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; var output = _Asset.outputs.GetEnumerator(); var graph = Root._Graph; var bindingIndex = 0; var bindingCount = _Bindings != null ? _Bindings.Count : 0; while (output.MoveNext()) { GetBindingDetails(output.Current, out var name, out var type, out var isMarkers); var binding = bindingIndex < bindingCount ? _Bindings[bindingIndex] : null; #if UNITY_ASSERTIONS if (type != null && !(binding is null) && !type.IsAssignableFrom(binding.GetType())) { Debug.LogError( $"Binding Type Mismatch: bindings[{bindingIndex}] is '{binding}' but should be a {type.FullName} for {name}", Root.Component as Object); bindingIndex++; continue; } Validate.AssertPlayable(this); #endif var playable = _Playable.GetInput(bindingIndex); if (type == typeof(Animator))// AnimationTrack. { if (binding != null) { #if UNITY_ASSERTIONS if (binding == Root.Component?.Animator) Debug.LogError( $"{nameof(PlayableAsset)} tracks should not be bound to the same {nameof(Animator)} as" + $" Animancer. Leaving binding {bindingIndex} empty will automatically apply its animation" + $" to the object being controlled by Animancer.", Root.Component as Object); #endif var playableOutput = AnimationPlayableOutput.Create(graph, name, (Animator)binding); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); } } else if (type == typeof(AudioSource))// AudioTrack. { if (binding != null) { var playableOutput = AudioPlayableOutput.Create(graph, name, (AudioSource)binding); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); } } else if (isMarkers)// Markers. { var animancer = Root.Component as Component; var playableOutput = ScriptPlayableOutput.Create(graph, name); playableOutput.SetUserData(animancer); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); foreach (var receiver in animancer.GetComponents()) { playableOutput.AddNotificationReceiver(receiver); } continue;// Don't increment the bindingIndex. } else// ActivationTrack, ControlTrack, PlayableTrack, SignalTrack. { var playableOutput = ScriptPlayableOutput.Create(graph, name); playableOutput.SetUserData(binding); playableOutput.SetSourcePlayable(playable); playableOutput.SetWeight(1); } bindingIndex++; } } /************************************************************************************************************************/ /// 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(); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }