| using System; |
| using System.Collections.Generic; |
| using UnityEngine.Animations; |
| #if !UNITY_2020_2_OR_NEWER |
| using UnityEngine.Experimental.Animations; |
| #endif |
|
|
| using UnityEngine.Playables; |
| using UnityEngine.Serialization; |
|
|
| #if UNITY_EDITOR |
| using UnityEditor; |
| #endif |
|
|
| namespace UnityEngine.Timeline |
| { |
| |
| |
| |
| [Flags] |
| public enum MatchTargetFields |
| { |
| |
| |
| |
| PositionX = 1 << 0, |
| |
| |
| |
| PositionY = 1 << 1, |
| |
| |
| |
| PositionZ = 1 << 2, |
| |
| |
| |
| RotationX = 1 << 3, |
| |
| |
| |
| RotationY = 1 << 4, |
| |
| |
| |
| RotationZ = 1 << 5 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public enum TrackOffset |
| { |
| |
| |
| |
| ApplyTransformOffsets, |
| |
| |
| |
| ApplySceneOffsets, |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Auto |
| } |
|
|
| |
| enum AppliedOffsetMode |
| { |
| NoRootTransform, |
| TransformOffset, |
| SceneOffset, |
| TransformOffsetLegacy, |
| SceneOffsetLegacy, |
| SceneOffsetEditor, |
| SceneOffsetLegacyEditor, |
| } |
|
|
| |
| static class MatchTargetFieldConstants |
| { |
| public static MatchTargetFields All = MatchTargetFields.PositionX | MatchTargetFields.PositionY | |
| MatchTargetFields.PositionZ | MatchTargetFields.RotationX | |
| MatchTargetFields.RotationY | MatchTargetFields.RotationZ; |
|
|
| public static MatchTargetFields None = 0; |
|
|
| public static MatchTargetFields Position = MatchTargetFields.PositionX | MatchTargetFields.PositionY | |
| MatchTargetFields.PositionZ; |
|
|
| public static MatchTargetFields Rotation = MatchTargetFields.RotationX | MatchTargetFields.RotationY | |
| MatchTargetFields.RotationZ; |
|
|
| public static bool HasAny(this MatchTargetFields me, MatchTargetFields fields) |
| { |
| return (me & fields) != None; |
| } |
|
|
| public static MatchTargetFields Toggle(this MatchTargetFields me, MatchTargetFields flag) |
| { |
| return me ^ flag; |
| } |
| } |
|
|
| |
| |
| |
| [Serializable] |
| [TrackClipType(typeof(AnimationPlayableAsset), false)] |
| [TrackBindingType(typeof(Animator))] |
| [ExcludeFromPreset] |
| [TimelineHelpURL(typeof(AnimationTrack))] |
| public partial class AnimationTrack : TrackAsset, ILayerable |
| { |
| const string k_DefaultInfiniteClipName = "Recorded"; |
| const string k_DefaultRecordableClipName = "Recorded"; |
|
|
| [SerializeField, FormerlySerializedAs("m_OpenClipPreExtrapolation")] |
| TimelineClip.ClipExtrapolation m_InfiniteClipPreExtrapolation = TimelineClip.ClipExtrapolation.None; |
|
|
| [SerializeField, FormerlySerializedAs("m_OpenClipPostExtrapolation")] |
| TimelineClip.ClipExtrapolation m_InfiniteClipPostExtrapolation = TimelineClip.ClipExtrapolation.None; |
|
|
| [SerializeField, FormerlySerializedAs("m_OpenClipOffsetPosition")] |
| Vector3 m_InfiniteClipOffsetPosition = Vector3.zero; |
|
|
| [SerializeField, FormerlySerializedAs("m_OpenClipOffsetEulerAngles")] |
| Vector3 m_InfiniteClipOffsetEulerAngles = Vector3.zero; |
|
|
| [SerializeField, FormerlySerializedAs("m_OpenClipTimeOffset")] |
| double m_InfiniteClipTimeOffset; |
|
|
| [SerializeField, FormerlySerializedAs("m_OpenClipRemoveOffset")] |
| bool m_InfiniteClipRemoveOffset; |
|
|
| [SerializeField] |
| bool m_InfiniteClipApplyFootIK = true; |
|
|
| [SerializeField, HideInInspector] |
| AnimationPlayableAsset.LoopMode mInfiniteClipLoop = AnimationPlayableAsset.LoopMode.UseSourceAsset; |
|
|
| [SerializeField] |
| MatchTargetFields m_MatchTargetFields = MatchTargetFieldConstants.All; |
| [SerializeField] |
| Vector3 m_Position = Vector3.zero; |
| [SerializeField] |
| Vector3 m_EulerAngles = Vector3.zero; |
|
|
|
|
| [SerializeField] AvatarMask m_AvatarMask; |
| [SerializeField] bool m_ApplyAvatarMask = true; |
|
|
| [SerializeField] TrackOffset m_TrackOffset = TrackOffset.ApplyTransformOffsets; |
|
|
| [SerializeField, HideInInspector] AnimationClip m_InfiniteClip; |
|
|
|
|
| #if UNITY_EDITOR |
| private AnimationClip m_DefaultPoseClip; |
| private AnimationClip m_CachedPropertiesClip; |
| private int m_CachedHash; |
| private EditorCurveBinding[] m_CachedBindings; |
|
|
| AnimationOffsetPlayable m_ClipOffset; |
|
|
| private Vector3 m_SceneOffsetPosition = Vector3.zero; |
| private Vector3 m_SceneOffsetRotation = Vector3.zero; |
|
|
| private bool m_HasPreviewComponents = false; |
| #endif |
|
|
| |
| |
| |
| public Vector3 position |
| { |
| get { return m_Position; } |
| set { m_Position = value; } |
| } |
|
|
| |
| |
| |
| public Quaternion rotation |
| { |
| get { return Quaternion.Euler(m_EulerAngles); } |
| set { m_EulerAngles = value.eulerAngles; } |
| } |
|
|
| |
| |
| |
| public Vector3 eulerAngles |
| { |
| get { return m_EulerAngles; } |
| set { m_EulerAngles = value; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| [Obsolete("applyOffset is deprecated. Use trackOffset instead", true)] |
| public bool applyOffsets |
| { |
| get { return false; } |
| set { } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public TrackOffset trackOffset |
| { |
| get { return m_TrackOffset; } |
| set { m_TrackOffset = value; } |
| } |
|
|
| |
| |
| |
| public MatchTargetFields matchTargetFields |
| { |
| get { return m_MatchTargetFields; } |
| set { m_MatchTargetFields = value & MatchTargetFieldConstants.All; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public AnimationClip infiniteClip |
| { |
| get { return m_InfiniteClip; } |
| internal set { m_InfiniteClip = value; } |
| } |
|
|
| |
| internal bool infiniteClipRemoveOffset |
| { |
| get { return m_InfiniteClipRemoveOffset; } |
| set { m_InfiniteClipRemoveOffset = value; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public AvatarMask avatarMask |
| { |
| get { return m_AvatarMask; } |
| set { m_AvatarMask = value; } |
| } |
|
|
| |
| |
| |
| public bool applyAvatarMask |
| { |
| get { return m_ApplyAvatarMask; } |
| set { m_ApplyAvatarMask = value; } |
| } |
|
|
| |
|
|
| internal override bool CanCompileClips() |
| { |
| return !muted && (m_Clips.Count > 0 || (m_InfiniteClip != null && !m_InfiniteClip.empty)); |
| } |
|
|
| |
| public override IEnumerable<PlayableBinding> outputs |
| { |
| get { yield return AnimationPlayableBinding.Create(name, this); } |
| } |
|
|
|
|
| |
| |
| |
| public bool inClipMode |
| { |
| get { return clips != null && clips.Length != 0; } |
| } |
|
|
| |
| |
| |
| public Vector3 infiniteClipOffsetPosition |
| { |
| get { return m_InfiniteClipOffsetPosition; } |
| set { m_InfiniteClipOffsetPosition = value; } |
| } |
|
|
| |
| |
| |
| public Quaternion infiniteClipOffsetRotation |
| { |
| get { return Quaternion.Euler(m_InfiniteClipOffsetEulerAngles); } |
| set { m_InfiniteClipOffsetEulerAngles = value.eulerAngles; } |
| } |
|
|
| |
| |
| |
| public Vector3 infiniteClipOffsetEulerAngles |
| { |
| get { return m_InfiniteClipOffsetEulerAngles; } |
| set { m_InfiniteClipOffsetEulerAngles = value; } |
| } |
|
|
| internal bool infiniteClipApplyFootIK |
| { |
| get { return m_InfiniteClipApplyFootIK; } |
| set { m_InfiniteClipApplyFootIK = value; } |
| } |
|
|
| internal double infiniteClipTimeOffset |
| { |
| get { return m_InfiniteClipTimeOffset; } |
| set { m_InfiniteClipTimeOffset = value; } |
| } |
|
|
| |
| |
| |
| public TimelineClip.ClipExtrapolation infiniteClipPreExtrapolation |
| { |
| get { return m_InfiniteClipPreExtrapolation; } |
| set { m_InfiniteClipPreExtrapolation = value; } |
| } |
|
|
| |
| |
| |
| public TimelineClip.ClipExtrapolation infiniteClipPostExtrapolation |
| { |
| get { return m_InfiniteClipPostExtrapolation; } |
| set { m_InfiniteClipPostExtrapolation = value; } |
| } |
|
|
| |
| |
| |
| internal AnimationPlayableAsset.LoopMode infiniteClipLoop |
| { |
| get { return mInfiniteClipLoop; } |
| set { mInfiniteClipLoop = value; } |
| } |
|
|
| [ContextMenu("Reset Offsets")] |
| void ResetOffsets() |
| { |
| m_Position = Vector3.zero; |
| m_EulerAngles = Vector3.zero; |
| UpdateClipOffsets(); |
| } |
|
|
| |
| |
| |
| |
| |
| public TimelineClip CreateClip(AnimationClip clip) |
| { |
| if (clip == null) |
| return null; |
|
|
| var newClip = CreateClip<AnimationPlayableAsset>(); |
| AssignAnimationClip(newClip, clip); |
| return newClip; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public void CreateInfiniteClip(string infiniteClipName) |
| { |
| if (inClipMode) |
| { |
| Debug.LogWarning("CreateInfiniteClip cannot create an infinite clip for an AnimationTrack that contains one or more Timeline Clips."); |
| return; |
| } |
|
|
| if (m_InfiniteClip != null) |
| return; |
|
|
| m_InfiniteClip = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(infiniteClipName) ? k_DefaultInfiniteClipName : infiniteClipName, this, false); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public TimelineClip CreateRecordableClip(string animClipName) |
| { |
| var clip = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(animClipName) ? k_DefaultRecordableClipName : animClipName, this, false); |
|
|
| var timelineClip = CreateClip(clip); |
| timelineClip.displayName = animClipName; |
| timelineClip.recordable = true; |
| timelineClip.start = 0; |
| timelineClip.duration = 1; |
|
|
| var apa = timelineClip.asset as AnimationPlayableAsset; |
| if (apa != null) |
| apa.removeStartOffset = false; |
|
|
| return timelineClip; |
| } |
|
|
| #if UNITY_EDITOR |
| internal Vector3 sceneOffsetPosition |
| { |
| get { return m_SceneOffsetPosition; } |
| set { m_SceneOffsetPosition = value; } |
| } |
|
|
| internal Vector3 sceneOffsetRotation |
| { |
| get { return m_SceneOffsetRotation; } |
| set { m_SceneOffsetRotation = value; } |
| } |
|
|
| internal bool hasPreviewComponents |
| { |
| get |
| { |
| if (m_HasPreviewComponents) |
| return true; |
|
|
| var parentTrack = parent as AnimationTrack; |
| if (parentTrack != null) |
| { |
| return parentTrack.hasPreviewComponents; |
| } |
|
|
| return false; |
| } |
| } |
| #endif |
|
|
| |
| |
| |
| |
| protected override void OnCreateClip(TimelineClip clip) |
| { |
| var extrapolation = TimelineClip.ClipExtrapolation.None; |
| if (!isSubTrack) |
| extrapolation = TimelineClip.ClipExtrapolation.Hold; |
| clip.preExtrapolationMode = extrapolation; |
| clip.postExtrapolationMode = extrapolation; |
| } |
|
|
| protected internal override int CalculateItemsHash() |
| { |
| return GetAnimationClipHash(m_InfiniteClip).CombineHash(base.CalculateItemsHash()); |
| } |
|
|
| internal void UpdateClipOffsets() |
| { |
| #if UNITY_EDITOR |
| if (m_ClipOffset.IsValid()) |
| { |
| m_ClipOffset.SetPosition(position); |
| m_ClipOffset.SetRotation(rotation); |
| } |
| #endif |
| } |
|
|
| Playable CompileTrackPlayable(PlayableGraph graph, AnimationTrack track, GameObject go, IntervalTree<RuntimeElement> tree, AppliedOffsetMode mode) |
| { |
| var mixer = AnimationMixerPlayable.Create(graph, track.clips.Length); |
| for (int i = 0; i < track.clips.Length; i++) |
| { |
| var c = track.clips[i]; |
| var asset = c.asset as PlayableAsset; |
| if (asset == null) |
| continue; |
|
|
| var animationAsset = asset as AnimationPlayableAsset; |
| if (animationAsset != null) |
| animationAsset.appliedOffsetMode = mode; |
|
|
| var source = asset.CreatePlayable(graph, go); |
| if (source.IsValid()) |
| { |
| var clip = new RuntimeClip(c, source, mixer); |
| tree.Add(clip); |
| graph.Connect(source, 0, mixer, i); |
| mixer.SetInputWeight(i, 0.0f); |
| } |
| } |
|
|
| if (!track.AnimatesRootTransform()) |
| return mixer; |
|
|
| return ApplyTrackOffset(graph, mixer, go, mode); |
| } |
|
|
| |
| |
| Playable ILayerable.CreateLayerMixer(PlayableGraph graph, GameObject go, int inputCount) |
| { |
| return Playable.Null; |
| } |
|
|
| internal override Playable CreateMixerPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree) |
| { |
| if (isSubTrack) |
| throw new InvalidOperationException("Nested animation tracks should never be asked to create a graph directly"); |
|
|
| List<AnimationTrack> flattenTracks = new List<AnimationTrack>(); |
| if (CanCompileClips()) |
| flattenTracks.Add(this); |
|
|
| var genericRoot = GetGenericRootNode(go); |
| var animatesRootTransformNoMask = AnimatesRootTransform(); |
| var animatesRootTransform = animatesRootTransformNoMask && !IsRootTransformDisabledByMask(go, genericRoot); |
| foreach (var subTrack in GetChildTracks()) |
| { |
| var child = subTrack as AnimationTrack; |
| if (child != null && child.CanCompileClips()) |
| { |
| var childAnimatesRoot = child.AnimatesRootTransform(); |
| animatesRootTransformNoMask |= child.AnimatesRootTransform(); |
| animatesRootTransform |= (childAnimatesRoot && !child.IsRootTransformDisabledByMask(go, genericRoot)); |
| flattenTracks.Add(child); |
| } |
| } |
|
|
| |
| AppliedOffsetMode mode = GetOffsetMode(go, animatesRootTransform); |
| int defaultBlendCount = GetDefaultBlendCount(); |
| var layerMixer = CreateGroupMixer(graph, go, flattenTracks.Count + defaultBlendCount); |
| for (int c = 0; c < flattenTracks.Count; c++) |
| { |
| int blendIndex = c + defaultBlendCount; |
| |
| var childMode = mode; |
| if (mode != AppliedOffsetMode.NoRootTransform && flattenTracks[c].IsRootTransformDisabledByMask(go, genericRoot)) |
| childMode = AppliedOffsetMode.NoRootTransform; |
|
|
| var compiledTrackPlayable = flattenTracks[c].inClipMode ? |
| CompileTrackPlayable(graph, flattenTracks[c], go, tree, childMode) : |
| flattenTracks[c].CreateInfiniteTrackPlayable(graph, go, tree, childMode); |
| graph.Connect(compiledTrackPlayable, 0, layerMixer, blendIndex); |
| layerMixer.SetInputWeight(blendIndex, flattenTracks[c].inClipMode ? 0 : 1); |
| if (flattenTracks[c].applyAvatarMask && flattenTracks[c].avatarMask != null) |
| { |
| layerMixer.SetLayerMaskFromAvatarMask((uint)blendIndex, flattenTracks[c].avatarMask); |
| } |
| } |
|
|
| var requiresMotionXPlayable = RequiresMotionXPlayable(mode, go); |
|
|
| |
| |
| requiresMotionXPlayable |= (defaultBlendCount > 0 && RequiresMotionXPlayable(GetOffsetMode(go, animatesRootTransformNoMask), go)); |
|
|
| |
| AttachDefaultBlend(graph, layerMixer, requiresMotionXPlayable); |
|
|
| |
| Playable mixer = layerMixer; |
| if (requiresMotionXPlayable) |
| { |
| |
| var motionXToDelta = AnimationMotionXToDeltaPlayable.Create(graph); |
| graph.Connect(mixer, 0, motionXToDelta, 0); |
| motionXToDelta.SetInputWeight(0, 1.0f); |
| motionXToDelta.SetAbsoluteMotion(UsesAbsoluteMotion(mode)); |
| mixer = (Playable)motionXToDelta; |
| } |
|
|
|
|
| #if UNITY_EDITOR |
| if (!Application.isPlaying) |
| { |
| var animator = GetBinding(go != null ? go.GetComponent<PlayableDirector>() : null); |
| if (animator != null) |
| { |
| GameObject targetGO = animator.gameObject; |
| IAnimationWindowPreview[] previewComponents = targetGO.GetComponents<IAnimationWindowPreview>(); |
|
|
| m_HasPreviewComponents = previewComponents.Length > 0; |
| if (m_HasPreviewComponents) |
| { |
| foreach (var component in previewComponents) |
| { |
| mixer = component.BuildPreviewGraph(graph, mixer); |
| } |
| } |
| } |
| } |
| #endif |
|
|
| return mixer; |
| } |
|
|
| private int GetDefaultBlendCount() |
| { |
| #if UNITY_EDITOR |
| if (Application.isPlaying) |
| return 0; |
|
|
| return ((m_CachedPropertiesClip != null) ? 1 : 0) + ((m_DefaultPoseClip != null) ? 1 : 0); |
| #else |
| return 0; |
| #endif |
| } |
|
|
| |
| |
| |
| private void AttachDefaultBlend(PlayableGraph graph, AnimationLayerMixerPlayable mixer, bool requireOffset) |
| { |
| #if UNITY_EDITOR |
| if (Application.isPlaying) |
| return; |
|
|
| int mixerInput = 0; |
| if (m_CachedPropertiesClip) |
| { |
| var cachedPropertiesClip = AnimationClipPlayable.Create(graph, m_CachedPropertiesClip); |
| cachedPropertiesClip.SetApplyFootIK(false); |
| var defaults = (Playable)cachedPropertiesClip; |
| if (requireOffset) |
| defaults = AttachOffsetPlayable(graph, defaults, m_SceneOffsetPosition, Quaternion.Euler(m_SceneOffsetRotation)); |
| graph.Connect(defaults, 0, mixer, mixerInput); |
| mixer.SetInputWeight(mixerInput, 1.0f); |
| mixerInput++; |
| } |
|
|
| if (m_DefaultPoseClip) |
| { |
| var defaultPose = AnimationClipPlayable.Create(graph, m_DefaultPoseClip); |
| defaultPose.SetApplyFootIK(false); |
| var blendDefault = (Playable)defaultPose; |
| if (requireOffset) |
| blendDefault = AttachOffsetPlayable(graph, blendDefault, m_SceneOffsetPosition, Quaternion.Euler(m_SceneOffsetRotation)); |
| graph.Connect(blendDefault, 0, mixer, mixerInput); |
| mixer.SetInputWeight(mixerInput, 1.0f); |
| } |
| #endif |
| } |
|
|
| private Playable AttachOffsetPlayable(PlayableGraph graph, Playable playable, Vector3 pos, Quaternion rot) |
| { |
| var offsetPlayable = AnimationOffsetPlayable.Create(graph, pos, rot, 1); |
| offsetPlayable.SetInputWeight(0, 1.0f); |
| graph.Connect(playable, 0, offsetPlayable, 0); |
| return offsetPlayable; |
| } |
|
|
| #if UNITY_EDITOR |
| private static string k_DefaultHumanoidClipPath = "Packages/com.unity.timeline/Editor/StyleSheets/res/HumanoidDefault.anim"; |
| private static AnimationClip s_DefaultHumanoidClip = null; |
|
|
| AnimationClip GetDefaultHumanoidClip() |
| { |
| if (s_DefaultHumanoidClip == null) |
| { |
| s_DefaultHumanoidClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(k_DefaultHumanoidClipPath); |
| if (s_DefaultHumanoidClip == null) |
| Debug.LogError("Could not load default humanoid animation clip for Timeline"); |
| } |
|
|
| return s_DefaultHumanoidClip; |
| } |
|
|
| #endif |
|
|
| bool RequiresMotionXPlayable(AppliedOffsetMode mode, GameObject gameObject) |
| { |
| if (mode == AppliedOffsetMode.NoRootTransform) |
| return false; |
| if (mode == AppliedOffsetMode.SceneOffsetLegacy) |
| { |
| var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null); |
| return animator != null && animator.hasRootMotion; |
| } |
| return true; |
| } |
|
|
| static bool UsesAbsoluteMotion(AppliedOffsetMode mode) |
| { |
| #if UNITY_EDITOR |
| |
| if (!Application.isPlaying) |
| return true; |
| #endif |
| return mode != AppliedOffsetMode.SceneOffset && |
| mode != AppliedOffsetMode.SceneOffsetLegacy; |
| } |
|
|
| bool HasController(GameObject gameObject) |
| { |
| var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null); |
|
|
| return animator != null && animator.runtimeAnimatorController != null; |
| } |
|
|
| internal Animator GetBinding(PlayableDirector director) |
| { |
| if (director == null) |
| return null; |
|
|
| UnityEngine.Object key = this; |
| if (isSubTrack) |
| key = parent; |
|
|
| UnityEngine.Object binding = null; |
| if (director != null) |
| binding = director.GetGenericBinding(key); |
|
|
| Animator animator = null; |
| if (binding != null) |
| { |
| animator = binding as Animator; |
| var gameObject = binding as GameObject; |
| if (animator == null && gameObject != null) |
| animator = gameObject.GetComponent<Animator>(); |
| } |
|
|
| return animator; |
| } |
|
|
| static AnimationLayerMixerPlayable CreateGroupMixer(PlayableGraph graph, GameObject go, int inputCount) |
| { |
| return AnimationLayerMixerPlayable.Create(graph, inputCount); |
| } |
|
|
| Playable CreateInfiniteTrackPlayable(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, AppliedOffsetMode mode) |
| { |
| if (m_InfiniteClip == null) |
| return Playable.Null; |
|
|
| var mixer = AnimationMixerPlayable.Create(graph, 1); |
|
|
| |
| |
| |
| var playable = AnimationPlayableAsset.CreatePlayable(graph, m_InfiniteClip, m_InfiniteClipOffsetPosition, m_InfiniteClipOffsetEulerAngles, false, mode, infiniteClipApplyFootIK, AnimationPlayableAsset.LoopMode.Off); |
| if (playable.IsValid()) |
| { |
| tree.Add(new InfiniteRuntimeClip(playable)); |
| graph.Connect(playable, 0, mixer, 0); |
| mixer.SetInputWeight(0, 1.0f); |
| } |
|
|
| if (!AnimatesRootTransform()) |
| return mixer; |
|
|
| var rootTrack = isSubTrack ? (AnimationTrack)parent : this; |
| return rootTrack.ApplyTrackOffset(graph, mixer, go, mode); |
| } |
|
|
| Playable ApplyTrackOffset(PlayableGraph graph, Playable root, GameObject go, AppliedOffsetMode mode) |
| { |
| #if UNITY_EDITOR |
| m_ClipOffset = AnimationOffsetPlayable.Null; |
| #endif |
|
|
| |
| if (mode == AppliedOffsetMode.SceneOffsetLegacy || |
| mode == AppliedOffsetMode.SceneOffset || |
| mode == AppliedOffsetMode.NoRootTransform |
| ) |
| return root; |
|
|
|
|
| var pos = position; |
| var rot = rotation; |
|
|
| #if UNITY_EDITOR |
| |
| if (mode == AppliedOffsetMode.SceneOffsetEditor) |
| { |
| pos = m_SceneOffsetPosition; |
| rot = Quaternion.Euler(m_SceneOffsetRotation); |
| } |
| #endif |
|
|
| var offsetPlayable = AnimationOffsetPlayable.Create(graph, pos, rot, 1); |
| #if UNITY_EDITOR |
| m_ClipOffset = offsetPlayable; |
| #endif |
| graph.Connect(root, 0, offsetPlayable, 0); |
| offsetPlayable.SetInputWeight(0, 1); |
|
|
| return offsetPlayable; |
| } |
|
|
| |
| internal override void GetEvaluationTime(out double outStart, out double outDuration) |
| { |
| if (inClipMode) |
| { |
| base.GetEvaluationTime(out outStart, out outDuration); |
| } |
| else |
| { |
| outStart = 0; |
| outDuration = TimelineClip.kMaxTimeValue; |
| } |
| } |
|
|
| internal override void GetSequenceTime(out double outStart, out double outDuration) |
| { |
| if (inClipMode) |
| { |
| base.GetSequenceTime(out outStart, out outDuration); |
| } |
| else |
| { |
| outStart = 0; |
| outDuration = Math.Max(GetNotificationDuration(), TimeUtility.GetAnimationClipLength(m_InfiniteClip)); |
| } |
| } |
|
|
| void AssignAnimationClip(TimelineClip clip, AnimationClip animClip) |
| { |
| if (clip == null || animClip == null) |
| return; |
|
|
| if (animClip.legacy) |
| throw new InvalidOperationException("Legacy Animation Clips are not supported"); |
|
|
| AnimationPlayableAsset asset = clip.asset as AnimationPlayableAsset; |
| if (asset != null) |
| { |
| asset.clip = animClip; |
| asset.name = animClip.name; |
| var duration = asset.duration; |
| if (!double.IsInfinity(duration) && duration >= TimelineClip.kMinDuration && duration < TimelineClip.kMaxTimeValue) |
| clip.duration = duration; |
| } |
| clip.displayName = animClip.name; |
| } |
|
|
| |
| |
| |
| |
| |
| public override void GatherProperties(PlayableDirector director, IPropertyCollector driver) |
| { |
| #if UNITY_EDITOR |
| m_SceneOffsetPosition = Vector3.zero; |
| m_SceneOffsetRotation = Vector3.zero; |
|
|
| var animator = GetBinding(director); |
| if (animator == null) |
| return; |
|
|
| var animClips = new List<AnimationClip>(this.clips.Length + 2); |
| GetAnimationClips(animClips); |
|
|
| var hasHumanMotion = animClips.Exists(clip => clip.humanMotion); |
| |
| if (!hasHumanMotion && animator.isHuman && AnimatesRootTransform() && |
| !DrivenPropertyManagerInternal.IsDriven(animator.transform, "m_LocalPosition.x") && |
| !DrivenPropertyManagerInternal.IsDriven(animator.transform, "m_LocalRotation.x")) |
| hasHumanMotion = true; |
|
|
| m_SceneOffsetPosition = animator.transform.localPosition; |
| m_SceneOffsetRotation = animator.transform.localEulerAngles; |
|
|
| |
| if (hasHumanMotion) |
| animClips.Add(GetDefaultHumanoidClip()); |
|
|
| m_DefaultPoseClip = hasHumanMotion ? GetDefaultHumanoidClip() : null; |
| var hash = AnimationPreviewUtilities.GetClipHash(animClips); |
| if (m_CachedBindings == null || m_CachedHash != hash) |
| { |
| m_CachedBindings = AnimationPreviewUtilities.GetBindings(animator.gameObject, animClips); |
| m_CachedPropertiesClip = AnimationPreviewUtilities.CreateDefaultClip(animator.gameObject, m_CachedBindings); |
| m_CachedHash = hash; |
| } |
|
|
| AnimationPreviewUtilities.PreviewFromCurves(animator.gameObject, m_CachedBindings); |
| #endif |
| } |
|
|
| |
| |
| |
| |
| private void GetAnimationClips(List<AnimationClip> animClips) |
| { |
| foreach (var c in clips) |
| { |
| var a = c.asset as AnimationPlayableAsset; |
| if (a != null && a.clip != null) |
| animClips.Add(a.clip); |
| } |
|
|
| if (m_InfiniteClip != null) |
| animClips.Add(m_InfiniteClip); |
|
|
| foreach (var childTrack in GetChildTracks()) |
| { |
| var animChildTrack = childTrack as AnimationTrack; |
| if (animChildTrack != null) |
| animChildTrack.GetAnimationClips(animClips); |
| } |
| } |
|
|
| |
| AppliedOffsetMode GetOffsetMode(GameObject go, bool animatesRootTransform) |
| { |
| if (!animatesRootTransform) |
| return AppliedOffsetMode.NoRootTransform; |
|
|
| if (m_TrackOffset == TrackOffset.ApplyTransformOffsets) |
| return AppliedOffsetMode.TransformOffset; |
|
|
| if (m_TrackOffset == TrackOffset.ApplySceneOffsets) |
| return (Application.isPlaying) ? AppliedOffsetMode.SceneOffset : AppliedOffsetMode.SceneOffsetEditor; |
|
|
| if (HasController(go)) |
| { |
| if (!Application.isPlaying) |
| return AppliedOffsetMode.SceneOffsetLegacyEditor; |
| return AppliedOffsetMode.SceneOffsetLegacy; |
| } |
|
|
| return AppliedOffsetMode.TransformOffsetLegacy; |
| } |
|
|
| private bool IsRootTransformDisabledByMask(GameObject gameObject, Transform genericRootNode) |
| { |
| if (avatarMask == null || !applyAvatarMask) |
| return false; |
|
|
| var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null); |
| if (animator == null) |
| return false; |
|
|
| if (animator.isHuman) |
| return !avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.Root); |
|
|
| if (avatarMask.transformCount == 0) |
| return false; |
|
|
| |
| if (genericRootNode == null) |
| return string.IsNullOrEmpty(avatarMask.GetTransformPath(0)) && !avatarMask.GetTransformActive(0); |
|
|
| |
| for (int i = 0; i < avatarMask.transformCount; i++) |
| { |
| if (genericRootNode == animator.transform.Find(avatarMask.GetTransformPath(i))) |
| return !avatarMask.GetTransformActive(i); |
| } |
|
|
| return false; |
| } |
|
|
| |
| private Transform GetGenericRootNode(GameObject gameObject) |
| { |
| var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null); |
| if (animator == null) |
| return null; |
|
|
| if (animator.isHuman) |
| return null; |
|
|
| if (animator.avatar == null) |
| return null; |
|
|
| |
| var rootName = animator.avatar.humanDescription.m_RootMotionBoneName; |
| if (rootName == animator.name || string.IsNullOrEmpty(rootName)) |
| return null; |
|
|
| |
| return FindInHierarchyBreadthFirst(animator.transform, rootName); |
| } |
|
|
| internal bool AnimatesRootTransform() |
| { |
| |
| if (AnimationPlayableAsset.HasRootTransforms(m_InfiniteClip)) |
| return true; |
|
|
| |
| foreach (var c in GetClips()) |
| { |
| var apa = c.asset as AnimationPlayableAsset; |
| if (apa != null && apa.hasRootTransforms) |
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| private static readonly Queue<Transform> s_CachedQueue = new Queue<Transform>(100); |
| private static Transform FindInHierarchyBreadthFirst(Transform t, string name) |
| { |
| s_CachedQueue.Clear(); |
| s_CachedQueue.Enqueue(t); |
| while (s_CachedQueue.Count > 0) |
| { |
| var r = s_CachedQueue.Dequeue(); |
| if (r.name == name) |
| return r; |
| for (int i = 0; i < r.childCount; i++) |
| s_CachedQueue.Enqueue(r.GetChild(i)); |
| } |
|
|
| return null; |
| } |
| } |
| } |
|
|