327 lines
10 KiB
C#
327 lines
10 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine.Animations;
|
|
using UnityEngine.Playables;
|
|
|
|
namespace UnityEngine.Timeline
|
|
{
|
|
/// <summary>
|
|
/// A Playable Asset that represents a single AnimationClip clip.
|
|
/// </summary>
|
|
[System.Serializable, NotKeyable]
|
|
public partial class AnimationPlayableAsset : PlayableAsset, ITimelineClipAsset, IPropertyPreview
|
|
{
|
|
/// <summary>
|
|
/// Whether the source AnimationClip loops during playback.
|
|
/// </summary>
|
|
public enum LoopMode
|
|
{
|
|
/// <summary>
|
|
/// Use the loop time setting from the source AnimationClip.
|
|
/// </summary>
|
|
[Tooltip("Use the loop time setting from the source AnimationClip.")]
|
|
UseSourceAsset = 0,
|
|
|
|
/// <summary>
|
|
/// The source AnimationClip loops during playback.
|
|
/// </summary>
|
|
[Tooltip("The source AnimationClip loops during playback.")]
|
|
On = 1,
|
|
|
|
/// <summary>
|
|
/// The source AnimationClip does not loop during playback.
|
|
/// </summary>
|
|
[Tooltip("The source AnimationClip does not loop during playback.")]
|
|
Off = 2
|
|
}
|
|
|
|
|
|
[SerializeField] private AnimationClip m_Clip;
|
|
[SerializeField] private Vector3 m_Position = Vector3.zero;
|
|
[SerializeField] private Vector3 m_EulerAngles = Vector3.zero;
|
|
[SerializeField] private bool m_UseTrackMatchFields = true;
|
|
[SerializeField] private MatchTargetFields m_MatchTargetFields = MatchTargetFieldConstants.All;
|
|
[SerializeField] private bool m_RemoveStartOffset = true; // set by animation track prior to compilation
|
|
[SerializeField] private bool m_ApplyFootIK = true;
|
|
[SerializeField] private LoopMode m_Loop = LoopMode.UseSourceAsset;
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
private AnimationOffsetPlayable m_AnimationOffsetPlayable;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The translational offset of the clip
|
|
/// </summary>
|
|
public Vector3 position
|
|
{
|
|
get
|
|
{
|
|
return m_Position;
|
|
}
|
|
set
|
|
{
|
|
m_Position = value;
|
|
#if UNITY_EDITOR
|
|
if (m_AnimationOffsetPlayable.IsValid())
|
|
m_AnimationOffsetPlayable.SetPosition(position);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The rotational offset of the clip, expressed as a Quaternion
|
|
/// </summary>
|
|
public Quaternion rotation
|
|
{
|
|
get
|
|
{
|
|
return Quaternion.Euler(m_EulerAngles);
|
|
}
|
|
|
|
set
|
|
{
|
|
m_EulerAngles = value.eulerAngles;
|
|
#if UNITY_EDITOR
|
|
if (m_AnimationOffsetPlayable.IsValid())
|
|
m_AnimationOffsetPlayable.SetRotation(value);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The rotational offset of the clip, expressed in Euler angles
|
|
/// </summary>
|
|
public Vector3 eulerAngles
|
|
{
|
|
get { return m_EulerAngles; }
|
|
set
|
|
{
|
|
m_EulerAngles = value;
|
|
#if UNITY_EDITOR
|
|
if (m_AnimationOffsetPlayable.IsValid())
|
|
m_AnimationOffsetPlayable.SetRotation(rotation);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies whether to use offset matching options as defined by the track.
|
|
/// </summary>
|
|
public bool useTrackMatchFields
|
|
{
|
|
get { return m_UseTrackMatchFields; }
|
|
set { m_UseTrackMatchFields = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies which fields should be matched when aligning offsets.
|
|
/// </summary>
|
|
public MatchTargetFields matchTargetFields
|
|
{
|
|
get { return m_MatchTargetFields; }
|
|
set { m_MatchTargetFields = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether to make the animation clip play relative to its first keyframe.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This option only applies to animation clips that animate Transform components.
|
|
/// </remarks>
|
|
public bool removeStartOffset
|
|
{
|
|
get { return m_RemoveStartOffset; }
|
|
set { m_RemoveStartOffset = value; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Enable to apply foot IK to the AnimationClip when the target is humanoid.
|
|
/// </summary>
|
|
public bool applyFootIK
|
|
{
|
|
get { return m_ApplyFootIK; }
|
|
set { m_ApplyFootIK = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the source AnimationClip loops during playback
|
|
/// </summary>
|
|
public LoopMode loop
|
|
{
|
|
get { return m_Loop; }
|
|
set { m_Loop = value; }
|
|
}
|
|
|
|
|
|
internal bool hasRootTransforms
|
|
{
|
|
get { return m_Clip != null && HasRootTransforms(m_Clip); }
|
|
}
|
|
|
|
// used for legacy 'scene' mode.
|
|
internal AppliedOffsetMode appliedOffsetMode { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
/// The source animation clip
|
|
/// </summary>
|
|
public AnimationClip clip
|
|
{
|
|
get { return m_Clip; }
|
|
set
|
|
{
|
|
if (value != null)
|
|
name = "AnimationPlayableAsset of " + value.name;
|
|
m_Clip = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the duration required to play the animation clip exactly once
|
|
/// </summary>
|
|
public override double duration
|
|
{
|
|
get
|
|
{
|
|
double length = TimeUtility.GetAnimationClipLength(clip);
|
|
if (length < float.Epsilon)
|
|
return base.duration;
|
|
return length;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a description of the PlayableOutputs that may be created for this asset.
|
|
/// </summary>
|
|
public override IEnumerable<PlayableBinding> outputs
|
|
{
|
|
get { yield return AnimationPlayableBinding.Create(name, this); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the root of a Playable subgraph to play the animation clip.
|
|
/// </summary>
|
|
/// <param name="graph">PlayableGraph that will own the playable</param>
|
|
/// <param name="go">The gameobject that triggered the graph build</param>
|
|
/// <returns>The root playable of the subgraph</returns>
|
|
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
|
{
|
|
Playable root = CreatePlayable(graph, m_Clip, position, eulerAngles, removeStartOffset, appliedOffsetMode, applyFootIK, m_Loop);
|
|
|
|
#if UNITY_EDITOR
|
|
m_AnimationOffsetPlayable = AnimationOffsetPlayable.Null;
|
|
if (root.IsValid() && root.IsPlayableOfType<AnimationOffsetPlayable>())
|
|
{
|
|
m_AnimationOffsetPlayable = (AnimationOffsetPlayable)root;
|
|
}
|
|
|
|
LiveLink();
|
|
#endif
|
|
|
|
return root;
|
|
}
|
|
|
|
internal static Playable CreatePlayable(PlayableGraph graph, AnimationClip clip, Vector3 positionOffset, Vector3 eulerOffset, bool removeStartOffset, AppliedOffsetMode mode, bool applyFootIK, LoopMode loop)
|
|
{
|
|
if (clip == null || clip.legacy)
|
|
return Playable.Null;
|
|
|
|
|
|
var clipPlayable = AnimationClipPlayable.Create(graph, clip);
|
|
clipPlayable.SetRemoveStartOffset(removeStartOffset);
|
|
clipPlayable.SetApplyFootIK(applyFootIK);
|
|
clipPlayable.SetOverrideLoopTime(loop != LoopMode.UseSourceAsset);
|
|
clipPlayable.SetLoopTime(loop == LoopMode.On);
|
|
|
|
Playable root = clipPlayable;
|
|
|
|
if (ShouldApplyScaleRemove(mode))
|
|
{
|
|
var removeScale = AnimationRemoveScalePlayable.Create(graph, 1);
|
|
graph.Connect(root, 0, removeScale, 0);
|
|
removeScale.SetInputWeight(0, 1.0f);
|
|
root = removeScale;
|
|
}
|
|
|
|
if (ShouldApplyOffset(mode, clip))
|
|
{
|
|
var offsetPlayable = AnimationOffsetPlayable.Create(graph, positionOffset, Quaternion.Euler(eulerOffset), 1);
|
|
graph.Connect(root, 0, offsetPlayable, 0);
|
|
offsetPlayable.SetInputWeight(0, 1.0F);
|
|
root = offsetPlayable;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
private static bool ShouldApplyOffset(AppliedOffsetMode mode, AnimationClip clip)
|
|
{
|
|
if (mode == AppliedOffsetMode.NoRootTransform || mode == AppliedOffsetMode.SceneOffsetLegacy)
|
|
return false;
|
|
|
|
return HasRootTransforms(clip);
|
|
}
|
|
|
|
private static bool ShouldApplyScaleRemove(AppliedOffsetMode mode)
|
|
{
|
|
return mode == AppliedOffsetMode.SceneOffsetLegacyEditor || mode == AppliedOffsetMode.SceneOffsetLegacy || mode == AppliedOffsetMode.TransformOffsetLegacy;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public void LiveLink()
|
|
{
|
|
if (m_AnimationOffsetPlayable.IsValid())
|
|
{
|
|
m_AnimationOffsetPlayable.SetPosition(position);
|
|
m_AnimationOffsetPlayable.SetRotation(rotation);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Returns the capabilities of TimelineClips that contain a AnimationPlayableAsset
|
|
/// </summary>
|
|
public ClipCaps clipCaps
|
|
{
|
|
get
|
|
{
|
|
var caps = ClipCaps.All;
|
|
if (m_Clip == null || (m_Loop == LoopMode.Off) || (m_Loop == LoopMode.UseSourceAsset && !m_Clip.isLooping))
|
|
caps &= ~ClipCaps.Looping;
|
|
|
|
// empty clips don't support clip in. This allows trim operations to simply become
|
|
// move operations
|
|
if (m_Clip == null || m_Clip.empty)
|
|
caps &= ~ClipCaps.ClipIn;
|
|
|
|
return caps;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the offsets to default values
|
|
/// </summary>
|
|
public void ResetOffsets()
|
|
{
|
|
position = Vector3.zero;
|
|
eulerAngles = Vector3.zero;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
|
{
|
|
driver.AddFromClip(m_Clip);
|
|
}
|
|
|
|
internal static bool HasRootTransforms(AnimationClip clip)
|
|
{
|
|
if (clip == null || clip.empty)
|
|
return false;
|
|
|
|
return clip.hasRootMotion || clip.hasGenericRootTransform || clip.hasMotionCurves || clip.hasRootCurves;
|
|
}
|
|
}
|
|
}
|