Unity5序列帧动画实现方法及相关问题的解决方案(利用TexturePacker导出图集、自动生成AnimationClip)

最近新加入了一个项目,特效是使用序列帧图片实现的。既然如此,我就准备写点代码根据导出的序列帧图片,网上搜索了一下,发现了雨松MOMO大神的自动生成AnimationClip的文章:

Unity2D研究院之自动生成动画、AnimationController、Prefab(一)

只不过这篇文章已经是两年前的版本了,里面的代码在Unity5里有很多已经不可用了。这篇文章的评论区有朋友写出了一些Unity5需要改动的地方,需要的朋友可以自己翻一下,文章底部也会给出我的完整代码。

这里记录下我遇到的几个问题及解决方法:

1、TexturePacker导出图片,Unity再导入后,锚点全部错乱,导致动画“一直在抖”。
  • 原因:TexturePacker导出图集时有进行透明度裁剪,又没有保留原图的位置信息

  • 解决方案:在TexturePacker设置中将TrimMode设置为Crop, keep position而非Crop, flush
    position

2、UI上是不能直接使用SpriteRenderer的动画的,如果使用Image动画,设置好每帧的Sprite后,即使已经解决了问题1,动画也会一直在抖。
  • 原因:Image里面是使用RectTransform的sizeDelta属性,也就是Inspector面板里的width及height来控制宽高的。锚点则是使用RectTransform的pivot属性。这两个值都要单独设置,且不能有曲线动画。

  • 解决方案:生成AnimationClip时,计算好每张图片的sizeDeltaX\sizeDeltaY\pivotX\pivotY,记录到动画片段里,且设置动画每帧的tangentMode为Constant即可。嗯,好像是解决了问题的样子,但是怎么动画还在抖???这就要看问题3了。

3、将动画片段帧率改为24帧后,Sprite动画就错乱了。比方说正常情况应该是第0帧显示图片1、第1帧显示图片2、第2帧显示图片3….正常情况下第5帧应该显示图片6的,但是这里的动画片段第5帧依然显示图片5(与第4帧显示的图片相同了)。

这个问题就要慢慢说一下了,百度和Google都搜索不到相关的文章(也可能是我搜索的思路不对。。。毕竟英文不是很熟。。。)自己来查又几乎一点头绪都没有。
先上个图给大家看一下:

Unity5序列帧动画实现方法及相关问题的解决方案(利用TexturePacker导出图集、自动生成AnimationClip)_第1张图片

可以看到在我导出的.anim文件里,已经设置好了每帧应该显示的sprite图片,注意下Element5里设置的是名为yaoshi10006的图。但是我在Animation动画编辑器里看到的却是这样:

Unity5序列帧动画实现方法及相关问题的解决方案(利用TexturePacker导出图集、自动生成AnimationClip)_第2张图片

奇了个怪。。我之前一直以为是我导出.anim动画文件时每帧的sprite写入的有问题,这才发现原来不是。然后我想在Animation编辑器里去更改这一帧对应的图片,但无论我改成什么图片都会被还原回去,无法保存。后来一通乱点之后才发现,我可以删除这第3帧的关键帧,重新增加这一帧的关键帧后,就可以设置图片,也可以保存了,动画也就正常了。。。

经过这一通乱点之后,就确认了一定是.anim动画文件的问题。但是如我上图所示,sprite图片设置是对的,那又是哪里有问题呢?我将代码自动生成的.anim额外保存了一份,然后与我修改好的这一份.anim文件用对比工具进行文字对比,发现了一件有意思的事情:

文件对比结果,右图为修改后正常的动画文件

可以看到,原自动生成的动画文件第5帧记录的time是0.20833334,而修改后的动画文件第5帧的time是0.20833333,就是因为这么一点差异,导致了第5帧的图片没有产生变化,依然使用了第4帧的图片。既然知道了问题出在哪里,就容易解决了不是,回头看下生成AnimationClip时这个time是如何设置的:

    float frameTime = 1/clip.frameRate;
    for (int i = 0; i < sprites.Length; i++)
    {
        Sprite sprite = sprites[i];
        keyFrames[i] = new ObjectReferenceKeyframe();
        keyFrames[i].time = frameTime * i;
        keyFrames[i].value = sprite;
    }

