UnityGame/Library/PackageCache/com.unity.timeline/Editor/Utilities/AnimatedParameterUtility.cs
2024-10-27 10:53:47 +03:00

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