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