File size: 8,187 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
/// <summary>
/// Extension Methods for AnimationTracks that require the Unity Editor, and may require the Timeline containing the Animation Track to be currently loaded in the Timeline Editor Window.
/// </summary>
public static class AnimationTrackExtensions
{
/// <summary>
/// Determines whether the Timeline window can enable recording mode on an AnimationTrack.
/// For a track to support recording, it needs to have a valid scene binding,
/// its offset mode should not be Auto and needs to be currently visible in the Timeline Window.
/// </summary>
/// <param name="track">The track to query.</param>
/// <returns>True if recording can start, False otherwise.</returns>
public static bool CanStartRecording(this AnimationTrack track)
{
if (track == null)
{
throw new ArgumentNullException(nameof(track));
}
if (TimelineEditor.state == null)
{
return false;
}
var director = TimelineEditor.inspectedDirector;
var animTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
return animTrack != null && animTrack.trackOffset != TrackOffset.Auto &&
TimelineEditor.inspectedAsset == animTrack.timelineAsset &&
director != null && TimelineUtility.GetSceneGameObject(director, animTrack) != null;
}
/// <summary>
/// Method that allows querying if a track is current enabled for animation recording.
/// </summary>
/// <param name="track">The track to query.</param>
/// <returns>True if currently recording and False otherwise.</returns>
public static bool IsRecording(this AnimationTrack track)
{
if (track == null)
{
throw new ArgumentNullException(nameof(track));
}
return TimelineEditor.state != null && TimelineEditor.state.IsArmedForRecord(track);
}
/// <summary>
/// Method that enables animation recording for an AnimationTrack.
/// </summary>
/// <param name="track">The AnimationTrack which will be put in recording mode.</param>
/// <returns>True if track was put successfully in recording mode, False otherwise. </returns>
public static bool StartRecording(this AnimationTrack track)
{
if (!CanStartRecording(track))
{
return false;
}
TimelineEditor.state.ArmForRecord(track);
return true;
}
/// <summary>
/// Disables recording mode of an AnimationTrack.
/// </summary>
/// <param name="track">The AnimationTrack which will be taken out of recording mode.</param>
public static void StopRecording(this AnimationTrack track)
{
if (!IsRecording(track) || TimelineEditor.state == null)
{
return;
}
TimelineEditor.state.UnarmForRecord(track);
}
internal static void ConvertToClipMode(this AnimationTrack track)
{
if (!track.CanConvertToClipMode())
return;
UndoExtensions.RegisterTrack(track, L10n.Tr("Convert To Clip"));
if (!track.infiniteClip.empty)
{
var animClip = track.infiniteClip;
TimelineUndo.PushUndo(animClip, L10n.Tr("Convert To Clip"));
UndoExtensions.RegisterTrack(track, L10n.Tr("Convert To Clip"));
var start = AnimationClipCurveCache.Instance.GetCurveInfo(animClip).keyTimes.FirstOrDefault();
animClip.ShiftBySeconds(-start);
track.infiniteClip = null;
var clip = track.CreateClip(animClip);
clip.start = start;
clip.preExtrapolationMode = track.infiniteClipPreExtrapolation;
clip.postExtrapolationMode = track.infiniteClipPostExtrapolation;
clip.recordable = true;
if (Mathf.Abs(animClip.length) < TimelineClip.kMinDuration)
{
clip.duration = 1;
}
var animationAsset = clip.asset as AnimationPlayableAsset;
if (animationAsset)
{
animationAsset.position = track.infiniteClipOffsetPosition;
animationAsset.eulerAngles = track.infiniteClipOffsetEulerAngles;
// going to / from infinite mode should reset this. infinite mode
animationAsset.removeStartOffset = track.infiniteClipRemoveOffset;
animationAsset.applyFootIK = track.infiniteClipApplyFootIK;
animationAsset.loop = track.infiniteClipLoop;
track.infiniteClipOffsetPosition = Vector3.zero;
track.infiniteClipOffsetEulerAngles = Vector3.zero;
}
track.CalculateExtrapolationTimes();
}
track.infiniteClip = null;
EditorUtility.SetDirty(track);
}
internal static void ConvertFromClipMode(this AnimationTrack track, TimelineAsset timeline)
{
if (!track.CanConvertFromClipMode())
return;
UndoExtensions.RegisterTrack(track, L10n.Tr("Convert From Clip"));
var clip = track.clips[0];
var delta = (float)clip.start;
track.infiniteClipTimeOffset = 0.0f;
track.infiniteClipPreExtrapolation = clip.preExtrapolationMode;
track.infiniteClipPostExtrapolation = clip.postExtrapolationMode;
var animAsset = clip.asset as AnimationPlayableAsset;
if (animAsset)
{
track.infiniteClipOffsetPosition = animAsset.position;
track.infiniteClipOffsetEulerAngles = animAsset.eulerAngles;
track.infiniteClipRemoveOffset = animAsset.removeStartOffset;
track.infiniteClipApplyFootIK = animAsset.applyFootIK;
track.infiniteClipLoop = animAsset.loop;
}
// clone it, it may not be in the same asset
var animClip = clip.animationClip;
float scale = (float)clip.timeScale;
if (!Mathf.Approximately(scale, 1.0f))
{
if (!Mathf.Approximately(scale, 0.0f))
scale = 1.0f / scale;
animClip.ScaleTime(scale);
}
TimelineUndo.PushUndo(animClip, L10n.Tr("Convert From Clip"));
animClip.ShiftBySeconds(delta);
// manually delete the clip
var asset = clip.asset;
clip.asset = null;
// Remove the clip, remove old assets
ClipModifier.Delete(timeline, clip);
TimelineUndo.PushDestroyUndo(null, track, asset);
track.infiniteClip = animClip;
EditorUtility.SetDirty(track);
}
internal static bool CanConvertToClipMode(this AnimationTrack track)
{
if (track == null || track.inClipMode)
return false;
return (track.infiniteClip != null && !track.infiniteClip.empty);
}
// Requirements to go from clip mode
// - one clip, recordable, and animation clip belongs to the same asset as the track
internal static bool CanConvertFromClipMode(this AnimationTrack track)
{
if ((track == null) ||
(!track.inClipMode) ||
(track.clips.Length != 1) ||
(track.clips[0].start < 0) ||
(!track.clips[0].recordable))
return false;
var asset = track.clips[0].asset as AnimationPlayableAsset;
if (asset == null)
return false;
return TimelineHelpers.HaveSameContainerAsset(track, asset.clip);
}
}
}
|