A game about forced loneliness, made by TACStudios
at master 1075 lines 42 kB view raw
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}