AnimatedParameterUtility.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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. var fieldInfo = type.GetField(segment, bindingFlags);
  171. if (fieldInfo == null ||
  172. fieldInfo.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() ||
  173. fieldInfo.GetCustomAttributes(typeof(HideInInspector), false).Any())
  174. {
  175. return false;
  176. }
  177. type = fieldInfo.FieldType;
  178. }
  179. return true;
  180. }
  181. static bool IsParameterAtPathAnimatable(UnityObject asset, string path)
  182. {
  183. if (asset == null)
  184. return false;
  185. return GetScriptPlayableFields(asset as IPlayableAsset)
  186. .Any(
  187. f => path.StartsWith(f.Name, StringComparison.Ordinal) &&
  188. path.Length > f.Name.Length &&
  189. path[f.Name.Length] == '.');
  190. }
  191. public static bool IsTypeAnimatable(SerializedPropertyType type)
  192. {
  193. // Note: Integer is not currently supported by the animated property system
  194. switch (type)
  195. {
  196. case SerializedPropertyType.Boolean:
  197. case SerializedPropertyType.Float:
  198. case SerializedPropertyType.Vector2:
  199. case SerializedPropertyType.Vector3:
  200. case SerializedPropertyType.Color:
  201. case SerializedPropertyType.Quaternion:
  202. case SerializedPropertyType.Vector4:
  203. return true;
  204. default:
  205. return false;
  206. }
  207. }
  208. public static bool IsParameterAnimated(UnityObject asset, AnimationClip animationData, string parameterName)
  209. {
  210. if (asset == null || animationData == null)
  211. return false;
  212. var binding = GetCurveBinding(asset, parameterName);
  213. var bindings = AnimationClipCurveCache.Instance.GetCurveInfo(animationData).bindings;
  214. return bindings.Any(x => BindingMatchesParameterName(x, binding.propertyName));
  215. }
  216. // Retrieve an animated parameter curve. parameter name is required to include the appropriate field for vectors
  217. // e.g.: position
  218. public static AnimationCurve GetAnimatedParameter(UnityObject asset, AnimationClip animationData, string parameterName)
  219. {
  220. if (!(asset is ScriptableObject) || animationData == null)
  221. return null;
  222. var binding = GetCurveBinding(asset, parameterName);
  223. return AnimationUtility.GetEditorCurve(animationData, binding);
  224. }
  225. // get an animatable curve binding for this parameter
  226. public static EditorCurveBinding GetCurveBinding(UnityObject asset, string parameterName)
  227. {
  228. var animationName = GetAnimatedParameterBindingName(asset, parameterName);
  229. return EditorCurveBinding.FloatCurve(string.Empty, GetValidAnimationType(asset), animationName);
  230. }
  231. public static string GetAnimatedParameterBindingName(UnityObject asset, string parameterName)
  232. {
  233. if (asset == null)
  234. return parameterName;
  235. string bindingName;
  236. if (!AnimatedParameterCache.TryGetBindingName(asset.GetType(), parameterName, out bindingName))
  237. {
  238. bindingName = GetAnimatedParameterBindingName_Internal(asset, parameterName);
  239. AnimatedParameterCache.SetBindingName(asset.GetType(), parameterName, bindingName);
  240. }
  241. return bindingName;
  242. }
  243. static string GetAnimatedParameterBindingName_Internal(UnityObject asset, string parameterName)
  244. {
  245. if (asset is IPlayableBehaviour)
  246. return parameterName;
  247. // strip the IScript playable field name
  248. var fields = GetScriptPlayableFields(asset as IPlayableAsset);
  249. foreach (var f in fields)
  250. {
  251. if (parameterName.StartsWith(f.Name, StringComparison.Ordinal))
  252. {
  253. if (parameterName.Length > f.Name.Length && parameterName[f.Name.Length] == '.')
  254. return parameterName.Substring(f.Name.Length + 1);
  255. }
  256. }
  257. return parameterName;
  258. }
  259. public static bool BindingMatchesParameterName(EditorCurveBinding binding, string parameterName)
  260. {
  261. if (binding.propertyName == parameterName)
  262. return true;
  263. var indexOfDot = binding.propertyName.IndexOf('.');
  264. return indexOfDot > 0 && parameterName.Length == indexOfDot &&
  265. binding.propertyName.StartsWith(parameterName, StringComparison.Ordinal);
  266. }
  267. // the animated type must be a non-abstract instantiable object.
  268. public static Type GetValidAnimationType(UnityObject asset)
  269. {
  270. return asset != null ? asset.GetType() : k_DefaultAnimationType;
  271. }
  272. public static FieldInfo GetFieldInfoForProperty(SerializedProperty property)
  273. {
  274. FieldInfo fieldInfo;
  275. if (!AnimatedParameterCache.TryGetFieldInfoForProperty(property, out fieldInfo))
  276. {
  277. Type _;
  278. fieldInfo = ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
  279. AnimatedParameterCache.SetFieldInfoForProperty(property, fieldInfo);
  280. }
  281. return fieldInfo;
  282. }
  283. public static T GetAttributeForProperty<T>(SerializedProperty property) where T : Attribute
  284. {
  285. var fieldInfo = GetFieldInfoForProperty(property);
  286. return fieldInfo.GetCustomAttributes(typeof(T), false).FirstOrDefault() as T;
  287. }
  288. }
  289. }