国庆了,回家了。时刻还是要吃一颗学习的心,在家了也要抽出时间好好学习一下。之前MOMO一直没研究过Unity2D,今天研究了一下,还是把自己今天的研究笔记记录下来。现在网络上已经有很多Unity2D的技术分享了,我这篇主要说说自动生成先关的东西。
Unity2D的制作流程
1、拿到美术给的帧动画
2、打开Animation windows 手动创建动画文件
3、创建AnimationController 手动连线
4、创建Prefab文件。
这也太麻烦了。全都手动来美术每次给你好几十个动画资源那岂不是要累死程序员了。所以我们不能手动,必须自动。
如下图所示,先看看我生成出来的结果。
我们的目标是Raw文件夹下放所有美术提供的帧动画,每个文件夹就是一组帧动画,文件夹名子就是动画的名子,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
using UnityEngine; using System.Collections; using System.IO; using System.Collections.Generic; using UnityEditor; using UnityEditorInternal; public class BuildAnimation : Editor { //生成出的Prefab的路径 private static string PrefabPath = "Assets/Resources/Prefabs"; //生成出的AnimationController的路径 private static string AnimationControllerPath = "Assets/AnimationController"; //生成出的Animation的路径 private static string AnimationPath = "Assets/Animation"; //美术给的原始图片路径 private static string ImagePath = Application.dataPath +"/Raw"; [MenuItem("Build/BuildAnimaiton")] static void BuildAniamtion() { DirectoryInfo raw = new DirectoryInfo (ImagePath); foreach (DirectoryInfo dictorys in raw.GetDirectories()) { List<AnimationClip> clips = new List<AnimationClip>(); foreach (DirectoryInfo dictoryAnimations in dictorys.GetDirectories()) { //每个文件夹就是一组帧动画,这里把每个文件夹下的所有图片生成出一个动画文件 clips.Add(BuildAnimationClip(dictoryAnimations)); } //把所有的动画文件生成在一个AnimationController里 AnimatorController controller = BuildAnimationController(clips,dictorys.Name); //最后生成程序用的Prefab文件 BuildPrefab(dictorys,controller); } } static AnimationClip BuildAnimationClip(DirectoryInfo dictorys) { string animationName = dictorys.Name; //查找所有图片,因为我找的测试动画是.jpg FileInfo []images = dictorys.GetFiles("*.jpg"); AnimationClip clip = new AnimationClip(); AnimationUtility.SetAnimationType(clip,ModelImporterAnimationType.Generic); EditorCurveBinding curveBinding = new EditorCurveBinding(); curveBinding.type = typeof(SpriteRenderer); curveBinding.path=""; curveBinding.propertyName = "m_Sprite"; ObjectReferenceKeyframe[] keyFrames = new ObjectReferenceKeyframe[images.Length]; //动画长度是按秒为单位,1/10就表示1秒切10张图片,根据项目的情况可以自己调节 float frameTime = 1/10f; for(int i =0; i< images.Length; i++){ Sprite sprite = Resources.LoadAssetAtPath<Sprite>(DataPathToAssetPath(images[i].FullName)); keyFrames[i] = new ObjectReferenceKeyframe (); keyFrames[i].time = frameTime *i; keyFrames[i].value = sprite; } //动画帧率,30比较合适 clip.frameRate = 30; //有些动画我希望天生它就动画循环 if(animationName.IndexOf("idle") >=0 ) { //设置idle文件为循环动画 SerializedObject serializedClip = new SerializedObject(clip); AnimationClipSettings clipSettings = new AnimationClipSettings(serializedClip.FindProperty("m_AnimationClipSettings")); clipSettings.loopTime = true; serializedClip.ApplyModifiedProperties(); } string parentName = System.IO.Directory.GetParent(dictorys.FullName).Name; System.IO.Directory.CreateDirectory(AnimationPath +"/"+parentName); AnimationUtility.SetObjectReferenceCurve(clip,curveBinding,keyFrames); AssetDatabase.CreateAsset(clip,AnimationPath +"/"+parentName +"/" +animationName+".anim"); AssetDatabase.SaveAssets(); return clip; } static AnimatorController BuildAnimationController(List<AnimationClip> clips ,string name) { AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(AnimationControllerPath +"/"+name+".controller"); AnimatorControllerLayer layer = animatorController.GetLayer(0); UnityEditorInternal.StateMachine sm = layer.stateMachine; foreach(AnimationClip newClip in clips) { State state = sm.AddState(newClip.name); state.SetAnimationClip(newClip,layer); Transition trans = sm.AddAnyStateTransition(state); trans.RemoveCondition(0); } AssetDatabase.SaveAssets(); return animatorController; } static void BuildPrefab(DirectoryInfo dictorys,AnimatorController animatorCountorller) { //生成Prefab 添加一张预览用的Sprite FileInfo images = dictorys.GetDirectories()[0].GetFiles("*.jpg")[0]; GameObject go = new GameObject(); go.name = dictorys.Name; SpriteRenderer spriteRender =go.AddComponent<SpriteRenderer>(); spriteRender.sprite = Resources.LoadAssetAtPath<Sprite>(DataPathToAssetPath(images.FullName)); Animator animator = go.AddComponent<Animator>(); animator.runtimeAnimatorController = animatorCountorller; PrefabUtility.CreatePrefab(PrefabPath+"/"+go.name+".prefab",go); DestroyImmediate(go); } public static string DataPathToAssetPath(string path) { if (Application.platform == RuntimePlatform.WindowsEditor) return path.Substring(path.IndexOf("Assets\\")); else return path.Substring(path.IndexOf("Assets/")); } 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; } } } } |
因为新版的动画系统Unity没有提供直接的API来设置动画的循环状态,所以我们只能通过写文件的形式来修改动画的天生属性。需要用到自己写封装的类 AnimationClipSettings 具体方法请看上面的代码。
有了自动生成动画的代码,就不怕美术一次给你多少组图片,或者更新了多少组图片都能很快的生成出来。
随便写一条测试脚本,来测试一下播放动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using UnityEngine; using System.Collections; public class NewBehaviourScript : MonoBehaviour { Animator animator ; void Start () { animator = GetComponent<Animator>(); } void OnGUI() { if(GUILayout.Button("idle")) { animator.Play("idle"); } } } |
动画播放的很正常的。
代码下载地址:http://pan.baidu.com/s/1eQEe3nW
欢迎大家一起讨论unity2d游戏开发,如果您有更好的方法或者建议欢迎在下面给我留言,谢谢。
- 本文固定链接: https://www.xuanyusong.com/archives/3243
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!
雨松 你好 我用unity5 自动生成的时候 “AnimationUtility.SetAnimationType”这个函数没有 注释掉了
但是生成的animation动画 显示“Sprite Render.Sprite Missing”
不知道 你是否遇到过 或者有什么好的解决方案吗?谢谢
雨松哥 ,我用5.0的unity编译这个项目,报11个错,要么是xx已过时,要么是不包含xx的定义,这可怎么办,现在急用啊
我修改了一下,可以在5.x下面正常运行了,把改的地方贴上来给大家static AnimatorController BuildAnimationController(List clips ,string name){AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(AnimationControllerPath “/” name “.controller”);AnimatorControllerLayer layer = animatorController.layers[0];AnimatorStateMachine sm = layer.stateMachine;foreach(AnimationClip newClip in clips){//AnimatorStateMachine machine = sm.AddStateMachine(newClip.name);AnimatorState state = sm.AddState(newClip.name);state.motion = newClip;//AnimatorStateTransition trans = sm.AddAnyStateTransition(state);if(newClip.name == “idle”){sm.defaultState = state;}//sm.AddEntryTransition(machine);//sm.AddStateMachineExitTransition(machine);//trans.RemoveCondition(0);}AssetDatabase.SaveAssets();return animatorController;}
我修改了一下,可以在5.x下面正常运行了,把改的地方贴上来给大家static AnimatorController BuildAnimationController(List clips ,string name) { AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(AnimationControllerPath +”/”+name+”.controller”); AnimatorControllerLayer layer = animatorController.layers[0]; AnimatorStateMachine sm = layer.stateMachine; foreach(AnimationClip newClip in clips) { //AnimatorStateMachine machine = sm.AddStateMachine(newClip.name); AnimatorState state = sm.AddState(newClip.name); state.motion = newClip; //AnimatorStateTransition trans = sm.AddAnyStateTransition(state); if(newClip.name == “idle”){ sm.defaultState = state; } //sm.AddEntryTransition(machine); //sm.AddStateMachineExitTransition(machine); //trans.RemoveCondition(0); } AssetDatabase.SaveAssets(); return animatorController; }
BuildPrefab方法里还有一处地方需要修改,不过很容易,看报错提示跟着改就可以了
雨松大哥,我用这个工具生成的动画,有些动画和原图不一样,这个可能是什么原因产生的呢?
我觉得你的问题和我遇到的一样,看来需要再加个排序的方法
momo神你好:PrefabUtility.CreatePrefab 我保存prefab的时候报错 :’AssetsAppResourcesMountmount_01.prefab’ is not a valid asset file name.不知道咋回事。大家有碰到么
为什么animator.play()只能播放一次动画?
动画没设置 循环。。
MOMO大神,我也用了你这个工具自动生成动画了,很好用,谢谢先~~ 不过还有个问题:即使文件没有修改,貌似不同的机器生成的controller和prefab也是不同的。我看了一下,主要是里面有文件的guid不同。比如在controller文件中,主要就是下面的guid在变化。 m_Motions: – {fileID: 7400000, guid: d99bf5e29c01d4e4baf15aa3467ca00d, type: 2}不知道大神有没有遇到过?或者知道怎么解决?
好像是这样,, 生成一遍 svn就变红了。。。
这样很麻烦啊,都没改文件,结果最终生成的assetbundle都不同,想根据assetbundle MD5做差异更新都不行了。很奇怪这个GUID对应哪个文件,搜索了所有文件都找不到这个GUID。
选择性生成吧。。
MOMO老师你好,我想问一下,你代码中的“AnimationUtility.SetAnimationType”,这个函数是在哪里看到的?我查官网的API时,发现完全没有关于这个函数的文档啊~
我也是这个问题,用unity5.0编译 说AnimationUtility已过时
用鼠标多选几个Sprites,拖到game视图,自动提示你生成帧动画。想怎么用就怎么用。简单粗暴,省事省心省力。。。。。。
这样有点不太好, 因为假如有1000组动画。。 都得人手动拖,, 有可能会出错。。 最好批量自动化。。。。 而且如果想设置动画的一些参数 比如循环 什么的。 都得手动勾这~~~~
这样1000组动画就要生成1000个animator了,不大妥当吧?如果每组动画有8个动作,那一个animator对应8个animtion,一共8000个animation根本吃不消。。再如一些图片资源是assetBundle动态加载的,就没法在编辑器里提前生成动画了。我是用代码解决图片循环的,这样比较可控。不知道动画能否带参数?比如动画循环的是enemy1_01 ~ enemy1_04 这是固定的,搞个变量的话 {name}_01 ~ {name}_04 这样这个animator不论什么图片都能放了,不知道这个支持不?
sprite package 就可以合并图集。。
请问: 将具有保存本地文件功能的unity发布成网页之后,运行之后,却不能在本地保存一个xml文件,无法执行file.creat,或者xmlDoc.Save()之类的保存功能,请问这是为什么呢?提示错误:MethodAccessException: Attempt to access a private/protected method failed. at System.Security.SecurityManager.ThrowException (System.Exception ex) [0x00000] in :0 好像是权限问题,要怎么解决呢
不好意思哈, 我没怎么研究过网页游戏开发。。
哦,没事儿,还是谢谢你!
,我有关注你的微博!呵呵
李总 内个不是ulua 是cslight https://github.com/lightszero/CSLightStudio
我本来不想说脏话的,你个二货,懂个什么雨松大神,无私奉献,这才是程序员的典范无条件支持雨松大神,我看的Unity第一本书就是大神写的,获益良多
请教一下 如何 修改生成动画的锚点呢
可以修改锚点, sprite 拖入project视图选择sprite 右边有一个选项是pivot 选择你需要的锚点 最后生成动画。
谢谢~~
为什么不用一张图做动画呢?
Unity那个sprite package。
嗯,我的意思的用一个有部件的人物图片,包含脚,头,身子之类的,然后使用动画曲线进行编辑,通过改变其相对位置来形成动画,这样的话,2D的动画就需要一张图就行了,Spriete Package的话他其实本质还是很多张图。至于为什么这么做,包的大小很容易就压下去了。
加油
。
你的序列帧播放每次都换图集了,是不是把美术给的资源用Texture Packer打到一张图集上,后者的内存占用比前者更小?
你说得对, 是应该放在一张图集上,但是我不太想用Texture Packer,我想一切操作都自动完成, unity的内个图集感觉有点奇怪。不过这两天我还会在深入的研究一下,谢谢你啦。。 内存应该差不多。 但是drawcall不一样。同一张图可以和批。。。
你好,请问用一张图集时,在生成预制体的时候,每帧关连的图片怎么绑定。
碎图没问题,通过大图json数据进行以上自动生成,发现预制体各动作的sprite renderer 纹理丢失,要如何处理。
unity 带的 Sprite package 可以,感觉挺奇怪的。。
美术提供的资源,有没有要求比如大小不是一样,要不要要求大小比如是2的幂次方?(没有弄在一张图集上面)
理论上最好正方形的图。 因为只有正方形的图 才可以进行 PVR ETC 的压缩。。