A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEngine.Animations;
4#if !UNITY_2020_2_OR_NEWER
5using UnityEngine.Experimental.Animations;
6#endif
7
8using UnityEngine.Playables;
9using UnityEngine.Serialization;
10
11#if UNITY_EDITOR
12using UnityEditor;
13#endif
14
15namespace UnityEngine.Timeline
16{
17 /// <summary>
18 /// Flags specifying which offset fields to match
19 /// </summary>
20 [Flags]
21 public enum MatchTargetFields
22 {
23 /// <summary>
24 /// Translation X value
25 /// </summary>
26 PositionX = 1 << 0,
27 /// <summary>
28 /// Translation Y value
29 /// </summary>
30 PositionY = 1 << 1,
31 /// <summary>
32 /// Translation Z value
33 /// </summary>
34 PositionZ = 1 << 2,
35 /// <summary>
36 /// Rotation Euler Angle X value
37 /// </summary>
38 RotationX = 1 << 3,
39 /// <summary>
40 /// Rotation Euler Angle Y value
41 /// </summary>
42 RotationY = 1 << 4,
43 /// <summary>
44 /// Rotation Euler Angle Z value
45 /// </summary>
46 RotationZ = 1 << 5
47 }
48
49 /// <summary>
50 /// Describes what is used to set the starting position and orientation of each Animation Track.
51 /// </summary>
52 /// <remarks>
53 /// By default, each Animation Track uses ApplyTransformOffsets to start from a set position and orientation.
54 /// To offset each Animation Track based on the current position and orientation in the scene, use ApplySceneOffsets.
55 /// </remarks>
56 public enum TrackOffset
57 {
58 /// <summary>
59 /// Use this setting to offset each Animation Track based on a set position and orientation.
60 /// </summary>
61 ApplyTransformOffsets,
62 /// <summary>
63 /// Use this setting to offset each Animation Track based on the current position and orientation in the scene.
64 /// </summary>
65 ApplySceneOffsets,
66 /// <summary>
67 /// Use this setting to offset root transforms based on the state of the animator.
68 /// </summary>
69 /// <remarks>
70 /// Only use this setting to support legacy Animation Tracks. This mode may be deprecated in a future release.
71 ///
72 /// In Auto mode, when the animator bound to the animation track contains an AnimatorController, it offsets all animations similar to ApplySceneOffsets.
73 /// If no controller is assigned, then all offsets are set to start from a fixed position and orientation, similar to ApplyTransformOffsets.
74 /// In Auto mode, in most cases, root transforms are not affected by local scale or Animator.humanScale, unless the animator has an AnimatorController and Animator.applyRootMotion is set to true.
75 /// </remarks>
76 Auto
77 }
78
79 // offset mode
80 enum AppliedOffsetMode
81 {
82 NoRootTransform,
83 TransformOffset,
84 SceneOffset,
85 TransformOffsetLegacy,
86 SceneOffsetLegacy,
87 SceneOffsetEditor, // scene offset mode in editor
88 SceneOffsetLegacyEditor,
89 }
90
91 // separate from the enum to hide them from UI elements
92 static class MatchTargetFieldConstants
93 {
94 public static MatchTargetFields All = MatchTargetFields.PositionX | MatchTargetFields.PositionY |
95 MatchTargetFields.PositionZ | MatchTargetFields.RotationX |
96 MatchTargetFields.RotationY | MatchTargetFields.RotationZ;
97
98 public static MatchTargetFields None = 0;
99
100 public static MatchTargetFields Position = MatchTargetFields.PositionX | MatchTargetFields.PositionY |
101 MatchTargetFields.PositionZ;
102
103 public static MatchTargetFields Rotation = MatchTargetFields.RotationX | MatchTargetFields.RotationY |
104 MatchTargetFields.RotationZ;
105
106 public static bool HasAny(this MatchTargetFields me, MatchTargetFields fields)
107 {
108 return (me & fields) != None;
109 }
110
111 public static MatchTargetFields Toggle(this MatchTargetFields me, MatchTargetFields flag)
112 {
113 return me ^ flag;
114 }
115 }
116
117 /// <summary>
118 /// A Timeline track used for playing back animations on an Animator.
119 /// </summary>
120 [Serializable]
121 [TrackClipType(typeof(AnimationPlayableAsset), false)]
122 [TrackBindingType(typeof(Animator))]
123 [ExcludeFromPreset]
124 [TimelineHelpURL(typeof(AnimationTrack))]
125 public partial class AnimationTrack : TrackAsset, ILayerable
126 {
127 const string k_DefaultInfiniteClipName = "Recorded";
128 const string k_DefaultRecordableClipName = "Recorded";
129
130 [SerializeField, FormerlySerializedAs("m_OpenClipPreExtrapolation")]
131 TimelineClip.ClipExtrapolation m_InfiniteClipPreExtrapolation = TimelineClip.ClipExtrapolation.None;
132
133 [SerializeField, FormerlySerializedAs("m_OpenClipPostExtrapolation")]
134 TimelineClip.ClipExtrapolation m_InfiniteClipPostExtrapolation = TimelineClip.ClipExtrapolation.None;
135
136 [SerializeField, FormerlySerializedAs("m_OpenClipOffsetPosition")]
137 Vector3 m_InfiniteClipOffsetPosition = Vector3.zero;
138
139 [SerializeField, FormerlySerializedAs("m_OpenClipOffsetEulerAngles")]
140 Vector3 m_InfiniteClipOffsetEulerAngles = Vector3.zero;
141
142 [SerializeField, FormerlySerializedAs("m_OpenClipTimeOffset")]
143 double m_InfiniteClipTimeOffset;
144
145 [SerializeField, FormerlySerializedAs("m_OpenClipRemoveOffset")]
146 bool m_InfiniteClipRemoveOffset; // cached value for remove offset
147
148 [SerializeField]
149 bool m_InfiniteClipApplyFootIK = true;
150
151 [SerializeField, HideInInspector]
152 AnimationPlayableAsset.LoopMode mInfiniteClipLoop = AnimationPlayableAsset.LoopMode.UseSourceAsset;
153
154 [SerializeField]
155 MatchTargetFields m_MatchTargetFields = MatchTargetFieldConstants.All;
156 [SerializeField]
157 Vector3 m_Position = Vector3.zero;
158 [SerializeField]
159 Vector3 m_EulerAngles = Vector3.zero;
160
161
162 [SerializeField] AvatarMask m_AvatarMask;
163 [SerializeField] bool m_ApplyAvatarMask = true;
164
165 [SerializeField] TrackOffset m_TrackOffset = TrackOffset.ApplyTransformOffsets;
166
167 [SerializeField, HideInInspector] AnimationClip m_InfiniteClip;
168
169
170#if UNITY_EDITOR
171 private AnimationClip m_DefaultPoseClip;
172 private AnimationClip m_CachedPropertiesClip;
173 private int m_CachedHash;
174 private EditorCurveBinding[] m_CachedBindings;
175
176 AnimationOffsetPlayable m_ClipOffset;
177
178 private Vector3 m_SceneOffsetPosition = Vector3.zero;
179 private Vector3 m_SceneOffsetRotation = Vector3.zero;
180
181 private bool m_HasPreviewComponents = false;
182#endif
183
184 /// <summary>
185 /// The translation offset of the entire track.
186 /// </summary>
187 public Vector3 position
188 {
189 get { return m_Position; }
190 set { m_Position = value; }
191 }
192
193 /// <summary>
194 /// The rotation offset of the entire track, expressed as a quaternion.
195 /// </summary>
196 public Quaternion rotation
197 {
198 get { return Quaternion.Euler(m_EulerAngles); }
199 set { m_EulerAngles = value.eulerAngles; }
200 }
201
202 /// <summary>
203 /// The euler angle representation of the rotation offset of the entire track.
204 /// </summary>
205 public Vector3 eulerAngles
206 {
207 get { return m_EulerAngles; }
208 set { m_EulerAngles = value; }
209 }
210
211 /// <summary>
212 /// Specifies whether to apply track offsets to all clips on the track.
213 /// </summary>
214 /// <remarks>
215 /// This can be used to offset all clips on a track, in addition to the clips individual offsets.
216 /// </remarks>
217 [Obsolete("applyOffset is deprecated. Use trackOffset instead", true)]
218 public bool applyOffsets
219 {
220 get { return false; }
221 set { }
222 }
223
224 /// <summary>
225 /// Specifies what is used to set the starting position and orientation of an Animation Track.
226 /// </summary>
227 /// <remarks>
228 /// Track Offset is only applied when the Animation Track contains animation that modifies the root Transform.
229 /// </remarks>
230 public TrackOffset trackOffset
231 {
232 get { return m_TrackOffset; }
233 set { m_TrackOffset = value; }
234 }
235
236 /// <summary>
237 /// Specifies which fields to match when aligning offsets of clips.
238 /// </summary>
239 public MatchTargetFields matchTargetFields
240 {
241 get { return m_MatchTargetFields; }
242 set { m_MatchTargetFields = value & MatchTargetFieldConstants.All; }
243 }
244
245 /// <summary>
246 /// An AnimationClip storing the data for an infinite track.
247 /// </summary>
248 /// <remarks>
249 /// The value of this property is null when the AnimationTrack is in Clip Mode.
250 /// </remarks>
251 public AnimationClip infiniteClip
252 {
253 get { return m_InfiniteClip; }
254 internal set { m_InfiniteClip = value; }
255 }
256
257 // saved value for converting to/from infinite mode
258 internal bool infiniteClipRemoveOffset
259 {
260 get { return m_InfiniteClipRemoveOffset; }
261 set { m_InfiniteClipRemoveOffset = value; }
262 }
263
264 /// <summary>
265 /// Specifies the AvatarMask to be applied to all clips on the track.
266 /// </summary>
267 /// <remarks>
268 /// Applying an AvatarMask to an animation track will allow discarding portions of the animation being applied on the track.
269 /// </remarks>
270 public AvatarMask avatarMask
271 {
272 get { return m_AvatarMask; }
273 set { m_AvatarMask = value; }
274 }
275
276 /// <summary>
277 /// Specifies whether to apply the AvatarMask to the track.
278 /// </summary>
279 public bool applyAvatarMask
280 {
281 get { return m_ApplyAvatarMask; }
282 set { m_ApplyAvatarMask = value; }
283 }
284
285 // is this track compilable
286
287 internal override bool CanCompileClips()
288 {
289 return !muted && (m_Clips.Count > 0 || (m_InfiniteClip != null && !m_InfiniteClip.empty));
290 }
291
292 /// <inheritdoc/>
293 public override IEnumerable<PlayableBinding> outputs
294 {
295 get { yield return AnimationPlayableBinding.Create(name, this); }
296 }
297
298
299 /// <summary>
300 /// Specifies whether the Animation Track has clips, or is in infinite mode.
301 /// </summary>
302 public bool inClipMode
303 {
304 get { return clips != null && clips.Length != 0; }
305 }
306
307 /// <summary>
308 /// The translation offset of a track in infinite mode.
309 /// </summary>
310 public Vector3 infiniteClipOffsetPosition
311 {
312 get { return m_InfiniteClipOffsetPosition; }
313 set { m_InfiniteClipOffsetPosition = value; }
314 }
315
316 /// <summary>
317 /// The rotation offset of a track in infinite mode.
318 /// </summary>
319 public Quaternion infiniteClipOffsetRotation
320 {
321 get { return Quaternion.Euler(m_InfiniteClipOffsetEulerAngles); }
322 set { m_InfiniteClipOffsetEulerAngles = value.eulerAngles; }
323 }
324
325 /// <summary>
326 /// The euler angle representation of the rotation offset of the track when in infinite mode.
327 /// </summary>
328 public Vector3 infiniteClipOffsetEulerAngles
329 {
330 get { return m_InfiniteClipOffsetEulerAngles; }
331 set { m_InfiniteClipOffsetEulerAngles = value; }
332 }
333
334 internal bool infiniteClipApplyFootIK
335 {
336 get { return m_InfiniteClipApplyFootIK; }
337 set { m_InfiniteClipApplyFootIK = value; }
338 }
339
340 internal double infiniteClipTimeOffset
341 {
342 get { return m_InfiniteClipTimeOffset; }
343 set { m_InfiniteClipTimeOffset = value; }
344 }
345
346 /// <summary>
347 /// The saved state of pre-extrapolation for clips converted to infinite mode.
348 /// </summary>
349 public TimelineClip.ClipExtrapolation infiniteClipPreExtrapolation
350 {
351 get { return m_InfiniteClipPreExtrapolation; }
352 set { m_InfiniteClipPreExtrapolation = value; }
353 }
354
355 /// <summary>
356 /// The saved state of post-extrapolation for clips when converted to infinite mode.
357 /// </summary>
358 public TimelineClip.ClipExtrapolation infiniteClipPostExtrapolation
359 {
360 get { return m_InfiniteClipPostExtrapolation; }
361 set { m_InfiniteClipPostExtrapolation = value; }
362 }
363
364 /// <summary>
365 /// The saved state of animation clip loop state when converted to infinite mode
366 /// </summary>
367 internal AnimationPlayableAsset.LoopMode infiniteClipLoop
368 {
369 get { return mInfiniteClipLoop; }
370 set { mInfiniteClipLoop = value; }
371 }
372
373 [ContextMenu("Reset Offsets")]
374 void ResetOffsets()
375 {
376 m_Position = Vector3.zero;
377 m_EulerAngles = Vector3.zero;
378 UpdateClipOffsets();
379 }
380
381 /// <summary>
382 /// Creates a TimelineClip on this track that uses an AnimationClip.
383 /// </summary>
384 /// <param name="clip">Source animation clip of the resulting TimelineClip.</param>
385 /// <returns>A new TimelineClip which has an AnimationPlayableAsset asset attached.</returns>
386 public TimelineClip CreateClip(AnimationClip clip)
387 {
388 if (clip == null)
389 return null;
390
391 var newClip = CreateClip<AnimationPlayableAsset>();
392 AssignAnimationClip(newClip, clip);
393 return newClip;
394 }
395
396 /// <summary>
397 /// Creates an AnimationClip that stores the data for an infinite track.
398 /// </summary>
399 /// <remarks>
400 /// If an infiniteClip already exists, this method produces no result, even if you provide a different value
401 /// for infiniteClipName.
402 /// </remarks>
403 /// <remarks>
404 /// This method can't create an infinite clip for an AnimationTrack that contains one or more Timeline clips.
405 /// Use AnimationTrack.inClipMode to determine whether it is possible to create an infinite clip on an AnimationTrack.
406 /// </remarks>
407 /// <remarks>
408 /// When used from the editor, this method attempts to save the created infinite clip to the TimelineAsset.
409 /// The TimelineAsset must already exist in the AssetDatabase to save the infinite clip. If the TimelineAsset
410 /// does not exist, the infinite clip is still created but it is not saved.
411 /// </remarks>
412 /// <param name="infiniteClipName">
413 /// The name of the AnimationClip to create.
414 /// This method does not ensure unique names. If you want a unique clip name, you must provide one.
415 /// See ObjectNames.GetUniqueName for information on a method that creates unique names.
416 /// </param>
417 public void CreateInfiniteClip(string infiniteClipName)
418 {
419 if (inClipMode)
420 {
421 Debug.LogWarning("CreateInfiniteClip cannot create an infinite clip for an AnimationTrack that contains one or more Timeline Clips.");
422 return;
423 }
424
425 if (m_InfiniteClip != null)
426 return;
427
428 m_InfiniteClip = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(infiniteClipName) ? k_DefaultInfiniteClipName : infiniteClipName, this, false);
429 }
430
431 /// <summary>
432 /// Creates a TimelineClip, AnimationPlayableAsset and an AnimationClip. Use this clip to record in a timeline.
433 /// </summary>
434 /// <remarks>
435 /// When used from the editor, this method attempts to save the created recordable clip to the TimelineAsset.
436 /// The TimelineAsset must already exist in the AssetDatabase to save the recordable clip. If the TimelineAsset
437 /// does not exist, the recordable clip is still created but it is not saved.
438 /// </remarks>
439 /// <param name="animClipName">
440 /// The name of the AnimationClip to create.
441 /// This method does not ensure unique names. If you want a unique clip name, you must provide one.
442 /// See ObjectNames.GetUniqueName for information on a method that creates unique names.
443 /// </param>
444 /// <returns>
445 /// Returns a new TimelineClip with an AnimationPlayableAsset asset attached.
446 /// </returns>
447 public TimelineClip CreateRecordableClip(string animClipName)
448 {
449 var clip = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(animClipName) ? k_DefaultRecordableClipName : animClipName, this, false);
450
451 var timelineClip = CreateClip(clip);
452 timelineClip.displayName = animClipName;
453 timelineClip.recordable = true;
454 timelineClip.start = 0;
455 timelineClip.duration = 1;
456
457 var apa = timelineClip.asset as AnimationPlayableAsset;
458 if (apa != null)
459 apa.removeStartOffset = false;
460
461 return timelineClip;
462 }
463
464#if UNITY_EDITOR
465 internal Vector3 sceneOffsetPosition
466 {
467 get { return m_SceneOffsetPosition; }
468 set { m_SceneOffsetPosition = value; }
469 }
470
471 internal Vector3 sceneOffsetRotation
472 {
473 get { return m_SceneOffsetRotation; }
474 set { m_SceneOffsetRotation = value; }
475 }
476
477 internal bool hasPreviewComponents
478 {
479 get
480 {
481 if (m_HasPreviewComponents)
482 return true;
483
484 var parentTrack = parent as AnimationTrack;
485 if (parentTrack != null)
486 {
487 return parentTrack.hasPreviewComponents;
488 }
489
490 return false;
491 }
492 }
493#endif
494
495 /// <summary>
496 /// Used to initialize default values on a newly created clip
497 /// </summary>
498 /// <param name="clip">The clip added to the track</param>
499 protected override void OnCreateClip(TimelineClip clip)
500 {
501 var extrapolation = TimelineClip.ClipExtrapolation.None;
502 if (!isSubTrack)
503 extrapolation = TimelineClip.ClipExtrapolation.Hold;
504 clip.preExtrapolationMode = extrapolation;
505 clip.postExtrapolationMode = extrapolation;
506 }
507
508 protected internal override int CalculateItemsHash()
509 {
510 return GetAnimationClipHash(m_InfiniteClip).CombineHash(base.CalculateItemsHash());
511 }
512
513 internal void UpdateClipOffsets()
514 {
515#if UNITY_EDITOR
516 if (m_ClipOffset.IsValid())
517 {
518 m_ClipOffset.SetPosition(position);
519 m_ClipOffset.SetRotation(rotation);
520 }
521#endif
522 }
523
524 Playable CompileTrackPlayable(PlayableGraph graph, AnimationTrack track, GameObject go, IntervalTree<RuntimeElement> tree, AppliedOffsetMode mode)
525 {
526 var mixer = AnimationMixerPlayable.Create(graph, track.clips.Length);
527 for (int i = 0; i < track.clips.Length; i++)
528 {
529 var c = track.clips[i];
530 var asset = c.asset as PlayableAsset;
531 if (asset == null)
532 continue;
533
534 var animationAsset = asset as AnimationPlayableAsset;
535 if (animationAsset != null)
536 animationAsset.appliedOffsetMode = mode;
537
538 var source = asset.CreatePlayable(graph, go);
539 if (source.IsValid())
540 {
541 var clip = new RuntimeClip(c, source, mixer);
542 tree.Add(clip);
543 graph.Connect(source, 0, mixer, i);
544 mixer.SetInputWeight(i, 0.0f);
545 }
546 }
547
548 if (!track.AnimatesRootTransform())
549 return mixer;
550
551 return ApplyTrackOffset(graph, mixer, go, mode);
552 }
553
554 /// <inheritdoc cref="ILayerable.CreateLayerMixer"/>
555 /// <returns>Returns <c>Playable.Null</c></returns>
556 Playable ILayerable.CreateLayerMixer(PlayableGraph graph, GameObject go, int inputCount)
557 {
558 return Playable.Null;
559 }
560
561 internal override Playable CreateMixerPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree)
562 {
563 if (isSubTrack)
564 throw new InvalidOperationException("Nested animation tracks should never be asked to create a graph directly");
565
566 List<AnimationTrack> flattenTracks = new List<AnimationTrack>();
567 if (CanCompileClips())
568 flattenTracks.Add(this);
569
570 var genericRoot = GetGenericRootNode(go);
571 var animatesRootTransformNoMask = AnimatesRootTransform();
572 var animatesRootTransform = animatesRootTransformNoMask && !IsRootTransformDisabledByMask(go, genericRoot);
573 foreach (var subTrack in GetChildTracks())
574 {
575 var child = subTrack as AnimationTrack;
576 if (child != null && child.CanCompileClips())
577 {
578 var childAnimatesRoot = child.AnimatesRootTransform();
579 animatesRootTransformNoMask |= child.AnimatesRootTransform();
580 animatesRootTransform |= (childAnimatesRoot && !child.IsRootTransformDisabledByMask(go, genericRoot));
581 flattenTracks.Add(child);
582 }
583 }
584
585 // figure out which mode to apply
586 AppliedOffsetMode mode = GetOffsetMode(go, animatesRootTransform);
587 int defaultBlendCount = GetDefaultBlendCount();
588 var layerMixer = CreateGroupMixer(graph, go, flattenTracks.Count + defaultBlendCount);
589 for (int c = 0; c < flattenTracks.Count; c++)
590 {
591 int blendIndex = c + defaultBlendCount;
592 // if the child is masking the root transform, compile it as if we are non-root mode
593 var childMode = mode;
594 if (mode != AppliedOffsetMode.NoRootTransform && flattenTracks[c].IsRootTransformDisabledByMask(go, genericRoot))
595 childMode = AppliedOffsetMode.NoRootTransform;
596
597 var compiledTrackPlayable = flattenTracks[c].inClipMode ?
598 CompileTrackPlayable(graph, flattenTracks[c], go, tree, childMode) :
599 flattenTracks[c].CreateInfiniteTrackPlayable(graph, go, tree, childMode);
600 graph.Connect(compiledTrackPlayable, 0, layerMixer, blendIndex);
601 layerMixer.SetInputWeight(blendIndex, flattenTracks[c].inClipMode ? 0 : 1);
602 if (flattenTracks[c].applyAvatarMask && flattenTracks[c].avatarMask != null)
603 {
604 layerMixer.SetLayerMaskFromAvatarMask((uint)blendIndex, flattenTracks[c].avatarMask);
605 }
606 }
607
608 var requiresMotionXPlayable = RequiresMotionXPlayable(mode, go);
609
610 // In the editor, we may require the motion X playable if we are animating the root transform but it is masked out, because the default poses
611 // need to properly update root motion
612 requiresMotionXPlayable |= (defaultBlendCount > 0 && RequiresMotionXPlayable(GetOffsetMode(go, animatesRootTransformNoMask), go));
613
614 // Attach the default poses
615 AttachDefaultBlend(graph, layerMixer, requiresMotionXPlayable);
616
617 // motionX playable not required in scene offset mode, or root transform mode
618 Playable mixer = layerMixer;
619 if (requiresMotionXPlayable)
620 {
621 // If we are animating a root transform, add the motionX to delta playable as the root node
622 var motionXToDelta = AnimationMotionXToDeltaPlayable.Create(graph);
623 graph.Connect(mixer, 0, motionXToDelta, 0);
624 motionXToDelta.SetInputWeight(0, 1.0f);
625 motionXToDelta.SetAbsoluteMotion(UsesAbsoluteMotion(mode));
626 mixer = (Playable)motionXToDelta;
627 }
628
629
630#if UNITY_EDITOR
631 if (!Application.isPlaying)
632 {
633 var animator = GetBinding(go != null ? go.GetComponent<PlayableDirector>() : null);
634 if (animator != null)
635 {
636 GameObject targetGO = animator.gameObject;
637 IAnimationWindowPreview[] previewComponents = targetGO.GetComponents<IAnimationWindowPreview>();
638
639 m_HasPreviewComponents = previewComponents.Length > 0;
640 if (m_HasPreviewComponents)
641 {
642 foreach (var component in previewComponents)
643 {
644 mixer = component.BuildPreviewGraph(graph, mixer);
645 }
646 }
647 }
648 }
649#endif
650
651 return mixer;
652 }
653
654 private int GetDefaultBlendCount()
655 {
656#if UNITY_EDITOR
657 if (Application.isPlaying)
658 return 0;
659
660 return ((m_CachedPropertiesClip != null) ? 1 : 0) + ((m_DefaultPoseClip != null) ? 1 : 0);
661#else
662 return 0;
663#endif
664 }
665
666 // Attaches the default blends to the layer mixer
667 // the base layer is a default clip of all driven properties
668 // the next layer is optionally the desired default pose (in the case of humanoid, the TPose)
669 private void AttachDefaultBlend(PlayableGraph graph, AnimationLayerMixerPlayable mixer, bool requireOffset)
670 {
671#if UNITY_EDITOR
672 if (Application.isPlaying)
673 return;
674
675 int mixerInput = 0;
676 if (m_CachedPropertiesClip)
677 {
678 var cachedPropertiesClip = AnimationClipPlayable.Create(graph, m_CachedPropertiesClip);
679 cachedPropertiesClip.SetApplyFootIK(false);
680 var defaults = (Playable)cachedPropertiesClip;
681 if (requireOffset)
682 defaults = AttachOffsetPlayable(graph, defaults, m_SceneOffsetPosition, Quaternion.Euler(m_SceneOffsetRotation));
683 graph.Connect(defaults, 0, mixer, mixerInput);
684 mixer.SetInputWeight(mixerInput, 1.0f);
685 mixerInput++;
686 }
687
688 if (m_DefaultPoseClip)
689 {
690 var defaultPose = AnimationClipPlayable.Create(graph, m_DefaultPoseClip);
691 defaultPose.SetApplyFootIK(false);
692 var blendDefault = (Playable)defaultPose;
693 if (requireOffset)
694 blendDefault = AttachOffsetPlayable(graph, blendDefault, m_SceneOffsetPosition, Quaternion.Euler(m_SceneOffsetRotation));
695 graph.Connect(blendDefault, 0, mixer, mixerInput);
696 mixer.SetInputWeight(mixerInput, 1.0f);
697 }
698#endif
699 }
700
701 private Playable AttachOffsetPlayable(PlayableGraph graph, Playable playable, Vector3 pos, Quaternion rot)
702 {
703 var offsetPlayable = AnimationOffsetPlayable.Create(graph, pos, rot, 1);
704 offsetPlayable.SetInputWeight(0, 1.0f);
705 graph.Connect(playable, 0, offsetPlayable, 0);
706 return offsetPlayable;
707 }
708
709#if UNITY_EDITOR
710 private static string k_DefaultHumanoidClipPath = "Packages/com.unity.timeline/Editor/StyleSheets/res/HumanoidDefault.anim";
711 private static AnimationClip s_DefaultHumanoidClip = null;
712
713 AnimationClip GetDefaultHumanoidClip()
714 {
715 if (s_DefaultHumanoidClip == null)
716 {
717 s_DefaultHumanoidClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(k_DefaultHumanoidClipPath);
718 if (s_DefaultHumanoidClip == null)
719 Debug.LogError("Could not load default humanoid animation clip for Timeline");
720 }
721
722 return s_DefaultHumanoidClip;
723 }
724
725#endif
726
727 bool RequiresMotionXPlayable(AppliedOffsetMode mode, GameObject gameObject)
728 {
729 if (mode == AppliedOffsetMode.NoRootTransform)
730 return false;
731 if (mode == AppliedOffsetMode.SceneOffsetLegacy)
732 {
733 var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
734 return animator != null && animator.hasRootMotion;
735 }
736 return true;
737 }
738
739 static bool UsesAbsoluteMotion(AppliedOffsetMode mode)
740 {
741#if UNITY_EDITOR
742 // in editor, previewing is always done in absolute motion
743 if (!Application.isPlaying)
744 return true;
745#endif
746 return mode != AppliedOffsetMode.SceneOffset &&
747 mode != AppliedOffsetMode.SceneOffsetLegacy;
748 }
749
750 bool HasController(GameObject gameObject)
751 {
752 var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
753
754 return animator != null && animator.runtimeAnimatorController != null;
755 }
756
757 internal Animator GetBinding(PlayableDirector director)
758 {
759 if (director == null)
760 return null;
761
762 UnityEngine.Object key = this;
763 if (isSubTrack)
764 key = parent;
765
766 UnityEngine.Object binding = null;
767 if (director != null)
768 binding = director.GetGenericBinding(key);
769
770 Animator animator = null;
771 if (binding != null) // the binding can be an animator or game object
772 {
773 animator = binding as Animator;
774 var gameObject = binding as GameObject;
775 if (animator == null && gameObject != null)
776 animator = gameObject.GetComponent<Animator>();
777 }
778
779 return animator;
780 }
781
782 static AnimationLayerMixerPlayable CreateGroupMixer(PlayableGraph graph, GameObject go, int inputCount)
783 {
784#if UNITY_2022_2_OR_NEWER
785 return AnimationLayerMixerPlayable.Create(graph, inputCount, false);
786#else
787 return AnimationLayerMixerPlayable.Create(graph, inputCount);
788#endif
789 }
790
791 Playable CreateInfiniteTrackPlayable(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, AppliedOffsetMode mode)
792 {
793 if (m_InfiniteClip == null)
794 return Playable.Null;
795
796 var mixer = AnimationMixerPlayable.Create(graph, 1);
797
798 // In infinite mode, we always force the loop mode of the clip off because the clip keys are offset in infinite mode
799 // which causes loop to behave different.
800 // The inline curve editor never shows loops in infinite mode.
801 var playable = AnimationPlayableAsset.CreatePlayable(graph, m_InfiniteClip, m_InfiniteClipOffsetPosition, m_InfiniteClipOffsetEulerAngles, false, mode, infiniteClipApplyFootIK, AnimationPlayableAsset.LoopMode.Off);
802 if (playable.IsValid())
803 {
804 tree.Add(new InfiniteRuntimeClip(playable));
805 graph.Connect(playable, 0, mixer, 0);
806 mixer.SetInputWeight(0, 1.0f);
807 }
808
809 if (!AnimatesRootTransform())
810 return mixer;
811
812 var rootTrack = isSubTrack ? (AnimationTrack)parent : this;
813 return rootTrack.ApplyTrackOffset(graph, mixer, go, mode);
814 }
815
816 Playable ApplyTrackOffset(PlayableGraph graph, Playable root, GameObject go, AppliedOffsetMode mode)
817 {
818#if UNITY_EDITOR
819 m_ClipOffset = AnimationOffsetPlayable.Null;
820#endif
821
822 // offsets don't apply in scene offset, or if there is no root transform (globally or on this track)
823 if (mode == AppliedOffsetMode.SceneOffsetLegacy ||
824 mode == AppliedOffsetMode.SceneOffset ||
825 mode == AppliedOffsetMode.NoRootTransform
826 )
827 return root;
828
829
830 var pos = position;
831 var rot = rotation;
832
833#if UNITY_EDITOR
834 // in the editor use the preview position to playback from if available
835 if (mode == AppliedOffsetMode.SceneOffsetEditor)
836 {
837 pos = m_SceneOffsetPosition;
838 rot = Quaternion.Euler(m_SceneOffsetRotation);
839 }
840#endif
841
842 var offsetPlayable = AnimationOffsetPlayable.Create(graph, pos, rot, 1);
843#if UNITY_EDITOR
844 m_ClipOffset = offsetPlayable;
845#endif
846 graph.Connect(root, 0, offsetPlayable, 0);
847 offsetPlayable.SetInputWeight(0, 1);
848
849 return offsetPlayable;
850 }
851
852 // the evaluation time is large so that the properties always get evaluated
853 internal override void GetEvaluationTime(out double outStart, out double outDuration)
854 {
855 if (inClipMode)
856 {
857 base.GetEvaluationTime(out outStart, out outDuration);
858 }
859 else
860 {
861 outStart = 0;
862 outDuration = TimelineClip.kMaxTimeValue;
863 }
864 }
865
866 internal override void GetSequenceTime(out double outStart, out double outDuration)
867 {
868 if (inClipMode)
869 {
870 base.GetSequenceTime(out outStart, out outDuration);
871 }
872 else
873 {
874 outStart = 0;
875 outDuration = Math.Max(GetNotificationDuration(), TimeUtility.GetAnimationClipLength(m_InfiniteClip));
876 }
877 }
878
879 void AssignAnimationClip(TimelineClip clip, AnimationClip animClip)
880 {
881 if (clip == null || animClip == null)
882 return;
883
884 if (animClip.legacy)
885 throw new InvalidOperationException("Legacy Animation Clips are not supported");
886
887 AnimationPlayableAsset asset = clip.asset as AnimationPlayableAsset;
888 if (asset != null)
889 {
890 asset.clip = animClip;
891 asset.name = animClip.name;
892 var duration = asset.duration;
893 if (!double.IsInfinity(duration) && duration >= TimelineClip.kMinDuration && duration < TimelineClip.kMaxTimeValue)
894 clip.duration = duration;
895 }
896 clip.displayName = animClip.name;
897 }
898
899 /// <summary>
900 /// Called by the Timeline Editor to gather properties requiring preview.
901 /// </summary>
902 /// <param name="director">The PlayableDirector invoking the preview</param>
903 /// <param name="driver">PropertyCollector used to gather previewable properties</param>
904 public override void GatherProperties(PlayableDirector director, IPropertyCollector driver)
905 {
906#if UNITY_EDITOR
907 m_SceneOffsetPosition = Vector3.zero;
908 m_SceneOffsetRotation = Vector3.zero;
909
910 var animator = GetBinding(director);
911 if (animator == null)
912 return;
913
914 var animClips = new List<AnimationClip>(this.clips.Length + 2);
915 GetAnimationClips(animClips);
916
917 var hasHumanMotion = animClips.Exists(clip => clip.humanMotion);
918 // case 1174752 - recording root transform on humanoid clips clips cause invalid pose. This will apply the default T-Pose, only if it not already driven by another track
919 if (!hasHumanMotion && animator.isHuman && AnimatesRootTransform() &&
920 !DrivenPropertyManagerInternal.IsDriven(animator.transform, "m_LocalPosition.x") &&
921 !DrivenPropertyManagerInternal.IsDriven(animator.transform, "m_LocalRotation.x"))
922 hasHumanMotion = true;
923
924 m_SceneOffsetPosition = animator.transform.localPosition;
925 m_SceneOffsetRotation = animator.transform.localEulerAngles;
926
927 // Create default pose clip from collected properties
928 if (hasHumanMotion)
929 animClips.Add(GetDefaultHumanoidClip());
930
931 m_DefaultPoseClip = hasHumanMotion ? GetDefaultHumanoidClip() : null;
932 var hash = AnimationPreviewUtilities.GetClipHash(animClips);
933 if (m_CachedBindings == null || m_CachedHash != hash)
934 {
935 m_CachedBindings = AnimationPreviewUtilities.GetBindings(animator.gameObject, animClips);
936 m_CachedPropertiesClip = AnimationPreviewUtilities.CreateDefaultClip(animator.gameObject, m_CachedBindings);
937 m_CachedHash = hash;
938 }
939
940 AnimationPreviewUtilities.PreviewFromCurves(animator.gameObject, m_CachedBindings); // faster to preview from curves then an animation clip
941#endif
942 }
943
944 /// <summary>
945 /// Gather all the animation clips for this track
946 /// </summary>
947 /// <param name="animClips"></param>
948 private void GetAnimationClips(List<AnimationClip> animClips)
949 {
950 foreach (var c in clips)
951 {
952 var a = c.asset as AnimationPlayableAsset;
953 if (a != null && a.clip != null)
954 animClips.Add(a.clip);
955 }
956
957 if (m_InfiniteClip != null)
958 animClips.Add(m_InfiniteClip);
959
960 foreach (var childTrack in GetChildTracks())
961 {
962 var animChildTrack = childTrack as AnimationTrack;
963 if (animChildTrack != null)
964 animChildTrack.GetAnimationClips(animClips);
965 }
966 }
967
968 // calculate which offset mode to apply
969 AppliedOffsetMode GetOffsetMode(GameObject go, bool animatesRootTransform)
970 {
971 if (!animatesRootTransform)
972 return AppliedOffsetMode.NoRootTransform;
973
974 if (m_TrackOffset == TrackOffset.ApplyTransformOffsets)
975 return AppliedOffsetMode.TransformOffset;
976
977 if (m_TrackOffset == TrackOffset.ApplySceneOffsets)
978 return (Application.isPlaying) ? AppliedOffsetMode.SceneOffset : AppliedOffsetMode.SceneOffsetEditor;
979
980 if (HasController(go))
981 {
982 if (!Application.isPlaying)
983 return AppliedOffsetMode.SceneOffsetLegacyEditor;
984 return AppliedOffsetMode.SceneOffsetLegacy;
985 }
986
987 return AppliedOffsetMode.TransformOffsetLegacy;
988 }
989
990 private bool IsRootTransformDisabledByMask(GameObject gameObject, Transform genericRootNode)
991 {
992 if (avatarMask == null || !applyAvatarMask)
993 return false;
994
995 var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
996 if (animator == null)
997 return false;
998
999 if (animator.isHuman)
1000 return !avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.Root);
1001
1002 if (avatarMask.transformCount == 0)
1003 return false;
1004
1005 // no special root supplied
1006 if (genericRootNode == null)
1007 return string.IsNullOrEmpty(avatarMask.GetTransformPath(0)) && !avatarMask.GetTransformActive(0);
1008
1009 // walk the avatar list to find the matching transform
1010 for (int i = 0; i < avatarMask.transformCount; i++)
1011 {
1012 if (genericRootNode == animator.transform.Find(avatarMask.GetTransformPath(i)))
1013 return !avatarMask.GetTransformActive(i);
1014 }
1015
1016 return false;
1017 }
1018
1019 // Returns the generic root transform node. Returns null if it is the root node, OR if it not a generic node
1020 private Transform GetGenericRootNode(GameObject gameObject)
1021 {
1022 var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
1023 if (animator == null)
1024 return null;
1025
1026 if (animator.isHuman)
1027 return null;
1028
1029 if (animator.avatar == null)
1030 return null;
1031
1032 // this returns the bone name, but not the full path
1033 var rootName = animator.avatar.humanDescription.m_RootMotionBoneName;
1034 if (rootName == animator.name || string.IsNullOrEmpty(rootName))
1035 return null;
1036
1037 // walk the hierarchy to find the first bone with this name
1038 return FindInHierarchyBreadthFirst(animator.transform, rootName);
1039 }
1040
1041 internal bool AnimatesRootTransform()
1042 {
1043 // infinite mode
1044 if (AnimationPlayableAsset.HasRootTransforms(m_InfiniteClip))
1045 return true;
1046
1047 // clip mode
1048 foreach (var c in GetClips())
1049 {
1050 var apa = c.asset as AnimationPlayableAsset;
1051 if (apa != null && apa.hasRootTransforms)
1052 return true;
1053 }
1054
1055 return false;
1056 }
1057
1058 private static readonly Queue<Transform> s_CachedQueue = new Queue<Transform>(100);
1059 private static Transform FindInHierarchyBreadthFirst(Transform t, string name)
1060 {
1061 s_CachedQueue.Clear();
1062 s_CachedQueue.Enqueue(t);
1063 while (s_CachedQueue.Count > 0)
1064 {
1065 var r = s_CachedQueue.Dequeue();
1066 if (r.name == name)
1067 return r;
1068 for (int i = 0; i < r.childCount; i++)
1069 s_CachedQueue.Enqueue(r.GetChild(i));
1070 }
1071
1072 return null;
1073 }
1074 }
1075}