AnimationClip同步工具

yanghui01發表於2024-05-20

用途:列出動畫的第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;
    }

}

相關文章