|
|
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; } }}
|