AnimatedParameterUtility.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEngine;
  6. using UnityEngine.Playables;
  7. using UnityEngine.Timeline;
  8. using UnityObject = UnityEngine.Object;
  9. namespace UnityEditor.Timeline
  10. {
  11. static class AnimatedParameterUtility
  12. {
  13. static readonly Type k_DefaultAnimationType = typeof(TimelineAsset);
  14. static SerializedObject s_CachedObject;
  15. public static ICurvesOwner ToCurvesOwner(IPlayableAsset playableAsset, TimelineAsset timeline)
  16. {
  17. if (playableAsset == null)
  18. return null;
  19. var curvesOwner = playableAsset as ICurvesOwner;
  20. if (curvesOwner == null)
  21. {
  22. // If the asset is not directly an ICurvesOwner, it might be the asset for a TimelineClip
  23. curvesOwner = TimelineRecording.FindClipWithAsset(timeline, playableAsset);
  24. }
  25. return curvesOwner;
  26. }
  27. public static bool TryGetSerializedPlayableAsset(UnityObject asset, out SerializedObject serializedObject)
  28. {
  29. serializedObject = null;
  30. if (asset == null || Attribute.IsDefined(asset.GetType(), typeof(NotKeyableAttribute)) || !HasScriptPlayable(asset))
  31. return false;
  32. serializedObject = GetSerializedPlayableAsset(asset);
  33. return serializedObject != null;
  34. }
  35. public static SerializedObject GetSerializedPlayableAsset(UnityObject asset)
  36. {
  37. if (!(asset is IPlayableAsset))
  38. return null;
  39. var scriptObject = asset as ScriptableObject;
  40. if (scriptObject == null)
  41. return null;
  42. if (s_CachedObject == null || s_CachedObject.targetObject != asset)
  43. {
  44. s_CachedObject = new SerializedObject(scriptObject);
  45. }
  46. return s_CachedObject;
  47. }
  48. public static void UpdateSerializedPlayableAsset(UnityObject asset)
  49. {
  50. var so = GetSerializedPlayableAsset(asset);
  51. if (so != null)
  52. so.UpdateIfRequiredOrScript();
  53. }
  54. public static bool HasScriptPlayable(UnityObject asset)
  55. {
  56. if (asset == null)
  57. return false;
  58. var scriptPlayable = asset as IPlayableBehaviour;
  59. return scriptPlayable != null || GetScriptPlayableFields(asset as IPlayableAsset).Any();
  60. }
  61. public static FieldInfo[] GetScriptPlayableFields(IPlayableAsset asset)
  62. {
  63. if (asset == null)
  64. return new FieldInfo[0];
  65. FieldInfo[] scriptPlayableFields;
  66. if (!AnimatedParameterCache.TryGetScriptPlayableFields(asset.GetType(), out scriptPlayableFields))
  67. {
  68. scriptPlayableFields = GetScriptPlayableFields_Internal(asset);
  69. AnimatedParameterCache.SetScriptPlayableFields(asset.GetType(), scriptPlayableFields);
  70. }
  71. return scriptPlayableFields;
  72. }
  73. static FieldInfo[] GetScriptPlayableFields_Internal(IPlayableAsset asset)
  74. {
  75. return asset.GetType()
  76. .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
  77. .Where(
  78. f => typeof(IPlayableBehaviour).IsAssignableFrom(f.FieldType) && // The field is an IPlayableBehaviour
  79. (f.IsPublic || f.GetCustomAttributes(typeof(SerializeField), false).Any()) && // The field is either public or marked with [SerializeField]
  80. !f.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() && // The field is not marked with [NotKeyable]
  81. !f.GetCustomAttributes(typeof(HideInInspector), false).Any() && // The field is not marked with [HideInInspector]
  82. !f.FieldType.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any()) // The field is not of a type marked with [NotKeyable]
  83. .ToArray();
  84. }
  85. public static bool HasAnyAnimatableParameters(UnityObject asset)
  86. {
  87. return GetAllAnimatableParameters(asset).Any();
  88. }
  89. public static IEnumerable<SerializedProperty> GetAllAnimatableParameters(UnityObject asset)
  90. {
  91. SerializedObject serializedObject;
  92. if (!TryGetSerializedPlayableAsset(asset, out serializedObject))
  93. yield break;
  94. var prop = serializedObject.GetIterator();
  95. // We need to keep this variable because prop starts invalid
  96. var outOfBounds = false;
  97. while (!outOfBounds && prop.NextVisible(true))
  98. {
  99. foreach (var property in SelectAnimatableProperty(prop))
  100. yield return property;
  101. // We can become out of bounds by calling SelectAnimatableProperty, if the last iterated property is a color.
  102. outOfBounds = !prop.isValid;
  103. }
  104. }
  105. static IEnumerable<SerializedProperty> SelectAnimatableProperty(SerializedProperty prop)
  106. {
  107. // We're only interested by animatable leaf parameters
  108. if (!prop.hasChildren && IsParameterAnimatable(prop))
  109. yield return prop.Copy();
  110. // Color type is not considered "visible" when iterating
  111. if (prop.propertyType == SerializedPropertyType.Color)
  112. {
  113. var end = prop.GetEndProperty();
  114. // For some reasons, if the last 2+ serialized properties are of type Color, prop becomes invalid and
  115. // Next() throws an exception. This is not the case when only the last serialized property is a Color.
  116. while (!SerializedProperty.EqualContents(prop, end) && prop.isValid && prop.Next(true))
  117. {
  118. foreach (var property in SelectAnimatableProperty(prop))
  119. yield return property;
  120. }
  121. }
  122. }
  123. public static bool IsParameterAnimatable(UnityObject asset, string parameterName)
  124. {
  125. SerializedObject serializedObject;
  126. if (!TryGetSerializedPlayableAsset(asset, out serializedObject))
  127. return false;
  128. var prop = serializedObject.FindProperty(parameterName);
  129. return IsParameterAnimatable(prop);
  130. }
  131. public static bool IsParameterAnimatable(SerializedProperty property)
  132. {
  133. if (property == null)
  134. return false;
  135. bool isAnimatable;
  136. if (!AnimatedParameterCache.TryGetIsPropertyAnimatable(property, out isAnimatable))
  137. {
  138. isAnimatable = IsParameterAnimatable_Internal(property);
  139. AnimatedParameterCache.SetIsPropertyAnimatable(property, isAnimatable);
  140. }
  141. return isAnimatable;
  142. }
  143. static bool IsParameterAnimatable_Internal(SerializedProperty property)
  144. {
  145. if (property == null)
  146. return false;
  147. var asset = property.serializedObject.targetObject;
  148. // Currently not supported
  149. if (asset is AnimationTrack)
  150. return false;
  151. if (IsParameterKeyable(property))
  152. return asset is IPlayableBehaviour || IsParameterAtPathAnimatable(asset, property.propertyPath);
  153. return false;
  154. }
  155. static bool IsParameterKeyable(SerializedProperty property)
  156. {
  157. return IsTypeAnimatable(property.propertyType) && IsKeyableInHierarchy(property);
  158. }
  159. static bool IsKeyableInHierarchy(SerializedProperty property)
  160. {
  161. const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
  162. var pathSegments = property.propertyPath.Split('.');
  163. var type = property.serializedObject.targetObject.GetType();
  164. foreach (var segment in pathSegments)
  165. {
  166. if (type.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any())
  167. {
  168. return false;
  169. }
  170. if (type.IsArray)
  171. {
  172. if (segment != "Array")
  173. type = type.GetElementType();
  174. continue;
  175. }
  176. var fieldInfo = type.GetField(segment, bindingFlags);
  177. if (fieldInfo == null ||
  178. fieldInfo.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() ||
  179. fieldInfo.GetCustomAttributes(typeof(HideInInspector), false).Any())
  180. {
  181. return false;
  182. }
  183. type = fieldInfo.FieldType;
  184. }
  185. return true;
  186. }
  187. static bool IsParameterAtPathAnimatable(UnityObject asset, string path)
  188. {
  189. if (asset == null)
  190. return false;
  191. return GetScriptPlayableFields(asset as IPlayableAsset)
  192. .Any(
  193. f => path.StartsWith(f.Name, StringComparison.Ordinal) &&
  194. path.Length > f.Name.Length &&
  195. path[f.Name.Length] == '.');
  196. }
  197. public static bool IsTypeAnimatable(SerializedPropertyType type)
  198. {
  199. // Note: Integer is not currently supported by the animated property system
  200. switch (type)
  201. {
  202. case SerializedPropertyType.Boolean:
  203. case SerializedPropertyType.Float:
  204. case SerializedPropertyType.Vector2:
  205. case SerializedPropertyType.Vector3:
  206. case SerializedPropertyType.Color:
  207. case SerializedPropertyType.Quaternion:
  208. case SerializedPropertyType.Vector4:
  209. return true;
  210. default:
  211. return false;
  212. }
  213. }
  214. public static bool IsParameterAnimated(UnityObject asset, AnimationClip animationData, string parameterName)
  215. {
  216. if (asset == null || animationData == null)
  217. return false;
  218. var binding = GetCurveBinding(asset, parameterName);
  219. var bindings = AnimationClipCurveCache.Instance.GetCurveInfo(animationData).bindings;
  220. return bindings.Any(x => BindingMatchesParameterName(x, binding.propertyName));
  221. }
  222. // Retrieve an animated parameter curve. parameter name is required to include the appropriate field for vectors
  223. // e.g.: position
  224. public static AnimationCurve GetAnimatedParameter(UnityObject asset, AnimationClip animationData, string parameterName)
  225. {
  226. if (!(asset is ScriptableObject) || animationData == null)
  227. return null;
  228. var binding = GetCurveBinding(asset, parameterName);
  229. return AnimationUtility.GetEditorCurve(animationData, binding);
  230. }
  231. // get an animatable curve binding for this parameter
  232. public static EditorCurveBinding GetCurveBinding(UnityObject asset, string parameterName)
  233. {
  234. var animationName = GetAnimatedParameterBindingName(asset, parameterName);
  235. return EditorCurveBinding.FloatCurve(string.Empty, GetValidAnimationType(asset), animationName);
  236. }
  237. public static string GetAnimatedParameterBindingName(UnityObject asset, string parameterName)
  238. {
  239. if (asset == null)
  240. return parameterName;
  241. string bindingName;
  242. if (!AnimatedParameterCache.TryGetBindingName(asset.GetType(), parameterName, out bindingName))
  243. {
  244. bindingName = GetAnimatedParameterBindingName_Internal(asset, parameterName);
  245. AnimatedParameterCache.SetBindingName(asset.GetType(), parameterName, bindingName);
  246. }
  247. return bindingName;
  248. }
  249. static string GetAnimatedParameterBindingName_Internal(UnityObject asset, string parameterName)
  250. {
  251. if (asset is IPlayableBehaviour)
  252. return parameterName;
  253. // strip the IScript playable field name
  254. var fields = GetScriptPlayableFields(asset as IPlayableAsset);
  255. foreach (var f in fields)
  256. {
  257. if (parameterName.StartsWith(f.Name, StringComparison.Ordinal))
  258. {
  259. if (parameterName.Length > f.Name.Length && parameterName[f.Name.Length] == '.')
  260. return parameterName.Substring(f.Name.Length + 1);
  261. }
  262. }
  263. return parameterName;
  264. }
  265. public static bool BindingMatchesParameterName(EditorCurveBinding binding, string parameterName)
  266. {
  267. if (binding.propertyName == parameterName)
  268. return true;
  269. var indexOfDot = binding.propertyName.IndexOf('.');
  270. return indexOfDot > 0 && parameterName.Length == indexOfDot &&
  271. binding.propertyName.StartsWith(parameterName, StringComparison.Ordinal);
  272. }
  273. // the animated type must be a non-abstract instantiable object.
  274. public static Type GetValidAnimationType(UnityObject asset)
  275. {
  276. return asset != null ? asset.GetType() : k_DefaultAnimationType;
  277. }
  278. public static FieldInfo GetFieldInfoForProperty(SerializedProperty property)
  279. {
  280. FieldInfo fieldInfo;
  281. if (!AnimatedParameterCache.TryGetFieldInfoForProperty(property, out fieldInfo))
  282. {
  283. Type _;
  284. fieldInfo = ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
  285. AnimatedParameterCache.SetFieldInfoForProperty(property, fieldInfo);
  286. }
  287. return fieldInfo;
  288. }
  289. public static T GetAttributeForProperty<T>(SerializedProperty property) where T : Attribute
  290. {
  291. var fieldInfo = GetFieldInfoForProperty(property);
  292. return fieldInfo.GetCustomAttributes(typeof(T), false).FirstOrDefault() as T;
  293. }
  294. }
  295. }