精华内容
下载资源
问答
  • 动画生成工具 动画生动画生成工具成工具
  • Unity动画生成工具

    千次阅读 2019-07-04 22:05:06
    然后类似的模型可能有很多,也就是我们所理解的皮肤,低级的是换贴图,高级一点的换模型,如果模型比较多的话,美术要创建和管理很多相同的动画,重复性的劳动,这会就需要有个动画生成器了。 思路 我们可以根据美术...

    前言

    美术做一个模型,附带很多动画,然后就创建和管理动画状态机,然后类似的模型可能有很多,也就是我们所理解的皮肤,低级的是换贴图,高级一点的换模型,如果模型比较多的话,美术要创建和管理很多相同的动画,重复性的劳动,这会就需要有个动画生成器了。

    思路

    我们可以根据美术的要求通过代码创建一个AnimatorController,但一旦美术修改什么需求我们就要跟着修改会比较麻烦,比较简便的是美术先创建一个动画控制器模板,然后接下来重复性的劳动我们就通过程序工具来解决。思路是copy模板,修改模板里面动画状态的Motion,指向当前模型的动画clip,Motion有的是简单的动画clip,这个直接替换没啥好说的,有的是混合树BlendTree,这个替换会碰到小坑,如果我们获取到混合树的Motion然后遍历里面的Children,替换每个child的Motion是替换不了的,这个我也是查看了BlendTree的源码才知道,源码里面Children的管理(增加和删除)都是通过覆盖的方式替换Children数组,但我也有通过替换Children数组的方式来实现还是没有替换成功,最后还是通过代码创建BlendTree的方式来替换BlendTree才成功了。

    效果

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    代码

    基于Odin插件的模式
    using Sirenix.OdinInspector;
    using Sirenix.OdinInspector.Editor;
    using Sirenix.Utilities;
    using Sirenix.Utilities.Editor;
    using System;
    using System.IO;
    using System.Windows.Forms;
    using UnityEditor;
    using UnityEditor.Animations;
    using UnityEngine;
    
    public class AnimatorGeneratorWindow : OdinEditorWindow
    {
        string fileDirectory;
        bool recursion = false;//是否是递归模式
    
        [UnityEditor.MenuItem("Tools/AnimatorGenerator")]
        private static void Open()
        {
            var window = GetWindow<AnimatorGeneratorWindow>();
            window.position = GUIHelper.GetEditorWindowRect().AlignCenter(450, 500);
        }
        [InlineEditor(InlineEditorModes.LargePreview)] //对材质和mesh有效,可以预览
        [LabelText("动画模板")]
        public AnimatorController AnimatorControllerTemplate;
    
        [Button("选择要生成的目录(递归遍历)", ButtonSizes.Medium), GUIColor(1, 1, 0)]
        [HorizontalGroup("ChooseMenu")]
        [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
        private void ChooseMenus()
        {
            Debug.Log("选择目录");
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.RootFolder = Environment.SpecialFolder.MyComputer;
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                fileDirectory = fbd.SelectedPath;
                recursion = true;
            }
            Debug.Log("选择目录:" + fileDirectory);
        }
    
        [Button("选择要生成的目录(单文件目录)", ButtonSizes.Medium), GUIColor(1, 1, 0)]
        [HorizontalGroup("ChooseMenu")]
        [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
        private void ChooseMenu()
        {
            Debug.Log("选择目录");
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.RootFolder = Environment.SpecialFolder.MyComputer;
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                fileDirectory = fbd.SelectedPath;
                recursion = false;
            }
            Debug.Log("选择目录:" + fileDirectory);
        }
    
        [Button("生成动画控制器", ButtonSizes.Medium), GUIColor(0, 1, 1)]
        [HorizontalGroup("Buttons")]
        [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
        private void GeneratorAnimatorButton()
        {
            Debug.Log("生成动画控制器");
            CreateAnimatorAssets();
        }
    
    
        private void CreateAnimatorAssets()
        {
            if (!Directory.Exists(fileDirectory))
            {
                throw new Exception("目录不存在或者路径不存在");
            }
            if (AnimatorControllerTemplate == null)
            {
                Debug.LogError("没有选择动画模板");
                return;
            }
    
            var animatorFilePath = AssetDatabase.GetAssetPath(AnimatorControllerTemplate);
            var dirArray = fileDirectory.Split('\\');
            var pathLastDirectoryName = dirArray[dirArray.Length - 1];
            var animatorExtension = Path.GetExtension(animatorFilePath);
    
            if (recursion)
            {
                var folders = Directory.GetDirectories(fileDirectory);
                foreach (var folder in folders)
                {
                    SingleFolderDispose(folder, animatorFilePath);
                }
            }
            else
            {
                SingleFolderDispose(fileDirectory, animatorFilePath);
            }
        }
    
    
        private void SingleFolderDispose(string folder, string animatorFilePath)
        {
            DirectoryInfo info = new DirectoryInfo(folder);
            string folderName = info.Name;
            var newAnimatorFilePath = Path.Combine(folder, folderName + Path.GetExtension(animatorFilePath));
            File.Copy(animatorFilePath, newAnimatorFilePath, true);
            AssetDatabase.Refresh();
            AnalyzeAnimController(folder, newAnimatorFilePath);
            AssetDatabase.Refresh();
            var obj = LoadFbx(folder, newAnimatorFilePath);
            PrefabUtility.SaveAsPrefabAsset(obj, string.Format("{0}/{1}.prefab", folder, folderName));
            DestroyImmediate(obj);
        }
    
        private GameObject LoadFbx(string folder, string animatorFilePath)
        {
            //找到fbx,找到当前目录下名字不带@符号的fbx 进行实例化
            FileInfo tempFile = null;
            DirectoryInfo folderDirectoryInfo = new DirectoryInfo(folder);
            var files = folderDirectoryInfo.GetFiles();
            foreach (var fileInfo in files)
            {
                if (!fileInfo.Name.Contains("@") && fileInfo.Name.Contains(".fbx") && !fileInfo.Name.Contains(".meta"))   //TODO:也可以根据floder名去找对应的fbx
                {
                    tempFile = fileInfo;
                    break;
                }
            }
            if (tempFile == null)
            {
                throw new Exception(string.Format("目录:{0} 没有找到不带@的fbx", folder));
            }
    
            var obj = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(GetAssetPath(tempFile.FullName))) as GameObject;
            //找到controller
    
            var controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(GetAssetPath(animatorFilePath));
            obj.GetComponent<Animator>().runtimeAnimatorController = controller;
            return obj;
        }
    
        private void AnalyzeAnimController(string floder, string controllerPath)
        {
            var assetPath = GetAssetPath(controllerPath);
            var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
            string animationFolder = Path.GetDirectoryName(assetPath) + "\\animations";
            animationFolder.Replace("\\", "/");
            //animatorController的Parameters不需要修改
            //遍历所有的layer
            for (int i = 0; i < animatorController.layers.Length; i++)
            {
                var layer = animatorController.layers[i];
                AnimatorStateMachine sm = layer.stateMachine;
                RecursionAnalyzeAnimatorStateMachine(sm, animationFolder);
            }
        }
    
        private string GetAssetPath(string fullPath)
        {
            var strs = fullPath.Split(new string[] { "Assets" }, StringSplitOptions.None);
            var assetPath = "Assets" + strs[strs.Length - 1];
            assetPath.Replace("\\", "/");
            return assetPath;
        }
    
        private void RecursionAnalyzeAnimatorStateMachine(AnimatorStateMachine stateMachine, string animationFlolder)
        {
            //遍历states
            for (int i = 0; i < stateMachine.states.Length; i++)
            {
                var animatorState = stateMachine.states[i];
                var motion = animatorState.state.motion;
                if (motion != null)
                {
                    if (motion is BlendTree)
                    {
                        BlendTree bt = motion as BlendTree;
    
                        ChildMotion[] childMotions = new ChildMotion[bt.children.Length];
    
    
                        for (int j = 0; j < bt.children.Length; j++)
                        {
                            var childMotion = bt.children[j];
                            var motionClip = GetAnimationClip(childMotion.motion.name, animationFlolder);
    
                            if (motionClip == null)
                            {
                                Debug.LogError("没有找到" + motion.name + "的动画控制器");
                            }
                            else
                            {
                                Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, childMotion.motion));   //根据名字找到对应的prefab 然后找出里面的动画文件加载
                                //childMotion.motion = (Motion)motionClip;
    
                                //var newChildMotion = new ChildMotion() { motion = motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                                //childMotion = newChildMotion;
    
                                childMotions[j] = new ChildMotion() { motion = (Motion)motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                            }
                        }
                        //bt.children = childMotions;
                        BlendTree newBt = new BlendTree()
                        {
                            blendParameter = bt.blendParameter,
                            blendParameterY = bt.blendParameterY,
                            blendType = bt.blendType,
                            hideFlags = bt.hideFlags,
                            maxThreshold = bt.maxThreshold,
                            minThreshold = bt.minThreshold,
                            name = bt.name,
                            useAutomaticThresholds = bt.useAutomaticThresholds,
                            children = childMotions,
                        };
                        animatorState.state.motion = newBt;
                    }
                    else
                    {
                        animatorState.state.motion = null;
                        var motionClip = GetAnimationClip(motion.name, animationFlolder);
                        if (motionClip == null)
                        {
                            Debug.LogError("没有找到" + motion.name + "的动画控制器");
                        }
                        else
                        {
                            animatorState.state.motion = (Motion)motionClip;
                            Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, motion));
                        }
                    }
                }
            }
            //遍历substatemachine
            for (int j = 0; j < stateMachine.stateMachines.Length; j++)
            {
                var stateMachines = stateMachine.stateMachines[j];
                RecursionAnalyzeAnimatorStateMachine(stateMachines.stateMachine, animationFlolder);
            }
        }
    
        private AnimationClip GetAnimationClip(string motionName, string animationFolder)
        {
            var motionNameExt = motionName.Substring(motionName.IndexOf("_"));
            DirectoryInfo directoryInfo = new DirectoryInfo(animationFolder);
            FileInfo tempFileInfo = null;
            var files = directoryInfo.GetFiles("*.FBX", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(motionNameExt + ".FBX"))    //有可能是Robert01_gun_jump_start  对应的Robert01@Robert01_gun_jump
                {
                    tempFileInfo = files[i];
                    break;
                }
            }
            if (tempFileInfo != null)
            {
                var datas = AssetDatabase.LoadAllAssetsAtPath(GetAssetPath(tempFileInfo.FullName));
                if (datas.Length == 0)
                {
                    Debug.Log(string.Format("Can't find clip in {0}", tempFileInfo.FullName));
                    return null;
                }
                foreach (var data in datas)
                {
                    if (!(data is AnimationClip))//如果不是动画文件则跳过
                        continue;
                    var newClip = data as AnimationClip;
                    return newClip;
                }
            }
            else
            {
                Debug.LogError("没有找到对应的动画FBX:" + motionName);
            }
            return null;
        }
    }
    
    基于OnGUI的原生模式

    OnGUI的模式是采用的Selection方式选择模板和目录。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor;
    using System.Windows.Forms;
    using System;
    using System.IO;
    using UnityEditor.Animations;
    
    public class AnimatorEditor : EditorWindow
    {
        string fileDirectory = string.Empty;
        string assetDirectory = string.Empty;
        bool recursion = false; //是否是递归模式
        AnimatorController animatorControllerTemplate = null;
    
        [UnityEditor.MenuItem("Tools/AnimatorEditor")]
        private static void Open()
        {
            var window = EditorWindow.GetWindow(typeof(AnimatorEditor), true, "动画生成器", true);
            window.Show();
        }
    
        void OnSelectionChange()
        {
            if (Selection.activeObject != null)
            {
                Debug.Log("选择物体:" + Selection.activeObject);
                if (Selection.activeObject is AnimatorController)
                {
                    animatorControllerTemplate = Selection.activeObject as AnimatorController;
                    Debug.Log("选择的物体是:" + animatorControllerTemplate.name);
                }
                if (Selection.activeObject is DefaultAsset) //选择目录
                {
                    var asset = Selection.activeObject as DefaultAsset;
                    string[] strs = Selection.assetGUIDs;
                    string path = AssetDatabase.GUIDToAssetPath(strs[0]);
                    assetDirectory = path;
                    fileDirectory = Path.Combine(Environment.CurrentDirectory, path);
                    Debug.Log("选择的路径:" + path);
                }
            }
        }
    
        void OnGUI()
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label("选择Controller模板:");
            GUILayout.Label(animatorControllerTemplate == null ? "" : animatorControllerTemplate.name);
            GUILayout.EndHorizontal();
            GUILayout.Space(5);
            GUILayout.BeginHorizontal();
            GUILayout.Label("选择生成的目录:");
            GUILayout.Label(assetDirectory);
            GUILayout.EndHorizontal();
            GUILayout.Space(5);
            GUILayout.BeginHorizontal();
            GUILayout.Label("是否批量生成:");
            recursion = EditorGUILayout.Toggle(recursion);
            GUILayout.EndHorizontal();
            GUILayout.Space(30);
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("选择要生成的目录(递归遍历)"))
            {
                FolderBrowserDialog fbd = new FolderBrowserDialog();
                fbd.RootFolder = Environment.SpecialFolder.MyComputer;
                if (fbd.ShowDialog() == DialogResult.OK)
                {
                    fileDirectory = fbd.SelectedPath;
                    recursion = true;
                }
                Debug.Log("选择目录:" + fileDirectory);
            }
            if (GUILayout.Button("选择要生成的目录(单文件目录)"))
            {
                FolderBrowserDialog fbd = new FolderBrowserDialog();
                fbd.RootFolder = Environment.SpecialFolder.MyComputer;
                if (fbd.ShowDialog() == DialogResult.OK)
                {
                    fileDirectory = fbd.SelectedPath;
                    recursion = false;
                }
                Debug.Log("选择目录:" + fileDirectory);
            }
            GUILayout.EndHorizontal();
            GUILayout.Space(30);
            if (GUILayout.Button("生成AnimatorController"))
            {
                Debug.Log("生成动画控制器");
                CreateAnimatorAssets();
            }
        }
    
    
        private void CreateAnimatorAssets()
        {
            if (!Directory.Exists(fileDirectory))
            {
                throw new Exception("目录不存在或者路径不存在");
            }
            if (animatorControllerTemplate == null)
            {
                Debug.LogError("没有选择动画模板");
                return;
            }
    
            var animatorFilePath = AssetDatabase.GetAssetPath(animatorControllerTemplate);
            var dirArray = fileDirectory.Split('\\');
            var pathLastDirectoryName = dirArray[dirArray.Length - 1];
            var animatorExtension = Path.GetExtension(animatorFilePath);
    
            if (recursion)
            {
                var folders = Directory.GetDirectories(fileDirectory);
                foreach (var folder in folders)
                {
                    SingleFolderDispose(folder, animatorFilePath);
                }
            }
            else
            {
                SingleFolderDispose(fileDirectory, animatorFilePath);
            }
        }
    
    
        private void SingleFolderDispose(string folder, string animatorFilePath)
        {
            DirectoryInfo info = new DirectoryInfo(folder);
            string folderName = info.Name;
            var newAnimatorFilePath = Path.Combine(folder, folderName + Path.GetExtension(animatorFilePath));
            File.Copy(animatorFilePath, newAnimatorFilePath, true);
            AssetDatabase.Refresh();
            AnalyzeAnimController(folder, newAnimatorFilePath);
            AssetDatabase.Refresh();
            var obj = LoadFbx(folder, newAnimatorFilePath);
            PrefabUtility.SaveAsPrefabAsset(obj, string.Format("{0}/{1}.prefab", folder, folderName));
            DestroyImmediate(obj);
        }
    
        private GameObject LoadFbx(string folder, string animatorFilePath)
        {
            //找到fbx,找到当前目录下名字不带@符号的fbx 进行实例化
            FileInfo tempFile = null;
            DirectoryInfo folderDirectoryInfo = new DirectoryInfo(folder);
            var files = folderDirectoryInfo.GetFiles();
            foreach (var fileInfo in files)
            {
                if (!fileInfo.Name.Contains("@") && fileInfo.Name.Contains(".fbx") && !fileInfo.Name.Contains(".meta"))   //TODO:也可以根据floder名去找对应的fbx
                {
                    tempFile = fileInfo;
                    break;
                }
            }
            if (tempFile == null)
            {
                throw new Exception(string.Format("目录:{0} 没有找到不带@的fbx", folder));
            }
    
            var obj = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(GetAssetPath(tempFile.FullName))) as GameObject;
            //找到controller
    
            var controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(GetAssetPath(animatorFilePath));
            obj.GetComponent<Animator>().runtimeAnimatorController = controller;
            return obj;
        }
    
        private void AnalyzeAnimController(string floder, string controllerPath)
        {
            var assetPath = GetAssetPath(controllerPath);
            var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
            string animationFolder = Path.GetDirectoryName(assetPath) + "\\animations";
            animationFolder.Replace("\\", "/");
            //animatorController的Parameters不需要修改
            //遍历所有的layer
            for (int i = 0; i < animatorController.layers.Length; i++)
            {
                var layer = animatorController.layers[i];
                AnimatorStateMachine sm = layer.stateMachine;
                RecursionAnalyzeAnimatorStateMachine(sm, animationFolder);
            }
        }
    
        private string GetAssetPath(string fullPath)
        {
            var strs = fullPath.Split(new string[] { "Assets" }, StringSplitOptions.None);
            var assetPath = "Assets" + strs[strs.Length - 1];
            assetPath.Replace("\\", "/");
            return assetPath;
        }
    
        private void RecursionAnalyzeAnimatorStateMachine(AnimatorStateMachine stateMachine, string animationFlolder)
        {
            //遍历states
            for (int i = 0; i < stateMachine.states.Length; i++)
            {
                var animatorState = stateMachine.states[i];
                var motion = animatorState.state.motion;
                if (motion != null)
                {
                    if (motion is BlendTree)
                    {
                        BlendTree bt = motion as BlendTree;
    
                        ChildMotion[] childMotions = new ChildMotion[bt.children.Length];
    
    
                        for (int j = 0; j < bt.children.Length; j++)
                        {
                            var childMotion = bt.children[j];
                            var motionClip = GetAnimationClip(childMotion.motion.name, animationFlolder);
    
                            if (motionClip == null)
                            {
                                Debug.LogError("没有找到" + motion.name + "的动画控制器");
                            }
                            else
                            {
                                Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, childMotion.motion));   //根据名字找到对应的prefab 然后找出里面的动画文件加载
                                //childMotion.motion = (Motion)motionClip;
    
                                //var newChildMotion = new ChildMotion() { motion = motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                                //childMotion = newChildMotion;
    
                                childMotions[j] = new ChildMotion() { motion = (Motion)motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                            }
                        }
                        //bt.children = childMotions;
                        BlendTree newBt = new BlendTree()
                        {
                            blendParameter = bt.blendParameter,
                            blendParameterY = bt.blendParameterY,
                            blendType = bt.blendType,
                            hideFlags = bt.hideFlags,
                            maxThreshold = bt.maxThreshold,
                            minThreshold = bt.minThreshold,
                            name = bt.name,
                            useAutomaticThresholds = bt.useAutomaticThresholds,
                            children = childMotions,
                        };
                        animatorState.state.motion = newBt;
                    }
                    else
                    {
                        animatorState.state.motion = null;
                        var motionClip = GetAnimationClip(motion.name, animationFlolder);
                        if (motionClip == null)
                        {
                            Debug.LogError("没有找到" + motion.name + "的动画控制器");
                        }
                        else
                        {
                            animatorState.state.motion = (Motion)motionClip;
                            Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, motion));
                        }
                    }
                }
            }
            //遍历substatemachine
            for (int j = 0; j < stateMachine.stateMachines.Length; j++)
            {
                var stateMachines = stateMachine.stateMachines[j];
                RecursionAnalyzeAnimatorStateMachine(stateMachines.stateMachine, animationFlolder);
            }
        }
    
        private AnimationClip GetAnimationClip(string motionName, string animationFolder)
        {
            var motionNameExt = motionName.Substring(motionName.IndexOf("_"));
            DirectoryInfo directoryInfo = new DirectoryInfo(animationFolder);
            FileInfo tempFileInfo = null;
            var files = directoryInfo.GetFiles("*.FBX", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(motionNameExt + ".FBX"))    //有可能是Robert01_gun_jump_start  对应的Robert01@Robert01_gun_jump
                {
                    tempFileInfo = files[i];
                    break;
                }
            }
            if (tempFileInfo != null)
            {
                var datas = AssetDatabase.LoadAllAssetsAtPath(GetAssetPath(tempFileInfo.FullName));
                if (datas.Length == 0)
                {
                    Debug.Log(string.Format("Can't find clip in {0}", tempFileInfo.FullName));
                    return null;
                }
                foreach (var data in datas)
                {
                    if (!(data is AnimationClip))//如果不是动画文件则跳过
                        continue;
                    var newClip = data as AnimationClip;
                    return newClip;
                }
            }
            else
            {
                Debug.LogError("没有找到对应的动画FBX:" + motionName);
            }
            return null;
        }
    }
    
    

    打开弹框

    打开弹框可以用Unity内置的System.Windows.Forms.dll的api来打开,将其放在Plugins下,打开方法:

    public void OpenFile()
    {
        OpenFileDialog dialog = new OpenFileDialog();
        dialog.Filter = "exe files (*.exe)|*.exe";  //过滤文件类型
        dialog.InitialDirectory = "D:\\";  //定义打开的默认文件夹位置,可以在显示对话框之前设置好各种属性
        if (dialog.ShowDialog() == DialogResult.OK)
        {
            Debug.Log(dialog.FileName);
        }
    }
    

    纯代码创建控制器

    using System;
    using UnityEngine;
    using System.Collections;
    using System.IO;
    using System.Linq;
    using UnityEditor;
    using UnityEditor.Animations;
    using System.Collections.Generic;
    
    public class AnimatorTool : MonoBehaviour
    {
        private static List<AnimatorState> stateList = new List<AnimatorState>();
        /// <summary>
        /// 菜单方法,遍历文件夹创建Animation Controller
        /// </summary>
        [MenuItem("Tools/CreateAnimator")]
        static void CreateAnimationAssets()
        {
            string rootFolder = "Assets/Resources/Fbx/";
            if (!Directory.Exists(rootFolder))
            {
                Directory.CreateDirectory(rootFolder);
                return;
            }
            // 遍历目录,查找生成controller文件
            var folders = Directory.GetDirectories(rootFolder);
            foreach (var folder in folders)
            {
                DirectoryInfo info = new DirectoryInfo(folder);
                string folderName = info.Name;
                // 创建animationController文件
                AnimatorController aController =
                    AnimatorController.CreateAnimatorControllerAtPath(string.Format("{0}/animation.controller", folder));  //在对应目录生成AnimatorController文件
    
                //添加参数
                aController.AddParameter("run", AnimatorControllerParameterType.Bool);
                aController.AddParameter("attack01", AnimatorControllerParameterType.Bool);
    
                // 得到其layer
                var layer = aController.layers[0];//Base Layer
                // 绑定动画文件
                AddStateTranstion(string.Format("{0}/{1}_model.fbx", folder, folderName), layer);
                Debug.Log(string.Format("<color=yellow>{0}</color>", layer));
                // 创建预设
                GameObject go = LoadFbx(folderName);
                PrefabUtility.CreatePrefab(string.Format("{0}/{1}.prefab", folder, folderName), go);
                DestroyImmediate(go);
            }
    
        }
    
        /// <summary>
        /// 添加动画状态机状态
        /// </summary>
        /// <param name="path"></param>
        /// <param name="layer"></param>
        private static void AddStateTranstion(string path, AnimatorControllerLayer layer)
        {
            AnimatorStateMachine sm = layer.stateMachine;  //状态机
            // 根据动画文件读取它的AnimationClip对象
            var datas = AssetDatabase.LoadAllAssetsAtPath(path);
            if (datas.Length == 0)
            {
                Debug.Log(string.Format("Can't find clip in {0}", path));
                return;
            }
            /*
            //创建默认state  
            AnimatorState defaultState = sm.AddState("default", new Vector3(300, 0, 0));
            //defaultState.motion=  
            sm.defaultState = defaultState;
            AnimatorStateTransition defaultTransition = sm.AddAnyStateTransition(defaultState);
            defaultTransition.AddCondition(AnimatorConditionMode.If, 0, "default");
            */
            // 先添加一个默认的空状态
            var emptyState = sm.AddState("empty", new Vector3(500, 0, 0));
            sm.AddAnyStateTransition(emptyState);
    
            //遍历模型中包含的动画片段,将其加入状态机中
            foreach (var data in datas)
            {
                int index = 0;
                if (!(data is AnimationClip)) //如果不是动画文件则跳过
                    continue;
                var newClip = data as AnimationClip; //如果是的话则转化
    
                if (newClip.name.StartsWith("__"))
                    continue;
                // 取出动画名字,添加到state里面
                AnimatorState state = sm.AddState(newClip.name, new Vector3(500, sm.states.Length * 60, 0)); //将动画添加到动画控制器
                stateList.Add(state);
                if (state.name == "walk")
                {
                    sm.defaultState = state;   //将walk设置为默认动画
                }
                Debug.Log(string.Format("<color=red>{0}</color>", state));
                index++;
                state.motion = newClip; //设置动画状态指定到自己的动画文件
                // 把State添加在Layer里面
                sm.AddAnyStateTransition(state); //将动画状态连线到AnyState
            }
    
            AddTransition(sm, "walk", "run", 1);
            AddTransition(sm, "run", "walk", 0);
    
            AddTransition(sm, "walk", "attack01", 1);
            AddTransition(sm, "attack01", "walk", 0);
    
            AddSuMechie(sm, 2, path, layer, "sub2Machine");
        }
    
        static void AddSuMechie(AnimatorStateMachine machine, int index1, string path, AnimatorControllerLayer layer, string sunStateMachine)
        {
            创建子状态机  
            //for (int k = 1; k < index1; k++)
            //{
            //    AnimatorStateMachine sub2Machine = machine.AddStateMachine("sub2Machine", new Vector3(100, 300, 0));
            //}
            AnimatorStateMachine sub2Machine = machine.AddStateMachine(sunStateMachine, new Vector3(100, 300, 0));
    
            // 根据动画文件读取它的AnimationClip对象
            var datas = AssetDatabase.LoadAllAssetsAtPath(path);
            if (datas.Length == 0)
            {
                Debug.Log(string.Format("Can't find clip in {0}", path));
                return;
            }
            foreach (var data in datas)
            {
                int index = 0;
                if (!(data is AnimationClip))
                    continue;
                var newClip = data as AnimationClip;
    
                if (newClip.name.StartsWith("__"))
                    continue;
                // 取出动画名字,添加到state里面
                AnimatorState state = sub2Machine.AddState(newClip.name, new Vector3(500, sub2Machine.states.Length * 60, 0));
                stateList.Add(state);
                if (state.name == "walk")
                {
                    sub2Machine.defaultState = state;
                }
                Debug.Log(string.Format("<color=red>{0}</color>", state));
                index++;
                state.motion = newClip;
                // 把State添加在Layer里面
                sub2Machine.AddAnyStateTransition(state);
            }
        }
    
        /// <summary>
        /// 添加状态之间的连线
        /// </summary>
        /// <param name="stateM">状态</param>
        /// <param name="ani_name"></param>
        /// <param name="ani_des"></param>
        /// <param name="flag"></param>
        static void AddTransition(AnimatorStateMachine stateM, string ani_name, string ani_des, int flag)
        {
            foreach (var item in stateM.states)
            {
                if (item.state.name == ani_name)
                {
                    foreach (var des in stateM.states)
                    {
                        if (des.state.name == ani_des)
                        {
                            AnimatorStateTransition transition = item.state.AddTransition(des.state); //添加连线
                            transition.hasExitTime = true;
                            transition.exitTime = 0.8f;
                            if (flag == 1)
                                transition.AddCondition(AnimatorConditionMode.If, flag, ani_des); //添加连线状态
                            else
                            {
                                transition.AddCondition(AnimatorConditionMode.IfNot, flag, ani_name);
                            }
                        }
                    }
                }
            }
            Resources.UnloadUnusedAssets(); //卸载资源
        }
    
    
        /// <summary>
        /// 生成带动画控制器的对象
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static GameObject LoadFbx(string name)
        {
            var obj = Instantiate(Resources.Load(string.Format("Fbx/{0}/{0}_model", name))) as GameObject;
            obj.GetComponent<Animator>().runtimeAnimatorController =
                Resources.Load<RuntimeAnimatorController>(string.Format("fbx/{0}/animation", name));
            return obj;
        }
    }
    
    

    碰到的坑

    据反馈,新生成的动画控制器在关闭Unity之后,重新打开会发现新生成的Animaor出问题了,motion丢失,但测试下来直接将Clip赋给Motin没有问题,子状态机这种情况也没有问题,唯独BlendTree有问题,我对比新生成的Animator文件跟模板Animator文件对比发现FileID=0,也就是说BlendTree文件并没有保存下来,但BlendTree又不像动画Clip那样我们能直接看到,经过查看Animator文件的数据会发现BlendTree信息写在Animator中,也就是BT并没有保存下来,那么如何保存代码修改的Animation的BlendTree呢?我看到Unity论坛有人碰到类似的问题,https://forum.unity.com/threads/how-to-save-the-animation-blend-tree-created-by-script.480320/,感谢题主!解决方法就是:
    在这里插入图片描述
    经过这段代码,会将BlendTree的信息写入到Animator中,
    在这里插入图片描述
    这样问题就解决了!

    工程下载

    https://github.com/dingxiaowei/AnimatorGenerator

    更多精品教程

    http://dingxiaowei.cn 拷贝到浏览器访问

    展开全文
  • 动画数据生成系统、动画数据生成方法和动画数据生成程序以及信息记录介质.zip
  • 动画生成器0.1

    2013-05-15 20:52:43
    属于动画前期使用的动画分镜生成器。非常的方便和好用
  • Flash调用外部视频片段生成Flash格式动画,这个在现在相当普遍了,比如FLV格式就是这种格式,当然也不全是调用外部视频生成,有的是调用后直接播放,比如那些Flash播放器就是这样子,不过本例子是调用视频后,生成了...
  • 基于C++ GDI+技术实现多张图片合并生成Gif动画格式图片的方案。 【1】支持bmp、jpg、tif、png等格式图片的合并 【2】支持图片尺寸、循环次数、图片持续时间的设定

    在写这篇文章前,首先吐槽一下:今天因工作需要,研究在C++中将多张图片合并生成Gif动画格式的方法。在网上看了很多这类的文章,没一个靠谱的,唯一靠谱的是使用C#实现的GIf编解码的方法(NGif http://www.codeproject.com/Articles/11505/NGif-Animated-GIF-Encoder-for-NET),它是使用流的方法进行编解码,满足要求,但将其转码成C++很麻烦,所以索性自己编写了一套在C++中将多张图片合并成Gif格式的方法,现在呈现给大家,以弥补C++模块的空缺。

    【1】Gif格式简介

    GIF(GraphicsInterchange Format)的原义是“图像互换格式”,是CompuServe公司在 1987年开发的图像文件格式。GIF文件的数据,是一种基于LZW算法的连续色调的无损压缩格式。其压缩率一般在50%左右,它不属于任何应用程序。目前几乎所有相关软件都支持它,公共领域有大量的软件在使用GIF图像文件。GIF图像文件的数据是经过压缩的,而且是采用了可变长度等压缩算法。GIF格式的另一个特点是其在一个GIF文件中可以存多幅彩色图像,如果把存于一个文件中的多幅图像数据逐幅读出并显示到屏幕上,就可构成一种最简单的动画。

    GIF格式自1987年由CompuServe公司引入后,因其体积小而成像相对清晰,特别适合于初期慢速的互联网,而从此大受欢迎。它采用无损压缩技术,只要图像不多于256色,则可既减少文件的大小,又保持成像的质量。(当然,现在也存在一些hack技术,在一定的条件下克服256色的限制,具体参见真彩色)然而,256色的限制大大局限了GIF文件的应用范围,如彩色相机等。(当然采用无损压缩技术的彩色相机照片亦不适合通过网络传输。)另一方面,在高彩图片上有着不俗表现的JPG格式却在简单的折线上效果难以差强人意。因此GIF格式普遍适用于图表,按钮等等只需少量颜色的图像(如黑白照片)

    【2】图像合并成GIf的方法

    使用 GDI+库即可完成多张图片合并生成Gif动画图片的功能,主要涉及:
    SaveAdd 方法,在原有的图像上添加一张新的图像:
    statue = m_pImage->SaveAdd(m_pBitMapVec[ix], &encoderParams);
    通过 EncoderParameters参数设置图像编码的信息,如第一张图像需要设置为 EncoderValueMultiFrame,其余的图像需要设置为 EncoderValueFrameDimensionTime
    GUID						gifGUID;
    	Gdiplus::EncoderParameters	encoderParams;  
    	GetEncoderClsid(L"image/gif", &gifGUID);
    	
    	encoderParams.Count = 1;
    	encoderParams.Parameter[0].Guid			= Gdiplus::EncoderSaveFlag;
    	encoderParams.Parameter[0].NumberOfValues = 1;
    	encoderParams.Parameter[0].Type			=  Gdiplus::EncoderParameterValueTypeLong;//第一帧需要设置为MultiFrame
    
    	long firstValue						= Gdiplus::EncoderValueMultiFrame;
    	encoderParams.Parameter[0].Value	=  &firstValue;	
    	
    	m_pImage->Save(m_pStrSavePath->c_str(), &gifGUID, &encoderParams);
    
    	//3.0 保存剩余的图像
    	size_t size = m_pBitMapVec.size();
    	firstValue						= Gdiplus::EncoderValueFrameDimensionTime;
    	encoderParams.Parameter[0].Value=  &firstValue;	
    	for (size_t ix = 0; ix <size; ix++)
    	{
    		statue = m_pImage->SaveAdd(m_pBitMapVec[ix], &encoderParams);
    	}
     

    通过SetPropertyItem函数设置Gif动画图像的循环的次数(PropertyTagLoopCount)及每个图像持续的时间(PropertyTagFrameDelay):

    	Gdiplus::PropertyItem propertyItem;
    
    	//1.0 设置动画循环的次数	
    	short sValue		= m_repeatNum; //0 -- 不限次数
    	propertyItem.id		= PropertyTagLoopCount; 
    	propertyItem.length = 2; //字节数
    	propertyItem.type	= PropertyTagTypeShort;
    	propertyItem.value	= &sValue; 
    	m_pImage->SetPropertyItem(&propertyItem);
    
    	//2.0 设置每副图像延迟的时间,从第一副开始
    	long lImageNum = 1 + m_pBitMapVec.size();
    	long *plValue = new long[lImageNum];
    	for (int ix=0; ix<lImageNum; ix++)
    	{
    		plValue[ix] = m_delayTime; //可以设置不一样
    	}
    	propertyItem.id		= PropertyTagFrameDelay; 
    	propertyItem.length = 4 * lImageNum;//字节数
    	propertyItem.type	= PropertyTagTypeLong;
    	propertyItem.value	= plValue; //不限次数
    	m_pImage->SetPropertyItem(&propertyItem);
    
    	delete []plValue;
    	plValue = NULL;
     

    【3】完整代码如下

    类声明:
    //================================================================================
    /// @brief 动态Gif图像的编码
    ///
    /// 通过动态添加多幅图像将其合并成Gif动画图像
    //================================================================================
    class CGifEncoder
    {
    public:
    	CGifEncoder();
    	~CGifEncoder();
    
    public:	
    	//=================================StartEncoder()=================================
    	/// @brief 开始gif编码
    	///
    	/// @param [in] saveFilePath gif图像保存的全路径
    	///
    	/// @return 成功返回true
    	//================================================================================
    	bool StartEncoder(wstring &saveFilePath); 	
    	//===================================AddFrame()===================================
    	/// @brief 添加图像
    	///
    	/// @param [in] im  Image对象
    	///
    	/// @return 成功返回true
    	//================================================================================
    	bool AddFrame(Gdiplus::Image *pImage);
    	//===================================AddFrame()===================================
    	/// @brief 添加图像
    	///
    	/// @param [in] framePath 图像的全路径
    	///
    	/// @return  成功返回true
    	//================================================================================
    	bool AddFrame(wstring &framePath);
    	//================================FinishEncoder()===============================
    	/// @brief 结束gif的编码
    	///
    	/// @return  成功返回true
    	//================================================================================
    	bool FinishEncoder();
    	//=================================SetDelayTime()=================================
    	/// @brief 设置两幅图像切换的时间间隔
    	///
    	/// @param [in] ms 时间,以毫秒为单位
    	///
    	/// @return 无
    	//================================================================================
    	void SetDelayTime(int ms);
    	//=================================SetRepeatNum()=================================
    	/// @brief 设置gif动画播放的次数
    	///
    	/// @param [in] num 次数,0表示无限次
    	///
    	/// @return 无
    	//================================================================================
    	void SetRepeatNum(int num);	
    	//=================================SetFrameRate()=================================
    	/// @brief 设置图像的帧率
    	///
    	/// @param [in] fps 帧率,每秒播放图像的数目
    	///
    	/// @return 无
    	//================================================================================
    	void SetFrameRate(float fps);
    	//=================================SetFrameSize()=================================
    	/// @brief 设置图像的尺寸
    	///
    	/// @param [in] width  图像的宽度
    	/// @param [in] height 图像的高度
    	///
    	/// @return 无
    	//================================================================================
    	void SetFrameSize(int width, int height);
    
    private:
    	void SetImagePropertyItem();
    	bool GetEncoderClsid(const WCHAR* format, CLSID* pClsid);	
    	
    private:
    	int					m_width; 
    	int					m_height;
    	int					m_repeatNum; 
    	int					m_delayTime; 
    	bool				m_started;
    	bool				m_haveFrame;
    
    	wstring				*m_pStrSavePath;
    	Gdiplus::Bitmap		*m_pImage;
    	vector<Gdiplus::Bitmap *> m_pBitMapVec;
    };
    

    类实现:
    CGifEncoder::CGifEncoder()
    {
    	m_started	= false;
    	m_width		= 320;
    	m_height	= 240;
    	m_delayTime = 100;
    	m_repeatNum = 0;
    	m_haveFrame = false;
    	m_pStrSavePath = NULL;
    	m_pImage	   = NULL;
    }
    
    CGifEncoder::~CGifEncoder()
    {
    	if (NULL != m_pStrSavePath)
    	{
    		delete m_pStrSavePath;
    	}
    	
    	if (NULL != m_pImage)
    	{
    		delete m_pImage;
    	}	
    
    	size_t size = m_pBitMapVec.size();
    	for (size_t ix=0; ix<size; ix++)
    	{
    		delete m_pBitMapVec[ix];
    		m_pBitMapVec[ix] = NULL;
    	}
    }
    
    bool CGifEncoder::StartEncoder( wstring &saveFilePath )
    {
    	bool flag = true;
    
    	if ( NULL != m_pStrSavePath)
    	{
    		delete m_pStrSavePath;
    		m_pStrSavePath = NULL;
    	}
    
    	m_pStrSavePath = new wstring(saveFilePath);
    	m_started = true;
    
    	return(flag);
    }
    
    bool CGifEncoder::AddFrame( Gdiplus::Image *pImage )
    {
    	if (!m_started || NULL == pImage)
    	{
    		return(false);
    	}
    
    	bool flag = true;
    	if (!m_haveFrame)
    	{
    		m_pImage = new Gdiplus::Bitmap(m_width, m_height);
    		Gdiplus::Graphics g(m_pImage);
    		g.DrawImage(pImage, 0, 0, m_width, m_height);
    
    		m_haveFrame = true;
    		return(true);
    	}
    
    	Gdiplus::Bitmap *pBitMap = new Gdiplus::Bitmap(m_width, m_height);
    	Gdiplus::Graphics g(pBitMap);
    	g.DrawImage(pImage, 0, 0, m_width, m_height);
    	m_pBitMapVec.push_back(pBitMap);
    	
    	return(flag);
    }
    
    bool CGifEncoder::AddFrame( wstring &framePath )
    {
    	if (!m_started)
    	{
    		return(false);
    	}
    
    	bool flag = true;
    	Gdiplus::Status statue;
    	if (!m_haveFrame)
    	{
    		m_pImage = new Gdiplus::Bitmap(m_width, m_height);
    		Gdiplus::Graphics g(m_pImage);
    
    		Gdiplus::Bitmap bitmap(framePath.c_str());
    		g.DrawImage(&bitmap, 0, 0, m_width, m_height);
    
    		m_haveFrame = true;
    		return(true);
    	}	
    
    	Gdiplus::Bitmap		*pBitMap = new Gdiplus::Bitmap(m_width, m_height);
    	Gdiplus::Graphics	g(pBitMap);
    
    	Gdiplus::Bitmap bitmap(framePath.c_str());
    	statue = g.DrawImage(&bitmap, 0, 0, m_width, m_height);
    
    	m_pBitMapVec.push_back(pBitMap);
    
    	return(flag);
    }
    
    bool CGifEncoder::FinishEncoder()
    {
    	if (!m_started || !m_haveFrame)
    	{
    		return(false);
    	}
    
    	bool	flag = true;
    	Gdiplus::Status statue;
    
    	//1.0 设置图像的属性
    	SetImagePropertyItem();
    
    	//2.0 保存第一副图像
    	GUID						gifGUID;
    	Gdiplus::EncoderParameters	encoderParams;  
    	GetEncoderClsid(L"image/gif", &gifGUID);
    	
    	encoderParams.Count = 1;
    	encoderParams.Parameter[0].Guid			= Gdiplus::EncoderSaveFlag;
    	encoderParams.Parameter[0].NumberOfValues = 1;
    	encoderParams.Parameter[0].Type			=  Gdiplus::EncoderParameterValueTypeLong;//第一帧需要设置为MultiFrame
    
    	long firstValue						= Gdiplus::EncoderValueMultiFrame;
    	encoderParams.Parameter[0].Value	=  &firstValue;	
    	
    	m_pImage->Save(m_pStrSavePath->c_str(), &gifGUID, &encoderParams);
    
    	//3.0 保存剩余的图像
    	size_t size = m_pBitMapVec.size();
    	firstValue						= Gdiplus::EncoderValueFrameDimensionTime;
    	encoderParams.Parameter[0].Value=  &firstValue;	
    	for (size_t ix = 0; ix <size; ix++)
    	{
    		statue = m_pImage->SaveAdd(m_pBitMapVec[ix], &encoderParams);
    	}
    
    	m_started = false;
    	m_haveFrame = false;
    	return(flag);
    }
    
    void CGifEncoder::SetDelayTime( int ms )
    {
    	if (ms > 0)
    	{
    		m_delayTime = ms / 10.0f;
    	}	
    }
    
    void CGifEncoder::SetRepeatNum( int num )
    {
    	if (num >= 0) 
    	{
    		m_repeatNum = num;
    	}
    }
    
    void CGifEncoder::SetFrameRate( float fps )
    {
    	if (fps > 0) 
    	{
    		m_delayTime = 100.0f / fps;
    	}
    }
    
    void CGifEncoder::SetFrameSize( int width, int height )
    {
    	if (!m_haveFrame) 
    	{
    		m_width  = width;
    		m_height = height;
    
    		if (m_width < 1) 
    			m_width = 320;
    
    		if (m_height < 1) 
    			height = 240;
    	}	
    }
    
    void CGifEncoder::SetImagePropertyItem()
    {
    	if (!m_started || NULL == m_pImage)
    	{
    		return;
    	}
    
    	Gdiplus::PropertyItem propertyItem;
    
    	//1.0 设置动画循环的次数	
    	short sValue		= m_repeatNum; //0 -- 不限次数
    	propertyItem.id		= PropertyTagLoopCount; 
    	propertyItem.length = 2; //字节数
    	propertyItem.type	= PropertyTagTypeShort;
    	propertyItem.value	= &sValue; 
    	m_pImage->SetPropertyItem(&propertyItem);
    
    	//2.0 设置每副图像延迟的时间,从第一副开始
    	long lImageNum = 1 + m_pBitMapVec.size();
    	long *plValue = new long[lImageNum];
    	for (int ix=0; ix<lImageNum; ix++)
    	{
    		plValue[ix] = m_delayTime; //可以设置不一样
    	}
    	propertyItem.id		= PropertyTagFrameDelay; 
    	propertyItem.length = 4 * lImageNum;//字节数
    	propertyItem.type	= PropertyTagTypeLong;
    	propertyItem.value	= plValue; //不限次数
    	m_pImage->SetPropertyItem(&propertyItem);
    
    	delete []plValue;
    	plValue = NULL;
    }
    
    bool CGifEncoder::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
    {
    	UINT num = 0, size = 0;
    
    	Gdiplus::GetImageEncodersSize(&num, &size);
    	if(size == 0)
    		return false;  // Failure
    
    	Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
    
    	Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
    	bool found = false;
    	for (UINT ix = 0; !found && ix < num; ++ix) 
    	{
    		if (_wcsicmp(pImageCodecInfo[ix].MimeType, format) == 0) 
    		{
    			*pClsid = pImageCodecInfo[ix].Clsid;
    			found = true;
    			break;
    		}
    	}
    
    	free(pImageCodecInfo);
    	return found;
    }
     

    【4】测试代码

    	CGifEncoder gifEncoder;
    	gifEncoder.SetFrameSize(200, 200);
    	gifEncoder.SetDelayTime(500);
    	gifEncoder.StartEncoder(wstring(L"C:\\1.gif"));
    	gifEncoder.AddFrame(wstring(L"C:\\1.png"));
    	gifEncoder.AddFrame(wstring(L"C:\\2.jpg"));
    	gifEncoder.AddFrame(wstring(L"C:\\3.jpg"));
    	gifEncoder.AddFrame(wstring(L"C:\\4.jpg"));
    	gifEncoder.FinishEncoder();

     

    展开全文
  • 利用CImage类读取png格式的图片生成精灵动画 /* 写此代码是为了教学所用,因为在网络上找了很久都没有找到一个利用CImage类来实现精灵动画的源码。图片使用的是网络资源,如有侵权,请联系成都信息工程学院计算机...
  • GIF动图色彩越简单相应生成的字符雨动画效果会越好 GIF动图色彩越简单相应生成的字符雨动画效果会越好 GIF动图色彩越简单相应生成的字符雨动画效果会越好 纯原创代码,如有问题,欢迎反应与指教。
  • vc gif 动画生成

    千次阅读 2014-03-05 17:35:33
    vc gif 动画生成 最近因为项目需求,需要制作一个程序来将用户指定的图片做成gif循环播放。 网上找资料发现,mfc、win32里头没有相应的api可以使用, 所以只好到网上找别人的代码喽。 最后我是用CxImage来实现的...

                                 vc gif 动画生成

    最近因为项目需求,需要制作一个程序来将用户指定的图片做成gif循环播放。

    网上找资料发现,mfc、win32里头没有相应的api可以使用,

    所以只好到网上找别人的代码喽。

    最后我是用CxImage来实现的。

     

    不得不说CxImage是个好东西,支持多种格式jpg、bmp、png、gif等等。

     

    下载下来的是用vc6做的工程,我用的vs2008,没想到直接打开就能转化成功,几乎没什么修改选项。

     

    gif对256色直接支持,24位的就要用到调试板了。

     

    还好CxImage里头就有Quantize.cpp这个可以生成调色板,什么算法没研究,直接用了。

     

    代码大概是这个过程:

    1 利用CxImage可以直接加载指定目录下的图片,可以进行缩放等处理

    2 利用Quantize.cpp里的q.ProcessImage(newimage->GetDIB());q.SetColorTable(ppal);就可以生成调色板了。

    3 利用CxImage可以把2生成的调试板设置进去,可以设置帧延迟

    4 利用CxImageGIF就可以设置循环方式

    5 利用CxIOFile可以写gif文件了。

     

    原来CxImage里头的api是一次性将多张图片写进去的,如果你一次要加载大量图片进行处理一次写进去,显然会用大量内存,

    所以我直接将CxImageGIF::Encode拆分成第一张和后续图片两个函数,这样就可以一张一张处理,这个可以开一个线程在后面慢慢算。

     

    相关代码片段都可以再cximage里头或者它的demo找到直接或间接的示例,自己在测试下就ok了。

     

     

    展开全文
  • H5 Canvas水墨画生成动画特效是一款跟随鼠标移动水墨图形生成特效,水墨风格的树枝动画特效。
  • 华芯飞开关机动画生成工具V1.0,可以制作MP4的启动画面的原厂程序,但是只支持320x240的图片格式。我费了好大劲也没有改成400x240的图片格式,如果有谁能帮我改成支持400x240的程序,请上传上来或用邮箱发给我(如果...
  • MATLAB 动画生成gif图片

    2010-04-29 19:44:35
    MATLAB生成gif的小程序,解决MATLAB不能生成gif图片的缺点
  • Python生成截图选餐GIF动画

    万次阅读 多人点赞 2021-07-26 19:21:52
    之前还看到什么截图选头像之类的动图,那类通过图片生成的动图都比较简单,通过文中提到的Imagine的动画作坊工具就可以做。所以本文只演示如何生成文字动图。 python生成文字动图 下面我们一步步来完成这个操作: ...

    大家好,我是小小明。

    之前群里有小伙伴问今天中午该吃什么,然后另一位小伙伴发了一张下面的动图:

    截图吃饭

    我个人觉得还挺有意思的,截图还真像抽奖一样随机选一个菜名。考虑到这张动图中的菜名候选并不见得都是我们能够吃的菜。我们可以用python根据菜名列表生成这样的动图玩玩。

    之前还看到什么截图选头像之类的动图,那类通过图片生成的动图都比较简单,通过文中提到的Imagine的动画作坊工具就可以做。所以本文只演示如何生成文字动图。

    python生成文字动图

    下面我们一步步来完成这个操作:

    下载表情图片到本地

    为了分析这种表情图片,第一步需要先下载下来,但是对于微信的表情动图,经过测试还真没法直接下载下来。

    虽然通过文件监控工具分析出,gif表情动图存储位置在C:\Users\ASUS\Documents\WeChat Files\你的微信ID\FileStorage\CustomEmotion\xx\xxxx位置,但是却无法用图片工具查看。用winhex分析二进制得到了V1MMWX这样的文件头,说明微信对表情都进行了一定程度的加密。虽然可以解密,但这样大动干戈未免过于麻烦。

    后面终于想到了一个简单的方案,那就是把向你有权限登录后台的公众号发送这个表情,再去公众号后台下载:

    image-20210726163537948

    微信发送的动图都是存储为自己特有V1MMWX加密格式,可能是为了使用自己独创的压缩算法有更大的压缩比吧。那说明我们想直接看本地微信存储的gif动图,只能自行开发专门针对这种微信格式的解码器了。

    分析动图

    下面我使用小工具Imagine,并使用动画作坊打开:

    image-20210726163518497

    可以看到这张动图由22张文字图片组成,帧切换时间为20毫秒。

    生成单张图片

    分析完成我们考虑用PIL库来生成单张图片,如果还没有安装该库的童鞋,使用以下命令安装该库:

    pip install pillow
    

    下面选择了用蓝底做背景。我们先来绘制中间的菜名文字:

    from PIL import Image, ImageFont, ImageDraw
    
    
    text = "珍珠土豆焖牛腩"
    size = 320
    fontsize = (size-20)//len(text)
    im = Image.new(mode='RGB', size=(size, size), color="lightblue")
    
    draw = ImageDraw.Draw(im=im)
    draw.text(xy=(10, (size-fontsize*1.5)/2),
              text=text, fill=0,
              font=ImageFont.truetype('msyh.ttc', size=fontsize))
    im
    

    image-20210726172326328

    由于菜品的名字文字个数不一致,为了都能填满整图,作了自动文字大小调整处理。

    字体我选择了微软雅黑,当然微软雅黑也有三种子字体,可以通过系统字体安装目录查看字体文件的属性从而知道字体对应的文件名:

    image-20210726164518133

    下方带阴影的的文字生成起来会麻烦一些,我的思路是先绘制纯黑的文字,在绘制带黑色边缘白色填充的文字向上偏移几个单位:

    def text_border(text, x, y, font, shadowcolor, fillcolor):
        draw.text((x - 1, y), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y), text, font=font, fill=shadowcolor)
        draw.text((x, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x, y + 1), text, font=font, fill=shadowcolor)
    
        draw.text((x - 1, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x - 1, y + 1), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y + 1), text, font=font, fill=shadowcolor)
    
        draw.text((x, y), text, font=font, fill=fillcolor)
    
    
    bottomtext = "不知道吃什么?截图吃饭"
    bottom_fontsize = 27
    bottom_font = ImageFont.truetype('STHUPO.TTF', size=bottom_fontsize)
    x, y = (size-bottom_fontsize*len(bottomtext))/2, size-bottom_fontsize*1.2
    draw.text(xy=(x, y), text=bottomtext,
              fill=0, font=bottom_font)
    text_border(bottomtext, x, y-4,
                bottom_font, 0, (255, 255, 255))
    im
    

    image-20210726172847077

    上述代码选择了华文琥珀作为字体,个人用来绘制文字边框的方法比较简单粗暴,如果有更好的办法,欢迎留言交流。

    考虑到后续图片发送到微信上显示都很小,干脆现在就压缩一下像素大小:

    im.thumbnail((128, 128))
    im
    

    image-20210726172948959

    下面我们封装一下生成代码,方便后续调用:

    from PIL import Image, ImageFont, ImageDraw
    
    
    def text_img(text, bgcolor="lightblue", bottomtext="不知道吃什么?截图吃饭", size=360, result_size=(128, 128)):
        def text_border(text, x, y, font, shadowcolor, fillcolor):
            draw.text((x - 1, y), text, font=font, fill=shadowcolor)
            draw.text((x + 1, y), text, font=font, fill=shadowcolor)
            draw.text((x, y - 1), text, font=font, fill=shadowcolor)
            draw.text((x, y + 1), text, font=font, fill=shadowcolor)
    
            draw.text((x - 1, y - 1), text, font=font, fill=shadowcolor)
            draw.text((x + 1, y - 1), text, font=font, fill=shadowcolor)
            draw.text((x - 1, y + 1), text, font=font, fill=shadowcolor)
            draw.text((x + 1, y + 1), text, font=font, fill=shadowcolor)
    
            draw.text((x, y), text, font=font, fill=fillcolor)
    
        im = Image.new(mode='RGB', size=(size, size), color=bgcolor)
        draw = ImageDraw.Draw(im=im)
        fontsize = (size-20)//len(text)
        draw.text(xy=(10, (size-fontsize*1.5)/2),
                  text=text, fill=0,
                  font=ImageFont.truetype('msyh.ttc', size=fontsize))
        bottom_fontsize = (size-20)//len(bottomtext)
        bottom_font = ImageFont.truetype('STHUPO.TTF', size=bottom_fontsize)
        x, y = (size-bottom_fontsize*len(bottomtext))/2, size-bottom_fontsize*1.2
        draw.text(xy=(x, y), text=bottomtext,
                  fill=0, font=bottom_font)
        text_border(bottomtext, x, y-4,
                    bottom_font, 0, (255, 255, 255))
        im.thumbnail(result_size)
        return im
    

    测试一下:

    text_img("鱼香茄子")
    

    image-20210726174000710

    ok,现在我们就已经能够给任何菜品生成图片了。但是菜品的名字哪里来呢?我找到了一个网站,下面考虑爬一下它:

    爬取菜品数据

    网址是:https://m.meishij.net/caipu/

    这个网站结果非常简单,一个简单的xpath即可获取到所有的菜品名称:

    image-20210726174726258

    下面开始下载:

    from lxml import etree
    import requests
    
    req = requests.get("https://m.meishij.net/caipu/")
    
    html = etree.HTML(req.text)
    menu = html.xpath("//dl[@class='recipe_list']//a/text()")
    menu = list(set([_.strip(".") for _ in menu]))
    print(len(menu), menu[:10], menu[-10:])
    
    3744 ['排骨藕汤', '芋圆', '海鲜汤', '凉拌杏鲍菇', '三汁焖锅', '奶香玉米汁', '炒豆角', '茄子酱', '芒果糯米糍', '馒头'] ['清蒸茄子', '西兰花炒鸡', '老式蛋糕', '排骨年糕', '清炒丝瓜', '芋头蒸排骨', '木耳炒肉', '蚝油油麦菜', '麻辣鸡块', '荷叶饼']
    

    有了这些菜名,我们已经可以用来生成动图了。不过为了以后还能够学做菜,我们可以将菜名保存起来,要学做菜的时候呢打开网页:https://so.meishi.cc/?q=菜名,进行搜索。

    保存菜名:

    with open("meau.csv", "w", encoding="u8") as f:
        f.write("菜名\n")
        for row in menu:
            f.write(row)
            f.write("\n")
    

    下面我们开始生成菜名动图:

    生成菜名动图

    3767多个菜名毕竟是太多,我们可以随意取30个菜名来生成动图:

    import random
    
    gif_list = random.choices(menu, k=30)
    print(gif_list)
    
    ['蒸水蛋', '肉桂卷', '凉瓜炒蛋', '芝士焗红薯', '香蕉酥', '酸奶慕斯', '鸡蛋肠粉', '红油肚丝', '玉米鸡蛋饼', '酸辣豆腐汤', '萝卜炖牛腩', '苦瓜排骨汤', '腐竹拌芹菜', '西红柿炒土', '蒜蓉蒸茄子', '豆沙面包', '蘑菇炒肉', '清炒莲藕', '黑椒牛肉粒', '南瓜煎饼', '炒黄瓜', '杂粮馒头', '桃山皮月饼', '葱爆肉', '小炒牛肉', '豆瓣鲫鱼', '虾仁烩豆腐', '素馅饺子', '凉拌黄瓜', '砂锅鱼头']
    

    PS:还是自己选好菜名,写死列表更好😅

    import imageio
    
    frames = [text_img(text) for text in gif_list]
    imageio.mimsave("meau.gif", frames, 'GIF', duration=0.02)
    

    生成结果:

    meau-1627295603332

    根据菜名列表生成动图的完整代码

    import imageio
    from PIL import Image, ImageFont, ImageDraw
    
    
    def text_img(text, bgcolor="lightblue", bottomtext="不知道吃什么?截图吃饭", size=360, result_size=(128, 128)):
        def text_border(text, x, y, font, shadowcolor, fillcolor):
            draw.text((x - 1, y), text, font=font, fill=shadowcolor)
            draw.text((x + 1, y), text, font=font, fill=shadowcolor)
            draw.text((x, y - 1), text, font=font, fill=shadowcolor)
            draw.text((x, y + 1), text, font=font, fill=shadowcolor)
    
            draw.text((x - 1, y - 1), text, font=font, fill=shadowcolor)
            draw.text((x + 1, y - 1), text, font=font, fill=shadowcolor)
            draw.text((x - 1, y + 1), text, font=font, fill=shadowcolor)
            draw.text((x + 1, y + 1), text, font=font, fill=shadowcolor)
    
            draw.text((x, y), text, font=font, fill=fillcolor)
    
        im = Image.new(mode='RGB', size=(size, size), color=bgcolor)
        draw = ImageDraw.Draw(im=im)
        fontsize = (size-20)//len(text)
        draw.text(xy=(10, (size-fontsize*1.5)/2),
                  text=text, fill=0,
                  font=ImageFont.truetype('msyh.ttc', size=fontsize))
        bottom_fontsize = (size-20)//len(bottomtext)
        bottom_font = ImageFont.truetype('STHUPO.TTF', size=bottom_fontsize)
        x, y = (size-bottom_fontsize*len(bottomtext))/2, size-bottom_fontsize*1.2
        draw.text(xy=(x, y), text=bottomtext,
                  fill=0, font=bottom_font)
        text_border(bottomtext, x, y-4,
                    bottom_font, 0, (255, 255, 255))
        im.thumbnail(result_size)
        return im
    
    
    def save_meau_gif(savename, meau):
        frames = [text_img(text) for text in meau]
        imageio.mimsave(savename, frames, 'GIF', duration=0.02)
    

    使用示例:

    meau = [
        "荷叶糯米鸡", "烤羊肉", "黑椒牛排", "家常大盘鸡", "蒜泥豆角",
        "洋葱炒牛肉", "丝瓜炒鸡蛋", "平菇炒鸡蛋", "鸡刨豆腐", "芙蓉鲜蔬汤",
        "炒西葫芦", "茄子豆角", "滑蛋牛肉", "香菇青菜", "地三鲜",
        "酱烧杏鲍菇", "腐乳鸡翅", "醋溜藕片", "椰子炖鸡", "香菇烧豆腐",
        "咖喱鸡腿饭", "鸡汁土豆泥", "茄子炖土豆", "炒乌冬面", "咖喱土豆鸡",
        "上汤娃娃菜", "蒜蓉蒸茄子", "芝士焗红薯", "栗子黄焖鸡", "丝瓜豆腐汤",
    ]
    save_meau_gif("meau.gif", meau)
    

    生成结果:

    meau

    自从我们的动图就生成完毕啦!不知道吃啥的时候都可以拿出来截图玩玩~🐶

    😆祝大家选餐愉快~

    PIL操作gif的其他操作

    其实用专门动图处理软件就可以操作,下面还是补充一下,python的操作API记录一下:

    Gif拆分

    比如我们拆分一下这张图:

    功夫熊

    from PIL import Image, ImageSequence
    
    img = Image.open('功夫熊.gif')
    for i, f in enumerate(ImageSequence.Iterator(img), 1):
        f.save(f'拆分/功夫熊-{i}.png')
    

    拆分结果:

    image-20210726191539826

    GIF倒放

    下面我们再将上面这张动图倒放一下:

    from PIL import Image, ImageSequence
    import imageio
    
    im = Image.open('功夫熊.gif')
    sequence = [f.copy() for f in ImageSequence.Iterator(im)]
    sequence.reverse()  # 将列表中的帧通过reverse()函数进行倒序
    sequence[0].save('倒放功夫熊.gif', save_all=True, append_images=sequence[1:])
    

    倒放功夫熊

    动作比较滑稽😆~

    展开全文
  • AS3生成GIF动画

    2010-02-16 16:28:26
    AS3生成GIF动画,用于在线生成GIF动画,别人写的代码。
  • c#图片生成GIF动画图片

    热门讨论 2010-08-27 11:24:14
    c#图片生成GIF动画图片c#图片生成GIF动画图片c#图片生成GIF动画图片
  • 主要利用blend制作动画生成WPF应用程序
  • 此脚本是一个 GUI,用于从图像生成动画 gif。 可以设置图像之间的时间。 以及从原始图像设置 gif 大小的压缩率。 可用于从从中获取不同图像的图形生成动画
  • Matlab 动画及Gif生成

    千次阅读 2016-12-02 12:14:43
    Matlab动画及Gif图片生成
  • Android应用内截动画生成Gif

    千次阅读 2018-01-26 14:12:20
    最近在练习写动画和自定义View,打算可以在应用里写一个功能一键生成动画播放的gif,就像bilibili播放视频时的长按录gif那样,省去用AndroidStudio录屏然后mp4转gif的麻烦了。在网上找了一圈大部分用的都是一个叫...
  • 基于Html5 Canvas的帧动画生成

    千次阅读 2016-05-13 17:11:22
    基于Html5 Canvas的帧动画生成
  • 本篇文章是对PHP生成GIF动画的方法进行了详细的分析介绍,需要的朋友参考下
  • tween.js是一款可生成平滑动画效果的js动画库。tween.js允许你以平滑的方式修改元素的属性值。它可以通过设置生成各种类似CSS3的动画效果。
  • 图片生成gif动画libGif.rar 源代码
  • SWFtofla动画源码生成

    2007-12-15 14:45:25
    SWFtofla是一款转换软件,用于将动画格式(.swf)转换成.fla源码格式,很好用
  • 用python演示一键生成动画技术,智力题《你是哪班的》。包含全部资源,直接可以运行。须安装几个第三方库。 参考教程地址: https://blog.csdn.net/xiaorang/article/details/106169578
  • 3d 动画藤蔓生成插件下载,可以生成迫真的藤蔓植物
  • C#生成Gif源码,使用c#生成gif动画验证码及给gif动画加上水印代码
  • 该Demo能够使用Object-c来生成GIF动画文件,并且生成动画文件能够播放!代码不多但是效率很高 生成的GIF文件在Docment文件下,可以去看下 这个Demo可以根据各位的需求修改
  • 通过引用Texture packerGUI生成的plist批量生成精灵动画
  • Canvas 生成交互动画

    千次阅读 2013-11-24 11:37:22
    今天介绍的是一个HTML5交互动画效果,难以置信。HTML5虽说还有很多东西在改进,但现在所能实现的 效果的程度我想是诸位很难想象得到的,实在是发展得太快了。     查看详情

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 160,413
精华内容 64,165
关键字:

动画生成的格式是什么