首页 > 其他分享 >AnimationClip同步工具

AnimationClip同步工具

时间:2024-05-20 23:07:48浏览次数:21  
标签:同步 AnimationClip private item objValue tf var 工具 Animator

用途:列出动画的第1帧与预制体GameObject当前值不同的,需要同步的可以手动同步

效果图

 

public struct ValueNotSameItem
{
    public EditorCurveBinding curveBinding; //关联参数
    public AnimationCurve animCurve; //动画曲线
    public float kfValue; //动画曲线上第1帧的值
    public float objValue; //预制体中GameObject上的值
}

 

public class AnimClipSyncToolWnd : EditorWindow
{
    [MenuItem("MyTools/AnimClipSyncToolWnd", false, 1)]
    static void ShowWindow()
    {
        var win = GetWindow(typeof(AnimClipSyncToolWnd), false, "AnimClipSyncToolWnd");
        win.minSize = new Vector2(500, 300);
    }

    private Vector2 m_ScrollPos;

    private GameObject m_PrefabAsset; //预制体资源
    private Animator m_Animator; //预制体根节点上的Animator组件

    private AnimationClip[] m_AnimClips; //Animator状态机下的所有动画文件

    private string[] m_AnimClipOptions;
    private int m_AnimClipOptionIndex;

    private List<string> m_NodePathList = new List<string>(); //值不同的节点路径
    private Dictionary<string, List<ValueNotSameItem>> m_ValueNotSameItemsDict = new Dictionary<string, List<ValueNotSameItem>>();

    private bool m_PrefabRefreshFlag = false;

    private void OnEnable()
    {
        m_PrefabRefreshFlag = true;
        Undo.undoRedoPerformed += On_UndoRedoPerformed;
    }

    private void OnDisable()
    {
        Undo.undoRedoPerformed -= On_UndoRedoPerformed;
    }

    private void On_UndoRedoPerformed()
    {
        var grpName = Undo.GetCurrentGroupName();
        Debug.Log($"undoRedo: {grpName}");
        m_PrefabRefreshFlag = true;
    }

