欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity 动画文件过渡帧的处理

程序员文章站 2022-03-21 23:47:25
...

原因

技能镜头是直接在 3ds Max 里面跟动作一起设计的,导出到 Unity 直接进行播放。然而因为导出的动画是连续的,在中间的镜头切换时,会产生过渡帧,影响表现。

Unity 动画文件过渡帧的处理

解决

因为 FBX 动画是只读,无法进行编辑,所以需要拷贝一份动画文件,再对这份动画文件进行编辑。

另外,对于动画的旋转插值来说,切镜头肯定会出现大于 180 度的情况,所以要将插值改成欧拉角插值方式。

Unity 动画文件过渡帧的处理

代码地址:

https://github.com/akof1314/AnimationPath/blob/master/AnimationPath/Assets/TangentToConstant/Editor/AnimationKeyframeTangentToConstantWindow.cs

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

 

相关标签: Unity