using System.Collections.Generic; using UnityEngine.Playables; namespace UnityEngine.Animations.Rigging { /// /// RigBuilder is the root component that holds the Rigs that create an Animation Rigging hierarchy. /// Its purpose is to create the PlayableGraph that will be used in the associated Animator component to animate /// a character with constraints. /// [RequireComponent(typeof(Animator))] [DisallowMultipleComponent, ExecuteInEditMode, AddComponentMenu("Animation Rigging/Setup/Rig Builder")] [HelpURL("https://docs.unity3d.com/Packages/com.unity.animation.rigging@1.3/manual/RiggingWorkflow.html#rig-builder-component")] public class RigBuilder : MonoBehaviour, IAnimationWindowPreview, IRigEffectorHolder { [SerializeField] private List m_RigLayers; private IRigLayer[] m_RuntimeRigLayers; private SyncSceneToStreamLayer m_SyncSceneToStreamLayer; [SerializeField] private List m_Effectors = new List(); private bool m_IsInPreview; #if UNITY_EDITOR /// public IEnumerable effectors { get => m_Effectors; } #endif /// /// Delegate function that covers a RigBuilder calling OnEnable. /// /// The RigBuilder component public delegate void OnAddRigBuilderCallback(RigBuilder rigBuilder); /// /// Delegate function that covers a RigBuilder calling OnDisable. /// /// The RigBuilder component public delegate void OnRemoveRigBuilderCallback(RigBuilder rigBuilder); /// /// Notification callback that is sent whenever a RigBuilder calls OnEnable. /// public static OnAddRigBuilderCallback onAddRigBuilder; /// /// Notification callback that is sent whenever a RigBuilder calls OnDisable. /// public static OnRemoveRigBuilderCallback onRemoveRigBuilder; void OnEnable() { // Build runtime data. if (Application.isPlaying) Build(); onAddRigBuilder?.Invoke(this); } void OnDisable() { // Clear runtime data. if (Application.isPlaying) Clear(); onRemoveRigBuilder?.Invoke(this); } void OnDestroy() { Clear(); } /// /// Updates the RigBuilder layers and evaluates the PlayableGraph manually. /// /// The time in seconds by which to advance the RigBuilder PlayableGraph. /// /// Manually evaluate the RigBuilder in LateUpdate. /// /// public void Evaluate(float deltaTime) { if (!graph.IsValid()) return; SyncLayers(); graph.Evaluate(deltaTime); } void Update() { if (!graph.IsValid()) return; SyncLayers(); } /// /// Synchronizes rigs and constraints with scene values. /// This must be called before evaluating the PlayableGraph. /// /// /// /// /// /// Synchronizing layers before evaluating a PlayableGraph created /// outside the RigBuilder in LateUpdate. /// /// public void SyncLayers() { if (m_RuntimeRigLayers == null) return; syncSceneToStreamLayer.Update(m_RuntimeRigLayers); for (int i = 0, count = m_RuntimeRigLayers.Length; i < count; ++i) { if (m_RuntimeRigLayers[i].IsValid() && m_RuntimeRigLayers[i].active) m_RuntimeRigLayers[i].Update(); } } /// /// Builds the RigBuilder PlayableGraph. /// /// Returns true if the RigBuilder has created a valid PlayableGraph. Returns false otherwise. public bool Build() { if (m_IsInPreview) return false; Clear(); var animator = GetComponent(); if (animator == null || layers.Count == 0) return false; // Make a copy of the layers list. m_RuntimeRigLayers = layers.ToArray(); graph = RigBuilderUtils.BuildPlayableGraph(animator, m_RuntimeRigLayers, syncSceneToStreamLayer); if (!graph.IsValid()) return false; graph.Play(); return true; } /// /// Builds the RigBuilder playable nodes in an external PlayableGraph. /// /// Destination PlayableGraph. /// Returns true if the RigBuilder has created Playable nodes. Returns false otherwise. public bool Build(PlayableGraph graph) { if (m_IsInPreview) return false; Clear(); var animator = GetComponent(); if (animator == null || layers.Count == 0) return false; // Make a copy of the layers list. m_RuntimeRigLayers = layers.ToArray(); RigBuilderUtils.BuildPlayableGraph(graph, animator, m_RuntimeRigLayers, syncSceneToStreamLayer); return true; } /// /// Destroys the RigBuilder PlayableGraph and frees associated RigLayers memory. /// public void Clear() { if (m_IsInPreview) return; if (graph.IsValid()) graph.Destroy(); if (m_RuntimeRigLayers != null) { foreach (var layer in m_RuntimeRigLayers) layer.Reset(); m_RuntimeRigLayers = null; } syncSceneToStreamLayer.Reset(); } // // IAnimationWindowPreview methods implementation // /// Notification callback when the animation previewer starts previewing an AnimationClip. /// This is called by the Animation Window or the Timeline Editor. public void StartPreview() { m_IsInPreview = true; if (!enabled) return; // Make a copy of the layer list if it doesn't already exist. if (m_RuntimeRigLayers == null) m_RuntimeRigLayers = layers.ToArray(); var animator = GetComponent(); if (animator != null) { foreach (var layer in m_RuntimeRigLayers) { layer.Initialize(animator); } } } /// Notification callback when the animation previewer stops previewing an AnimationClip. /// This is called by the Animation Window or the Timeline Editor. public void StopPreview() { m_IsInPreview = false; if (!enabled) return; if (Application.isPlaying) return; Clear(); } /// Notification callback when the animation previewer updates its PlayableGraph before sampling an AnimationClip. /// This is called by the Animation Window or the Timeline Editor. /// The animation previewer PlayableGraph public void UpdatePreviewGraph(PlayableGraph graph) { if (!enabled) return; if (!graph.IsValid() || m_RuntimeRigLayers == null) return; syncSceneToStreamLayer.Update(m_RuntimeRigLayers); foreach (var layer in m_RuntimeRigLayers) { if (layer.IsValid() && layer.active) layer.Update(); } } /// /// Appends custom Playable nodes to the animation previewer PlayableGraph. /// /// The animation previewer PlayableGraph /// The current root of the PlayableGraph /// public Playable BuildPreviewGraph(PlayableGraph graph, Playable inputPlayable) { if (!enabled) return inputPlayable; if (m_RuntimeRigLayers == null) StartPreview(); var animator = GetComponent(); if (animator == null || m_RuntimeRigLayers == null || m_RuntimeRigLayers.Length == 0) return inputPlayable; var playableChains = RigBuilderUtils.BuildPlayables(animator, graph, m_RuntimeRigLayers, syncSceneToStreamLayer); foreach(var chain in playableChains) { if (chain.playables == null || chain.playables.Length == 0) continue; chain.playables[0].AddInput(inputPlayable, 0, 1); inputPlayable = chain.playables[chain.playables.Length - 1]; } return inputPlayable; } #if UNITY_EDITOR /// public void AddEffector(Transform transform, RigEffectorData.Style style) { var effector = new RigEffectorData(); effector.Initialize(transform, style); m_Effectors.Add(effector); } /// public void RemoveEffector(Transform transform) { m_Effectors.RemoveAll((RigEffectorData data) => data.transform == transform); } /// public bool ContainsEffector(Transform transform) { return m_Effectors.Exists((RigEffectorData data) => data.transform == transform); } #endif /// /// Returns a list of RigLayer associated to this RigBuilder. /// public List layers { get { if (m_RigLayers == null) m_RigLayers = new List(); return m_RigLayers; } set => m_RigLayers = value; } private SyncSceneToStreamLayer syncSceneToStreamLayer { get { if (m_SyncSceneToStreamLayer == null) m_SyncSceneToStreamLayer = new SyncSceneToStreamLayer(); return m_SyncSceneToStreamLayer; } set => m_SyncSceneToStreamLayer = value; } /// /// Retrieves the PlayableGraph created by this RigBuilder. /// public PlayableGraph graph { get; private set; } } }