一看time是一个float对象,难道会是由于精度问题所引起的?赶紧将代码进行了如下改动:

        double frameTime = 1d / 24d;
        for (int i = 0; i < sprites.Length; i++)
        {
            Sprite sprite = sprites[i];
            keyFrames[i] = new ObjectReferenceKeyframe();
            keyFrames[i].time = (float)(frameTime * i);
            keyFrames[i].value = sprite;
        }

到此,终于修复了动画中sprite图片错乱的问题。总算是有一个解决方法了吧,也不知道是否有更加合适的解决方案,如果有欢迎跟我说。。。下面是完整的自动生成动画文件的代码,其中的大部分思路均引用自雨松MOMO大神的文章。

using UnityEngine;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine.UI;

/// 
/// 根据sprites图集自动生成序列帧动画
/// 
public class BuildAnimationEditor : Editor
{
    [MenuItem("Assets/BuildSpriteRenderAnimation")]
    public static void BuildSpriteRenderAnimation()
    {
        BuildAnimation(true, typeof(SpriteRenderer));
    }

    [MenuItem("Assets/BuildImageAnimation")]
    public static void BuildImageAnimation()
    {
        BuildAnimation(true, typeof(Image));
    }

    public static void BuildAnimation(bool isLoop, System.Type curveBindingType)
    {
        UnityEngine.Object[] arr = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.TopLevel);

        foreach (Object obj in arr)
        {
            string path = AssetDatabase.GetAssetPath(obj);

            Sprite[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(path)
                .Select(x => x as Sprite).Where(x => x != null).ToArray();

            if (sprites == null || sprites.Length == 0)
                continue;

            string directoryName = Path.GetDirectoryName(path);
            string animationName = Path.GetFileNameWithoutExtension(path);
            if (curveBindingType == typeof (Image))
                animationName = "UI-" + animationName;

            //这里是否循环的参数我写死了为true,可以根据自己的需要进行修改.
            AnimationClip clip = BuildAnimationClip(directoryName, animationName, sprites, true, curveBindingType);
            List clips = new List();
            clips.Add(clip);

            AnimatorController controller = BuildAnimationController(clips, directoryName, animationName);
            BuildPrefab(directoryName, animationName, controller, sprites[0], curveBindingType);
        }
    }


    static AnimationClip BuildAnimationClip(string directoryPath, string animationName, Sprite[] sprites, bool isLoop, System.Type curveBindingType)
    {
        AnimationClip clip = new AnimationClip();

#if !UNITY_5
        AnimationUtility.SetAnimationType(clip, ModelImporterAnimationType.Generic);
#endif

        EditorCurveBinding curveBinding = new EditorCurveBinding();
        curveBinding.type = curveBindingType;
        curveBinding.path = "";
        curveBinding.propertyName = "m_Sprite";
        ObjectReferenceKeyframe[] keyFrames = new ObjectReferenceKeyframe[sprites.Length];

        //动画帧率,这里统一用24帧
        clip.frameRate = 24f;

        //这里的keyFrames[i].time是float类型,而float的精度会影响到sprite的切换,解决方法就是使用double类型计算time。。。。
        //动画长度是按秒为单位,1/10就表示1秒切10张图片,根据项目的情况可以自己调节
        double frameTime = 1d / 24d;
        for (int i = 0; i < sprites.Length; i++)
        {
            Sprite sprite = sprites[i];
            keyFrames[i] = new ObjectReferenceKeyframe();
            keyFrames[i].time = (float)(frameTime * i);
            keyFrames[i].value = sprite;
        }

        if (curveBindingType == typeof(Image))
        {
            //如果是Image动画,要额外设置宽高、pivot等属性
            BuildImageAnimationClip(clip, sprites, frameTime);
        }

        //有些动画我希望天生它就动画循环
        if (isLoop)
        {
            SerializedObject serializedClip = new SerializedObject(clip);
            AnimationClipSettings clipSettings = new AnimationClipSettings(serializedClip.FindProperty("m_AnimationClipSettings"));
            clipSettings.loopTime = true;
            serializedClip.ApplyModifiedProperties();
        }

        AnimationUtility.SetObjectReferenceCurve(clip, curveBinding, keyFrames);
        AssetDatabase.CreateAsset(clip, directoryPath + "/" + animationName + ".anim");
        AssetDatabase.SaveAssets();

        return clip;
    }

