AnimatedParameterUtility.cs
13.9 KB
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Timeline
{
static class AnimatedParameterUtility
{
static readonly Type k_DefaultAnimationType = typeof(TimelineAsset);
static SerializedObject s_CachedObject;
public static ICurvesOwner ToCurvesOwner(IPlayableAsset playableAsset, TimelineAsset timeline)
{
if (playableAsset == null)
return null;
var curvesOwner = playableAsset as ICurvesOwner;
if (curvesOwner == null)
{
// If the asset is not directly an ICurvesOwner, it might be the asset for a TimelineClip
curvesOwner = TimelineRecording.FindClipWithAsset(timeline, playableAsset);
}
return curvesOwner;
}
public static bool TryGetSerializedPlayableAsset(UnityObject asset, out SerializedObject serializedObject)
{
serializedObject = null;
if (asset == null || Attribute.IsDefined(asset.GetType(), typeof(NotKeyableAttribute)) || !HasScriptPlayable(asset))
return false;
serializedObject = GetSerializedPlayableAsset(asset);
return serializedObject != null;
}
public static SerializedObject GetSerializedPlayableAsset(UnityObject asset)
{
if (!(asset is IPlayableAsset))
return null;
var scriptObject = asset as ScriptableObject;
if (scriptObject == null)
return null;
if (s_CachedObject == null || s_CachedObject.targetObject != asset)
{
s_CachedObject = new SerializedObject(scriptObject);
}
return s_CachedObject;
}
public static void UpdateSerializedPlayableAsset(UnityObject asset)
{
var so = GetSerializedPlayableAsset(asset);
if (so != null)
so.UpdateIfRequiredOrScript();
}
public static bool HasScriptPlayable(UnityObject asset)
{
if (asset == null)
return false;
var scriptPlayable = asset as IPlayableBehaviour;
return scriptPlayable != null || GetScriptPlayableFields(asset as IPlayableAsset).Any();
}
public static FieldInfo[] GetScriptPlayableFields(IPlayableAsset asset)
{
if (asset == null)
return new FieldInfo[0];
FieldInfo[] scriptPlayableFields;
if (!AnimatedParameterCache.TryGetScriptPlayableFields(asset.GetType(), out scriptPlayableFields))
{
scriptPlayableFields = GetScriptPlayableFields_Internal(asset);
AnimatedParameterCache.SetScriptPlayableFields(asset.GetType(), scriptPlayableFields);
}
return scriptPlayableFields;
}
static FieldInfo[] GetScriptPlayableFields_Internal(IPlayableAsset asset)
{
return asset.GetType()
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(
f => typeof(IPlayableBehaviour).IsAssignableFrom(f.FieldType) && // The field is an IPlayableBehaviour
(f.IsPublic || f.GetCustomAttributes(typeof(SerializeField), false).Any()) && // The field is either public or marked with [SerializeField]
!f.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() && // The field is not marked with [NotKeyable]
!f.GetCustomAttributes(typeof(HideInInspector), false).Any() && // The field is not marked with [HideInInspector]
!f.FieldType.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any()) // The field is not of a type marked with [NotKeyable]
.ToArray();
}
public static bool HasAnyAnimatableParameters(UnityObject asset)
{
return GetAllAnimatableParameters(asset).Any();
}
public static IEnumerable<SerializedProperty> GetAllAnimatableParameters(UnityObject asset)
{
SerializedObject serializedObject;
if (!TryGetSerializedPlayableAsset(asset, out serializedObject))
yield break;
var prop = serializedObject.GetIterator();
// We need to keep this variable because prop starts invalid
var outOfBounds = false;
while (!outOfBounds && prop.NextVisible(true))
{
foreach (var property in SelectAnimatableProperty(prop))
yield return property;
// We can become out of bounds by calling SelectAnimatableProperty, if the last iterated property is a color.
outOfBounds = !prop.isValid;
}
}
static IEnumerable<SerializedProperty> SelectAnimatableProperty(SerializedProperty prop)
{
// We're only interested by animatable leaf parameters
if (!prop.hasChildren && IsParameterAnimatable(prop))
yield return prop.Copy();
// Color type is not considered "visible" when iterating
if (prop.propertyType == SerializedPropertyType.Color)
{
var end = prop.GetEndProperty();
// For some reasons, if the last 2+ serialized properties are of type Color, prop becomes invalid and
// Next() throws an exception. This is not the case when only the last serialized property is a Color.
while (!SerializedProperty.EqualContents(prop, end) && prop.isValid && prop.Next(true))
{
foreach (var property in SelectAnimatableProperty(prop))
yield return property;
}
}
}
public static bool IsParameterAnimatable(UnityObject asset, string parameterName)
{
SerializedObject serializedObject;
if (!TryGetSerializedPlayableAsset(asset, out serializedObject))
return false;
var prop = serializedObject.FindProperty(parameterName);
return IsParameterAnimatable(prop);
}
public static bool IsParameterAnimatable(SerializedProperty property)
{
if (property == null)
return false;
bool isAnimatable;
if (!AnimatedParameterCache.TryGetIsPropertyAnimatable(property, out isAnimatable))
{
isAnimatable = IsParameterAnimatable_Internal(property);
AnimatedParameterCache.SetIsPropertyAnimatable(property, isAnimatable);
}
return isAnimatable;
}
static bool IsParameterAnimatable_Internal(SerializedProperty property)
{
if (property == null)
return false;
var asset = property.serializedObject.targetObject;
// Currently not supported
if (asset is AnimationTrack)
return false;
if (IsParameterKeyable(property))
return asset is IPlayableBehaviour || IsParameterAtPathAnimatable(asset, property.propertyPath);
return false;
}
static bool IsParameterKeyable(SerializedProperty property)
{
return IsTypeAnimatable(property.propertyType) && IsKeyableInHierarchy(property);
}
static bool IsKeyableInHierarchy(SerializedProperty property)
{
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var pathSegments = property.propertyPath.Split('.');
var type = property.serializedObject.targetObject.GetType();
foreach (var segment in pathSegments)
{
if (type.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any())
{
return false;
}
if (type.IsArray)
{
if (segment != "Array")
type = type.GetElementType();
continue;
}
var fieldInfo = type.GetField(segment, bindingFlags);
if (fieldInfo == null ||
fieldInfo.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() ||
fieldInfo.GetCustomAttributes(typeof(HideInInspector), false).Any())
{
return false;
}
type = fieldInfo.FieldType;
}
return true;
}
static bool IsParameterAtPathAnimatable(UnityObject asset, string path)
{
if (asset == null)
return false;
return GetScriptPlayableFields(asset as IPlayableAsset)
.Any(
f => path.StartsWith(f.Name, StringComparison.Ordinal) &&
path.Length > f.Name.Length &&
path[f.Name.Length] == '.');
}
public static bool IsTypeAnimatable(SerializedPropertyType type)
{
// Note: Integer is not currently supported by the animated property system
switch (type)
{
case SerializedPropertyType.Boolean:
case SerializedPropertyType.Float:
case SerializedPropertyType.Vector2:
case SerializedPropertyType.Vector3:
case SerializedPropertyType.Color:
case SerializedPropertyType.Quaternion:
case SerializedPropertyType.Vector4:
return true;
default:
return false;
}
}
public static bool IsParameterAnimated(UnityObject asset, AnimationClip animationData, string parameterName)
{
if (asset == null || animationData == null)
return false;
var binding = GetCurveBinding(asset, parameterName);
var bindings = AnimationClipCurveCache.Instance.GetCurveInfo(animationData).bindings;
return bindings.Any(x => BindingMatchesParameterName(x, binding.propertyName));
}
// Retrieve an animated parameter curve. parameter name is required to include the appropriate field for vectors
// e.g.: position
public static AnimationCurve GetAnimatedParameter(UnityObject asset, AnimationClip animationData, string parameterName)
{
if (!(asset is ScriptableObject) || animationData == null)
return null;
var binding = GetCurveBinding(asset, parameterName);
return AnimationUtility.GetEditorCurve(animationData, binding);
}
// get an animatable curve binding for this parameter
public static EditorCurveBinding GetCurveBinding(UnityObject asset, string parameterName)
{
var animationName = GetAnimatedParameterBindingName(asset, parameterName);
return EditorCurveBinding.FloatCurve(string.Empty, GetValidAnimationType(asset), animationName);
}
public static string GetAnimatedParameterBindingName(UnityObject asset, string parameterName)
{
if (asset == null)
return parameterName;
string bindingName;
if (!AnimatedParameterCache.TryGetBindingName(asset.GetType(), parameterName, out bindingName))
{
bindingName = GetAnimatedParameterBindingName_Internal(asset, parameterName);
AnimatedParameterCache.SetBindingName(asset.GetType(), parameterName, bindingName);
}
return bindingName;
}
static string GetAnimatedParameterBindingName_Internal(UnityObject asset, string parameterName)
{
if (asset is IPlayableBehaviour)
return parameterName;
// strip the IScript playable field name
var fields = GetScriptPlayableFields(asset as IPlayableAsset);
foreach (var f in fields)
{
if (parameterName.StartsWith(f.Name, StringComparison.Ordinal))
{
if (parameterName.Length > f.Name.Length && parameterName[f.Name.Length] == '.')
return parameterName.Substring(f.Name.Length + 1);
}
}
return parameterName;
}
public static bool BindingMatchesParameterName(EditorCurveBinding binding, string parameterName)
{
if (binding.propertyName == parameterName)
return true;
var indexOfDot = binding.propertyName.IndexOf('.');
return indexOfDot > 0 && parameterName.Length == indexOfDot &&
binding.propertyName.StartsWith(parameterName, StringComparison.Ordinal);
}
// the animated type must be a non-abstract instantiable object.
public static Type GetValidAnimationType(UnityObject asset)
{
return asset != null ? asset.GetType() : k_DefaultAnimationType;
}
public static FieldInfo GetFieldInfoForProperty(SerializedProperty property)
{
FieldInfo fieldInfo;
if (!AnimatedParameterCache.TryGetFieldInfoForProperty(property, out fieldInfo))
{
Type _;
fieldInfo = ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
AnimatedParameterCache.SetFieldInfoForProperty(property, fieldInfo);
}
return fieldInfo;
}
public static T GetAttributeForProperty<T>(SerializedProperty property) where T : Attribute
{
var fieldInfo = GetFieldInfoForProperty(property);
return fieldInfo.GetCustomAttributes(typeof(T), false).FirstOrDefault() as T;
}
}
}