    private void OnGUI()
    {
        m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);
        {
            OnGUI_ScrollView();
        }
        EditorGUILayout.EndScrollView();
    }

    private void OnGUI_ScrollView()
    {
        var prefabAsset = (GameObject)EditorGUILayout.ObjectField("Prefab", m_PrefabAsset, typeof(GameObject), false);
        bool isPrefabChange = m_PrefabAsset != prefabAsset;
        if (null != m_PrefabAsset)
        {
            if (GUILayout.Button($"Refresh"))
                isPrefabChange = true;
        }

        if (m_PrefabRefreshFlag || isPrefabChange)
        {
            m_PrefabRefreshFlag = false;
            m_PrefabAsset = prefabAsset;
            m_Animator = null;

            m_AnimClips = null;
            m_AnimClipOptions = null;
            m_AnimClipOptionIndex = 0;

            m_NodePathList.Clear();
            m_ValueNotSameItemsDict.Clear();

            if (null != m_PrefabAsset)
            {
                m_Animator = m_PrefabAsset.GetComponent<Animator>();
                if (null != m_Animator)
                    RefreshAnimClips(m_Animator);
            }
        }

        if (null == m_PrefabAsset) return;
        if (null == m_Animator)
        {
            EditorGUILayout.LabelField("No Animator !!!");
            return;
        }

        foreach (var path in m_NodePathList)
        {
            var list = m_ValueNotSameItemsDict[path];
            EditorGUILayout.LabelField(path);
            
            EditorGUI.indentLevel = 1;
            for (int j = 0; j < list.Count; ++j)
            {
                var item = list[j];
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField($"{item.curveBinding.propertyName}: obj: {item.objValue}, kf: {item.kfValue}");

                if (GUILayout.Button("set as obj", GUILayout.Width(70)))
                {
                    float delta = item.objValue - item.kfValue;
                    item.kfValue = item.objValue;
                    list[j] = item;

                    //所有帧+delta值
                    var keyFrames = item.animCurve.keys;
                    for (int i = 0; i < keyFrames.Length; ++i)
                    {
                        var kf = keyFrames[i];
                        kf.value = TrimFloat(kf.value + delta);
                        item.animCurve.MoveKey(i, kf);
                    }
                    //保存
                    var clip = m_AnimClips[m_AnimClipOptionIndex];
                    Undo.RecordObject(clip, "all keyFrames+delta");
                    AnimationUtility.SetEditorCurve(clip, item.curveBinding, item.animCurve);
                    EditorUtility.SetDirty(clip);
                    AssetDatabase.SaveAssets();
                    Repaint();
                }

                if (GUILayout.Button("set as kf", GUILayout.Width(70)))
                {
                    item.objValue = item.kfValue;
                    list[j] = item;
                    OnClick_SetAsKf(item);
                }
                EditorGUILayout.EndHorizontal();
            }
            EditorGUI.indentLevel = 0;
            EditorGUILayout.Space(5); //条目间隔
        }
    }

    //刷新Animator状态机的所有AnimClip
    private void RefreshAnimClips(Animator animat)
    {
        m_AnimClips = animat.runtimeAnimatorController.animationClips;
        if (null != m_AnimClips && m_AnimClips.Length > 0)
        {
            m_AnimClipOptions = new string[m_AnimClips.Length];
            for (int i = 0; i < m_AnimClips.Length; ++i)
            {
                var clip = m_AnimClips[i];
                m_AnimClipOptions[i] = clip.name;
            }

            var curClip = m_AnimClips[m_AnimClipOptionIndex];
            RefreshAnimCurves(curClip);
        }
    }

    //刷新AnimClip的所有动画曲线
    private void RefreshAnimCurves(AnimationClip clip)
    {
        EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip); //一个property关联一个动画曲线
        var prefabTf = m_PrefabAsset.transform;
        foreach (var b in bindings)
        {
            var curve = AnimationUtility.GetEditorCurve(clip, b);
            var kframes = curve.keys;
            if (kframes.Length <= 0) continue;

            var tf = prefabTf.Find(b.path);
            if (null == tf) continue;

            var kf0 = kframes[0];
            CheckFirstKeyFrame(b, curve, tf, kf0);
        }
    }

    private void OnClick_SetAsKf(ValueNotSameItem item)
    {
        var tf = m_PrefabAsset.transform.Find(item.curveBinding.path);
        Undo.RecordObject(tf, "Set As kf");
        var rtf = tf as RectTransform;
        switch (item.curveBinding.propertyName)
        {
        case "m_LocalPosition.x":
        {
            var pos = tf.localPosition;
            pos.x = item.kfValue;
            tf.localPosition = pos;
            break;
        }
        case "m_LocalPosition.y":
        {
            var pos = tf.localPosition;
            pos.y = item.kfValue;
            tf.localPosition = pos;
            break;
        }
        case "m_LocalPosition.z":
        {
            var pos = tf.localPosition;
            pos.z = item.kfValue;
            tf.localPosition = pos;
            break;
        }

        case "m_LocalScale.x":
        {
            var s = tf.localScale;
            s.x = item.kfValue;
            tf.localScale = s;
            break;
        }
        case "m_LocalScale.y":
        {
            var s = tf.localScale;
            s.y = item.kfValue;
            tf.localScale = s;
            break;
        }

        case "localEulerAnglesRaw.x":
        {
            var euler = tf.localEulerAngles;
            euler.x = item.kfValue;
            tf.localEulerAngles = euler;
            break;
        }
        case "localEulerAnglesRaw.y":
        {
            var euler = tf.localEulerAngles;
            euler.y = item.kfValue;
            tf.localEulerAngles = euler;
            break;
        }
        case "localEulerAnglesRaw.z":
        {
            var euler = tf.localEulerAngles;
            euler.z = item.kfValue;
            tf.localEulerAngles = euler;
            break;
        }

        case "m_AnchoredPosition.x":
        {
            var anPos = rtf.anchoredPosition;
            anPos.x = item.kfValue;
            rtf.anchoredPosition = anPos;
            break;
        }
        case "m_AnchoredPosition.y":
        {
            var anPos = rtf.anchoredPosition;
            anPos.y = item.kfValue;
            rtf.anchoredPosition = anPos;
            break;
        }

        case "m_SizeDelta.x":
        {
            var size = rtf.sizeDelta;
            size.x = item.kfValue;
            rtf.sizeDelta = size;
            break;
        }
        case "m_SizeDelta.y":
        {
            var size = rtf.sizeDelta;
            size.y = item.kfValue;
            rtf.sizeDelta = size;
            break;
        }
        }

        EditorUtility.SetDirty(m_PrefabAsset);
        //PrefabUtility.SavePrefabAsset(prefabAsset);
        AssetDatabase.SaveAssets();
        Repaint();
    }
    

    //检查第1帧的值, 与预制体中GameObject当前的值是否相同
    /// <param name="animCurve">property关联的动画曲线</param>
    private void CheckFirstKeyFrame(EditorCurveBinding b, AnimationCurve animCurve, Transform tf, Keyframe kf0)
    {
        float objValue = 0;
        switch (b.propertyName)
        {
        case "m_LocalPosition.x":
        {
            objValue = tf.localPosition.x;
            break;
        }
        case "m_LocalPosition.y":
        {
            objValue = tf.localPosition.y;
            break;
        }
        case "m_LocalPosition.z":
        {
            objValue = tf.localPosition.z;
            break;
        }
        case "m_LocalScale.x":
        {
            objValue = tf.localScale.x;
            break;
        }
        case "m_LocalScale.y":
        {
            objValue = tf.localScale.y;
            break;
        }
        case "localEulerAnglesRaw.x":
        {
            objValue = tf.localEulerAngles.x;
            break;
        }
        case "localEulerAnglesRaw.y":
        {
            objValue = tf.localEulerAngles.y;
            break;
        }
        case "localEulerAnglesRaw.z":
        {
            objValue = tf.localEulerAngles.z;
            break;
        }
        case "m_AnchoredPosition.x":
        {
            var rtf = (RectTransform)tf;
            objValue = rtf.anchoredPosition.x;
            break;
        }
        case "m_AnchoredPosition.y":
        {
            var rtf = (RectTransform)tf;
            objValue = rtf.anchoredPosition.y;
            break;
        }
        case "m_SizeDelta.x":
        {
            var rtf = (RectTransform)tf;
            objValue = rtf.sizeDelta.x;
            break;
        }
        case "m_SizeDelta.y":
        {
            var rtf = (RectTransform)tf;
            objValue = rtf.sizeDelta.y;
            break;
        }
        default:
            return;

        }

        float delta = objValue - kf0.value;
        if (Mathf.Abs(delta) >= 0.001f)
        {
            if (!m_ValueNotSameItemsDict.TryGetValue(b.path, out var list))
            {
                list = new List<ValueNotSameItem>();
                m_ValueNotSameItemsDict.Add(b.path, list);
                m_NodePathList.Add(b.path);
            }

            var cframe = new ValueNotSameItem();
            cframe.animCurve = animCurve;
            cframe.curveBinding = b;
            cframe.objValue = objValue;
            cframe.kfValue = kf0.value;
            list.Add(cframe);
        }
    }

    public static float TrimFloat(float f)
    {
        int i = (int)(f * 1000);
        float result = i / 1000.0f;
        return result;
    }

}

 