    private static void BuildImageAnimationClip(AnimationClip clip, Sprite[] sprites, double frameTime)
    {
        EditorCurveBinding sizeDelta_x_curveBinding = new EditorCurveBinding();
        sizeDelta_x_curveBinding.type = typeof(RectTransform);
        sizeDelta_x_curveBinding.path = "";
        sizeDelta_x_curveBinding.propertyName = "m_SizeDelta.x";

        EditorCurveBinding sizeDelta_y_curveBinding = new EditorCurveBinding();
        sizeDelta_y_curveBinding.type = typeof(RectTransform);
        sizeDelta_y_curveBinding.path = "";
        sizeDelta_y_curveBinding.propertyName = "m_SizeDelta.y";

        EditorCurveBinding pivot_x_curveBinding = new EditorCurveBinding();
        pivot_x_curveBinding.type = typeof(RectTransform);
        pivot_x_curveBinding.path = "";
        pivot_x_curveBinding.propertyName = "m_Pivot.x";

        EditorCurveBinding pivot_y_curveBinding = new EditorCurveBinding();
        pivot_y_curveBinding.type = typeof(RectTransform);
        pivot_y_curveBinding.path = "";
        pivot_y_curveBinding.propertyName = "m_Pivot.y";

        Keyframe[] sizeDelta_x_keyframes = new Keyframe[sprites.Length];
        Keyframe[] sizeDelta_y_keyframes = new Keyframe[sprites.Length];
        Keyframe[] pivot_x_keyframes = new Keyframe[sprites.Length];
        Keyframe[] pivot_y_keyframes = new Keyframe[sprites.Length];

        for (int i = 0; i < sprites.Length; i++)
        {
            sizeDelta_x_keyframes[i] = new Keyframe((float)(frameTime * i), sprites[i].rect.width);
            sizeDelta_y_keyframes[i] = new Keyframe((float)(frameTime * i), sprites[i].rect.height);

            float x_pivot = sprites[i].pivot.x / sprites[i].rect.width;
            float y_pivot = sprites[i].pivot.y / sprites[i].rect.height;
            pivot_x_keyframes[i] = new Keyframe((float)(frameTime * i), x_pivot);
            pivot_y_keyframes[i] = new Keyframe((float)(frameTime * i), y_pivot);
        }

        AnimationCurve sizeDelta_x_curve = new AnimationCurve(sizeDelta_x_keyframes);
        AnimationCurve sizeDelta_y_curve = new AnimationCurve(sizeDelta_y_keyframes);
        AnimationCurve pivot_x_curve = new AnimationCurve(pivot_x_keyframes);
        AnimationCurve pivot_y_curve = new AnimationCurve(pivot_y_keyframes);

        for (int i = 0; i < sprites.Length; i++)
        {
            AnimationUtility.SetKeyLeftTangentMode(sizeDelta_x_curve, i, AnimationUtility.TangentMode.Constant);
            AnimationUtility.SetKeyRightTangentMode(sizeDelta_x_curve, i, AnimationUtility.TangentMode.Constant);

            AnimationUtility.SetKeyLeftTangentMode(sizeDelta_y_curve, i, AnimationUtility.TangentMode.Constant);
            AnimationUtility.SetKeyRightTangentMode(sizeDelta_y_curve, i, AnimationUtility.TangentMode.Constant);

            AnimationUtility.SetKeyLeftTangentMode(pivot_x_curve, i, AnimationUtility.TangentMode.Constant);
            AnimationUtility.SetKeyRightTangentMode(pivot_x_curve, i, AnimationUtility.TangentMode.Constant);

            AnimationUtility.SetKeyLeftTangentMode(pivot_y_curve, i, AnimationUtility.TangentMode.Constant);
            AnimationUtility.SetKeyRightTangentMode(pivot_y_curve, i, AnimationUtility.TangentMode.Constant);
        }

        AnimationUtility.SetEditorCurve(clip, sizeDelta_x_curveBinding, sizeDelta_x_curve);
        AnimationUtility.SetEditorCurve(clip, sizeDelta_y_curveBinding, sizeDelta_y_curve);
        AnimationUtility.SetEditorCurve(clip, pivot_x_curveBinding, pivot_x_curve);
        AnimationUtility.SetEditorCurve(clip, pivot_y_curveBinding, pivot_y_curve);
    }

