Maze / Library /PackageCache /com.unity.timeline@1.6.5 /Editor /Utilities /AnimatedParameterUtility.cs
| 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) | |
| return false; | |
| 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; | |
| // only value types are supported | |
| if (!type.IsValueType && !typeof(IPlayableBehaviour).IsAssignableFrom(type)) | |
| return false; | |
| } | |
| 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.LastIndexOf('.'); | |
| 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; | |
| } | |
| } | |
| } | |