标签:同步,AnimationClip,private,item,objValue,tf,var,工具,Animator
From: https://www.cnblogs.com/sailJs/p/18201798

相关文章

  • Spring Boot —— 集成文档工具
    Swagger->SpringDoc官网地址:https://springdoc.org/是基于OpenAPI3.0规范构建的集成SwaggerUI和ReDoc文档生成工具,可自动注入OpenAPI规范的JSON描述文件,支持OAUTH2、JWT等认证机制。推荐SpringBoot2.4及以上版本使用springdoc-openapi-ui集成Swagger3.x,SpringBoo......
  • 主流原型设计工具介绍
    AxureAxure是一种强大的原型设计工具,它允许用户创建交互式的、高保真度的原型,以及进行用户体验设计和界面设计。Axure可以帮助设计师和产品经理快速创建和共享原型,以便团队成员之间进行沟通和反馈。Axure提供了丰富的交互组件和功能,例如可交互的按钮、链接、表单元素等,使用户能......
  • URLSearchParams:url查询处理工具
    letparams=newURLSearchParams(a=1&b=2&c=3#hash)方法和属性:.get('').has('')//返回true/false.append(name,value)//向URL中添加新的参数.set(name,value)//设置指定参数的值,如果参数不存在则添加新参数.delete(name)//删除指定名称的参数.key()......
  • 阿里 Canal 实时同步 MySQL 增量数据至 ClickHouse 数据库
    主要实现思路1、在clickhouse中创建MySQL引擎表。2、根据MySQL引擎表的信息创建目标表。3、实现canal实时增量同步MySQL数据到clickhouse。MySQL的准备修改配置文件开启Binlog[root@hadoop100module]$sudovim/etc/my.cnfserver-id=1log-bin=mysql-binbinlog_form......
  • 主流原型设计工具
    在现代软件和网页开发过程中,原型设计工具是不可或缺的,它们帮助设计师和开发者在实际开发前快速迭代和测试设计思路。以下是几种主流原型设计工具的对比介绍,包括其特点、使用方法以及具体的案例说明。1、Figma网址:https://www.figma.com特点:协作功能强大:Figma是一款基于云......
  • 文件文本对比工具
    ​ 前言在码代码或做文档的时候,需要对比两个文件的差异,有版本管理工具的时候对比版本就可以,但有时总是有这样或那样的的意外,今天在网上找了几个这样的工具做下对比评测。如果你有更好的工具可以留言告诉我。 一、Meld这是一款开源的文件比对工具,支持Windows、Linux和m......
  • 3DES加密、解密工具类,有需要的直接cv大法拿去用吧
    package你的类路径;importcn.hutool.core.codec.Base64;importjavax.crypto.BadPaddingException;importjavax.crypto.Cipher;importjavax.crypto.IllegalBlockSizeException;importjavax.crypto.NoSuchPaddingException;importjavax.crypto.spec.SecretKeySpec;importjav......
  • 健壮性测试工具-stress-ng
    stress-ng用于模拟系统资源占用的压力测试工具,不限于CPU、磁盘、网络、内存、进程、文件系统等。安装方式:源码安装,源码包下载地址https://fossies.org/linux/privat/stress-ng-0.17.08.tar.gz/ 下载后解压 进入安装目录,进行编译安装make&&makeinstall安装完后,验证安装......
  • 一键自动化博客发布工具,用过的人都说好(腾讯云篇)
    之前说过blog-auto-publishing-tools的实现方式是连到现有的浏览器中,而不是使用内置的浏览器。其中一个很大程度的原因是像腾讯云这种博客发布平台几乎每天都需要重新登录一次,登录还需要手机扫码。所以自动化实现起来非常复杂。所以,我们需要一个已经登录好的浏览器,来实现自动化......
  • 解决wps office必须登录才能编辑使用,即解除不登录账号 工具栏 全灰色限制!
    方法步骤:1、鼠标右键点击“开始”菜单,选择“运行“,输入regedit,点击确定打开注册表编辑器。2、在打开的注册表编辑器页面,依次展开以下注册表项:\HKEY_CURRENT_USER\SOFTWARE\Kingsoft\Office\6.0\plugins\officespace\flogin3、在右侧找到 enableForceLoginForFirstInstallD......