    static AnimatorController BuildAnimationController(List clips, string directoryName, string name)
    {
        AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(directoryName + "/" + name + ".controller");
        AnimatorControllerLayer layer = animatorController.layers[0];
        AnimatorStateMachine asm = layer.stateMachine;
        foreach (AnimationClip newClip in clips)
        {
            AnimatorState state = asm.AddState(newClip.name);
            state.motion = newClip;
            AnimatorStateTransition trans = asm.AddAnyStateTransition(state);
            trans.hasExitTime = false;
        }
        AssetDatabase.SaveAssets();
        return animatorController;
    }

    static void BuildPrefab(string directoryName, string goName, AnimatorController animatorCountorller, Sprite defaultSprite, System.Type curveBindingType)
    {
        //生成Prefab 添加一张预览用的Sprite
        GameObject go = new GameObject(goName);

        if (curveBindingType == typeof (SpriteRenderer))
        {
            SpriteRenderer spriteRender = go.AddComponent();
            spriteRender.sprite = defaultSprite;
            spriteRender.sortingLayerName = "Effect";
        }
        else if (curveBindingType == typeof (Image))
        {
            Image image = go.AddComponent();
            image.sprite = defaultSprite;
            image.rectTransform.sizeDelta = new Vector2(defaultSprite.rect.width, defaultSprite.rect.height);

            float x_pivot = defaultSprite.pivot.x / defaultSprite.rect.width;
            float y_pivot = defaultSprite.pivot.y / defaultSprite.rect.height;
            image.rectTransform.pivot = new Vector2(x_pivot, y_pivot);
        }

        Animator animator = go.AddComponent();
        animator.runtimeAnimatorController = animatorCountorller;
        animator.cullingMode = AnimatorCullingMode.CullCompletely;

        PrefabUtility.CreatePrefab(directoryName + "/" + goName + ".prefab", go);
        DestroyImmediate(go);
    }

    class AnimationClipSettings
    {
        SerializedProperty m_Property;

        private SerializedProperty Get(string property) { return m_Property.FindPropertyRelative(property); }

        public AnimationClipSettings(SerializedProperty prop) { m_Property = prop; }

        public float startTime { get { return Get("m_StartTime").floatValue; } set { Get("m_StartTime").floatValue = value; } }
        public float stopTime { get { return Get("m_StopTime").floatValue; } set { Get("m_StopTime").floatValue = value; } }
        public float orientationOffsetY { get { return Get("m_OrientationOffsetY").floatValue; } set { Get("m_OrientationOffsetY").floatValue = value; } }
        public float level { get { return Get("m_Level").floatValue; } set { Get("m_Level").floatValue = value; } }
        public float cycleOffset { get { return Get("m_CycleOffset").floatValue; } set { Get("m_CycleOffset").floatValue = value; } }

        public bool loopTime { get { return Get("m_LoopTime").boolValue; } set { Get("m_LoopTime").boolValue = value; } }
        public bool loopBlend { get { return Get("m_LoopBlend").boolValue; } set { Get("m_LoopBlend").boolValue = value; } }
        public bool loopBlendOrientation { get { return Get("m_LoopBlendOrientation").boolValue; } set { Get("m_LoopBlendOrientation").boolValue = value; } }
        public bool loopBlendPositionY { get { return Get("m_LoopBlendPositionY").boolValue; } set { Get("m_LoopBlendPositionY").boolValue = value; } }
        public bool loopBlendPositionXZ { get { return Get("m_LoopBlendPositionXZ").boolValue; } set { Get("m_LoopBlendPositionXZ").boolValue = value; } }
        public bool keepOriginalOrientation { get { return Get("m_KeepOriginalOrientation").boolValue; } set { Get("m_KeepOriginalOrientation").boolValue = value; } }
        public bool keepOriginalPositionY { get { return Get("m_KeepOriginalPositionY").boolValue; } set { Get("m_KeepOriginalPositionY").boolValue = value; } }
        public bool keepOriginalPositionXZ { get { return Get("m_KeepOriginalPositionXZ").boolValue; } set { Get("m_KeepOriginalPositionXZ").boolValue = value; } }
        public bool heightFromFeet { get { return Get("m_HeightFromFeet").boolValue; } set { Get("m_HeightFromFeet").boolValue = value; } }
        public bool mirror { get { return Get("m_Mirror").boolValue; } set { Get("m_Mirror").boolValue = value; } }
    }

}

你可能感兴趣的:(Unity5,动画,unity,动画,序列帧动画)