1. 程式人生 > >Unity 動畫檔案過渡幀的處理

Unity 動畫檔案過渡幀的處理

原因

技能鏡頭是直接在 3ds Max 裡面跟動作一起設計的,匯出到 Unity 直接進行播放。然而因為匯出的動畫是連續的,在中間的鏡頭切換時,會產生過渡幀,影響表現。

解決

因為 FBX 動畫是隻讀,無法進行編輯,所以需要拷貝一份動畫檔案,再對這份動畫檔案進行編輯。

另外,對於動畫的旋轉插值來說,切鏡頭肯定會出現大於 180 度的情況,所以要將插值改成尤拉角插值方式。

程式碼地址:

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public class AnimationKeyframeTangentToConstantWindow : EditorWindow
{
    public static Action<AnimationClip, AnimationClip> onClipCopyModify;

    //[MenuItem("Tool/動畫瞬幀工具")]
    public static void Init()
    {
        EditorWindow editorWindow = GetWindow<AnimationKeyframeTangentToConstantWindow>(true, "動畫瞬切幀工具");
        editorWindow.minSize = new Vector2(160f, 30f);
        editorWindow.maxSize = new Vector2(editorWindow.minSize.x, editorWindow.minSize.y);
    }

    private void OnGUI()
    {
        EditorGUILayout.BeginHorizontal();
        if (GUILayout.Button("瞬切", "LargeButton", GUILayout.Width(150f)))
        {
            DoKeyframeTangentToConstant();
        }
        EditorGUILayout.EndHorizontal();
    }

    private void DoKeyframeTangentToConstant()
    {
        AnimationWindowReflect animationWindowReflect = AnimationWindowUtil.GetAnimationWindowReflect();
        if (!animationWindowReflect.firstAnimationWindow)
        {
            SimpleDisplayDialog("Animation 視窗沒有開啟");
            return;
        }

        AnimationClip activeAnimationClip = animationWindowReflect.activeAnimationClip;
        if (activeAnimationClip == null)
        {
            SimpleDisplayDialog("Animation 視窗沒有任何動畫片段");
            return;
        }

        float currentTime = animationWindowReflect.currentTime;
        if ((activeAnimationClip.hideFlags & HideFlags.NotEditable) != HideFlags.None)
        {
            // FBX 動畫則自動執行拷貝
            AnimationClip oldClip = activeAnimationClip;
            activeAnimationClip = CopyAnimationClipAsset(activeAnimationClip);
            animationWindowReflect.activeAnimationClip = activeAnimationClip;
            animationWindowReflect.currentTime = currentTime;
            if (onClipCopyModify != null)
            {
                onClipCopyModify(oldClip, activeAnimationClip);
            }
        }

        KeyframeTangentToConstant(activeAnimationClip, currentTime);
        animationWindowReflect.firstAnimationWindow.Repaint();
        SimpleDisplayDialog("瞬切完成");
    }

    private static void SimpleDisplayDialog(string text)
    {
        EditorUtility.DisplayDialog("提示", text, "確定");
    }

    /// <summary>
    /// 參照 ProjectWindowUtil.DuplicateSelectedAssets
    /// </summary>
    /// <param name="clip"></param>
    /// <returns></returns>
    private static AnimationClip CopyAnimationClipAsset(AnimationClip clip)
    {
        string assetPath = AssetDatabase.GetAssetPath(clip);
        string path = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(Path.GetDirectoryName(assetPath),
                                                                Path.GetFileNameWithoutExtension(assetPath)) + ".anim");
        AnimationClip animationClip2 = new AnimationClip();
        EditorUtility.CopySerialized(clip, animationClip2);
        AssetDatabase.CreateAsset(animationClip2, path);
        AssetDatabase.ImportAsset(path);

        if (Selection.activeObject == clip)
        {
            Selection.activeObject = animationClip2;
        }
        return animationClip2;
    }

    private static void KeyframeTangentToConstant(AnimationClip clip, float time)
    {
        Undo.RegisterCompleteObjectUndo(clip, "Keyframe Tangent To Constant");
        EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
        SetInterpolation(clip, curveBindings, Mode.RawEuler);
        curveBindings = AnimationUtility.GetCurveBindings(clip);
        foreach (var curveBinding in curveBindings)
        {
            AnimationCurve animationCurve = AnimationUtility.GetEditorCurve(clip, curveBinding);
            for (var i = 0; i < animationCurve.keys.Length; i++)
            {
                var keyframe = animationCurve.keys[i];
                if (Mathf.Approximately(keyframe.time, time))
                {
                    AnimationUtility.SetKeyRightTangentMode(animationCurve, i, AnimationUtility.TangentMode.Constant);
                }
            }

            AnimationUtility.SetEditorCurve(clip, curveBinding, animationCurve);
        }
    }

    private enum Mode
    {
        Baked,
        NonBaked,
        RawQuaternions,
        RawEuler,
        Undefined,
    }

    private static bool IsTransformType(System.Type type)
    {
        return type == typeof(Transform) || type == typeof(RectTransform);
    }

    private static Mode GetModeFromCurveData(EditorCurveBinding data)
    {
        if (IsTransformType(data.type) && data.propertyName.StartsWith("localEulerAngles"))
        {
            if (data.propertyName.StartsWith("localEulerAnglesBaked"))
                return Mode.Baked;
            return data.propertyName.StartsWith("localEulerAnglesRaw") ? Mode.RawEuler : Mode.NonBaked;
        }
        return IsTransformType(data.type) && data.propertyName.StartsWith("m_LocalRotation") ? Mode.RawQuaternions : Mode.Undefined;
    }

    private static string GetPrefixForInterpolation(Mode newInterpolationMode)
    {
        if (newInterpolationMode == Mode.Baked)
            return "localEulerAnglesBaked";
        if (newInterpolationMode == Mode.NonBaked)
            return "localEulerAngles";
        if (newInterpolationMode == Mode.RawEuler)
            return "localEulerAnglesRaw";
        if (newInterpolationMode == Mode.RawQuaternions)
            return "m_LocalRotation";
        return null;
    }

    private static EditorCurveBinding RemapAnimationBindingForRotationCurves(EditorCurveBinding curveBinding, AnimationClip clip)
    {
        if (!IsTransformType(curveBinding.type))
            return curveBinding;
        Mode modeFromCurveData = GetModeFromCurveData(curveBinding);
        if (modeFromCurveData == Mode.Undefined)
            return curveBinding;
        string str = curveBinding.propertyName.Split('.')[1];
        EditorCurveBinding binding = curveBinding;
        if (modeFromCurveData != Mode.NonBaked)
        {
            binding.propertyName = GetPrefixForInterpolation(Mode.NonBaked) + "." + str;
            if (AnimationUtility.GetEditorCurve(clip, binding) != null)
                return binding;
        }
        if (modeFromCurveData != Mode.Baked)
        {
            binding.propertyName = GetPrefixForInterpolation(Mode.Baked) + "." + str;
            if (AnimationUtility.GetEditorCurve(clip, binding) != null)
                return binding;
        }
        if (modeFromCurveData != Mode.RawEuler)
        {
            binding.propertyName = GetPrefixForInterpolation(Mode.RawEuler) + "." + str;
            if (AnimationUtility.GetEditorCurve(clip, binding) != null)
                return binding;
        }
        return curveBinding;
    }

    /// <summary>
    /// 參照 RotationCurveInterpolation.SetInterpolation
    /// </summary>
    /// <param name="clip"></param>
    private static void SetInterpolation(AnimationClip clip, EditorCurveBinding[] curveBindings, Mode newInterpolationMode)
    {
        List<EditorCurveBinding> list1 = new List<EditorCurveBinding>();
        List<AnimationCurve> list2 = new List<AnimationCurve>();
        List<EditorCurveBinding> list3 = new List<EditorCurveBinding>();
        foreach (var curveBinding in curveBindings)
        {
            EditorCurveBinding editorCurveBinding = RemapAnimationBindingForRotationCurves(curveBinding, clip);
            switch (GetModeFromCurveData(editorCurveBinding))
            {
                case Mode.Undefined:
                    break;
                case Mode.RawQuaternions:
                    break;
                default:
                    AnimationCurve editorCurve = AnimationUtility.GetEditorCurve(clip, editorCurveBinding);
                    if (editorCurve != null)
                    {
                        string propertyName = editorCurveBinding.propertyName;
                        string str = GetPrefixForInterpolation(newInterpolationMode) + '.' + propertyName[propertyName.Length - 1];
                        list1.Add(new EditorCurveBinding()
                        {
                            propertyName = str,
                            type = editorCurveBinding.type,
                            path = editorCurveBinding.path
                        });
                        list2.Add(editorCurve);
                        list3.Add(new EditorCurveBinding()
                        {
                            propertyName = editorCurveBinding.propertyName,
                            type = editorCurveBinding.type,
                            path = editorCurveBinding.path
                        });
                    }
                    break;
            }
        }

        foreach (EditorCurveBinding binding in list3)
            AnimationUtility.SetEditorCurve(clip, binding, null);
        foreach (EditorCurveBinding binding in list1)
            AnimationUtility.SetEditorCurve(clip, binding, list2[list1.IndexOf(binding)]);
    }
}