Unity中获取AnimationClip的关键帧信息

Unity中获取AnimationClip的关键帧信息

引子

之前在做项目时,经常会遇到:需要模型保持动画中某一帧的状态的需求;或者直接将一系列模型的状态数据做成动画,给到我们。这时我总是会先想着怎么样能从动画中将这些数据(关键帧)提取出来呢?但之前因为思路的方向错误,导致一直都没有找到合适的解决方案,最终这些需求大部分都是,将动画手动分割成对应的小段循环播放,或手动抄写数据。
这几天终于无意间在网上找到了,简单方便的方案来解决这个问题。

解决方案

之前我的错误思路总是在找AnimatorAnimationAnimationClipRuntimeAnimatorController等类中的相应变量、属性或方法,来获取动画中的关键帧数据。这是错的,至少当前的这些类中没有这样的接口。
根据我查到的文章,并查询了官方文档发现通过一个编辑器类可以获取到帧信息AnimationUtility
也就是说它只能在编辑器模式下使用,是无法在程序打包之后使用获取的,也因此我上面提到的思路是错误的。
由此便想到了在编辑器模式下把帧信息序列化(可以保存成数据文件,也可以保存在场景的某物体脚本组件上),然后在运行时直接读取便能解决问题。
核心接口使用:

foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
    AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
    EditorGUILayout.LabelField(binding.path + "/" + binding.propertyName + ", Keys: " + curve.keys.Length);
    for (int i = 0; i < curve.length; i++)
    {
        EditorGUILayout.LabelField("Keys" + i + ":" + curve[i].value);
    }
}

代码中clip为传入的AnimationClip对象,通过AnimationUtility.GetCurveBindings接口获取与动画剪辑相关的所有动画事件,然后再通过AnimationUtility.GetEditorCurve接口得到绑定所指向的float曲线,之后便可以得到我们想要得到的数据了,这里列举几个主要的:binding.path是模型中所操作的对象节点路径;binding.propertyName是所操作的对象节点属性(例如PositionRotationScaleBlendShape等);curve为关键帧曲线;curve.lengthcurve.keys.Length都是关键帧个数;最后循环中的curve[i].value是关键帧中数值,curve[i].time是关键帧中时间。

示例

以下是我项目中的示例,由于只能是编辑器模式下使用,所以是一个Basic脚本的Inspector窗口的Editor编辑脚本。

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Basic), true)]
public class BasicEditor : Editor
{
    protected Basic m_script;
    protected SerializedProperty scriptProp;
    protected float width_view;
    protected GUILayoutOption width_whole;
    protected GUILayoutOption width_half;

