359 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			359 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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; | ||
|  |         } | ||
|  |     } | ||
|  | } |