    private void OnEnable()
    {
        m_script = (Basic)target;
        scriptProp = serializedObject.FindProperty("m_Script");
    }
    public override void OnInspectorGUI()
    {
        width_whole = GUILayout.Width(width_view);
        width_half = GUILayout.Width(width_view / 2);

        //base.OnInspectorGUI();
        serializedObject.Update();
        GUI.enabled = false;
        EditorGUILayout.PropertyField(scriptProp);
        GUI.enabled = true;
        EditorGUILayout.Space();

        m_script.m_clip = (AnimationClip)EditorGUILayout.ObjectField("基础动画", m_script.m_clip, typeof(AnimationClip), true);
        if (m_script.m_clip != null)
        {
            BasicExpressionsInfo info = m_script.m_info;
            if (GUILayout.Button("读取动画数据"))
            {
                foreach (var binding in AnimationUtility.GetCurveBindings(m_script.m_clip))
                {
                    //Debug.Log(binding.path);
                    AnimationCurve curve = AnimationUtility.GetEditorCurve(m_script.m_clip, binding);
                    string[] temp = binding.propertyName.Split('.');
                    string name = temp[temp.Length - 1];
                    name = name.Split('_')[0];
                    if (!m_script.m_baseExpressionName.Contains(name))
                    {
                        m_script.m_baseExpressionName.Add(name);
                        m_script.m_baseExpressionValue.Add(0);
                        FloatArr floatArr = new FloatArr()
                        {
                            m_value = new float[curve.keys.Length]
                        };
                        info.m_baseExpressionFloat.Add(floatArr);
                        StringArr stringArr = new StringArr
                        {
                            m_value = new string[curve.keys.Length]
                        };
                        info.m_baseExpressionString.Add(stringArr);
                        info.m_baseExpressionIndex.Add(0);
                    }
                    int index = m_script.m_baseExpressionName.IndexOf(name);
                    for (int i = 0; i < curve.length; i++)
                    {
                        info.m_baseExpressionFloat[index].m_value[i] = curve[i].value;
                        info.m_baseExpressionString[index].m_value[i] = curve[i].time.ToString() + " --- " + curve[i].value.ToString();
                    }
                }
            }
            bool isWidth = false;
            info.m_foldoutAnimation = EditorGUILayout.BeginFoldoutHeaderGroup(info.m_foldoutAnimation, "BlendShape(动画:帧数 --- 数值)");
            if (!isWidth && info.m_foldoutAnimation)
            {
                isWidth = true;
            }
            if (info.m_foldoutAnimation)
            {
                string[] names = m_script.m_baseExpressionName.ToArray();
                for (int i = 0; i < names.Length; i++)
                {
                    info.m_baseExpressionIndex[i] = EditorGUILayout.Popup(names[i], info.m_baseExpressionIndex[i], info.m_baseExpressionString[i].m_value);
                }
            }
            EditorGUILayout.EndFoldoutHeaderGroup();
            EditorGUILayout.Space();
            if (GUILayout.Button("重置所有数据为动画数据"))
            {
                string[] names = m_script.m_baseExpressionName.ToArray();
                for (int i = 0; i < names.Length; i++)
                {
                    m_script.m_baseExpressionValue[i] = info.m_baseExpressionFloat[i].m_value[info.m_baseExpressionIndex[i]];
                }
            }
            info.m_foldoutPredefine = EditorGUILayout.BeginFoldoutHeaderGroup(info.m_foldoutPredefine, "BlendShape(预设值)");
            if (!isWidth && info.m_foldoutPredefine)
            {
                isWidth = true;
            }
            if (info.m_foldoutPredefine)
            {
                string[] names = m_script.m_baseExpressionName.ToArray();
                for (int i = 0; i < names.Length; i++)
                {
                    GUILayout.BeginHorizontal();
                    EditorGUILayout.LabelField(names[i], width_half);
                    if (GUILayout.Button("重置为动画数据", width_half))
                    {
                        m_script.m_baseExpressionValue[i] = info.m_baseExpressionFloat[i].m_value[info.m_baseExpressionIndex[i]];
                    }
                    GUILayout.EndHorizontal();
                    m_script.m_baseExpressionValue[i] = EditorGUILayout.Slider(m_script.m_baseExpressionValue[i], 0, 100);
                }
            }
            if (isWidth)
            {
                width_view = EditorGUIUtility.currentViewWidth - 39;
            }
            else
            {
                width_view = EditorGUIUtility.currentViewWidth - 30;
            }
        }
        serializedObject.ApplyModifiedProperties();
        if (GUI.changed)
        {//当Inspector 面板发生变化时保存数据
            EditorUtility.SetDirty(target);
        }
    }
    private void OnDestroy()
    {

    }
}

在将Basic脚本放到场景中,拖入动画,点击读取动画数据按钮,便会将动画中的BlendShape数据读取并保存到了脚本中,再点击重置所有数据为动画数据按钮,即,将这些脚本动画数据,覆盖真正程序运行的所要使用的变量属性,保存场景之后这些数据也就会保存在场景中,以便程序运行时使用。
下面是示例脚本的其他相关代码及Inspector窗口效果:

using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 基础表情
/// </summary>
public class Basic : MonoBehaviour
{
#if UNITY_EDITOR
    public BasicExpressionsInfo m_info = new BasicExpressionsInfo();
#endif
    public AnimationClip m_clip;
    public List<string> m_baseExpressionName = new List<string>();
    public List<float> m_baseExpressionValue = new List<float>();
}

#if UNITY_EDITOR
/// <summary>
/// 编辑器信息
/// </summary>
[Serializable]//便于编辑器储存,可视化操作
public class BasicExpressionsInfo
{
    public bool m_foldoutAnimation;
    public bool m_foldoutPredefine;
    public List<FloatArr> m_baseExpressionFloat = new List<FloatArr>();
    public List<StringArr> m_baseExpressionString = new List<StringArr>();
    public List<int> m_baseExpressionIndex = new List<int>();
}
[Serializable]//便于编辑器储存,可视化操作
public class FloatArr { public float[] m_value; }
[Serializable]//便于编辑器储存,可视化操作
public class StringArr { public string[] m_value; }
#endif

在这里插入图片描述在这里插入图片描述

参考链接

地址:https://blog.csdn.net/qq_36292069/article/details/88601302

  • 12
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天富儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值