首页 >Unity3D频道 >【Unity2D研究院之游戏开发】 > Unity2D研究院之自动生成动画、AnimationController、Prefab(一)
2014
10-03

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

国庆了,回家了。时刻还是要吃一颗学习的心,在家了也要抽出时间好好学习一下。之前MOMO一直没研究过Unity2D,今天研究了一下,还是把自己今天的研究笔记记录下来。现在网络上已经有很多Unity2D的技术分享了,我这篇主要说说自动生成先关的东西。

Unity2D的制作流程

1、拿到美术给的帧动画

2、打开Animation windows 手动创建动画文件

3、创建AnimationController 手动连线

4、创建Prefab文件。

这也太麻烦了。全都手动来美术每次给你好几十个动画资源那岂不是要累死程序员了。所以我们不能手动,必须自动。

如下图所示,先看看我生成出来的结果。

Snip20141003_2

 

我们的目标是Raw文件夹下放所有美术提供的帧动画,每个文件夹就是一组帧动画,文件夹名子就是动画的名子,代码如下所示。

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 具体方法请看上面的代码。

有了自动生成动画的代码,就不怕美术一次给你多少组图片,或者更新了多少组图片都能很快的生成出来。

随便写一条测试脚本,来测试一下播放动画。

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");

		}
	}
}

动画播放的很正常的。

Snip20141003_3

代码下载地址:http://pan.baidu.com/s/1eQEe3nW

欢迎大家一起讨论unity2d游戏开发,如果您有更好的方法或者建议欢迎在下面给我留言,谢谢。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity2D研究院之自动生成动画、AnimationController、Prefab(一)》有 53 条评论

  1. 邹庆伟说:

    雨松 你好 我用unity5 自动生成的时候 “AnimationUtility.SetAnimationType”这个函数没有 注释掉了
    但是生成的animation动画 显示“Sprite Render.Sprite Missing”
    不知道 你是否遇到过 或者有什么好的解决方案吗?谢谢

  2. 雨松哥 ,我用5.0的unity编译这个项目,报11个错,要么是xx已过时,要么是不包含xx的定义,这可怎么办,现在急用啊

  3. 伦子说:

    我修改了一下,可以在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;}

  4. 伦子说:

    我修改了一下,可以在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. NCjia说:

    雨松大哥,我用这个工具生成的动画,有些动画和原图不一样,这个可能是什么原因产生的呢?

  6. 守望说:

    momo神你好:PrefabUtility.CreatePrefab 我保存prefab的时候报错 :’AssetsAppResourcesMountmount_01.prefab’ is not a valid asset file name.不知道咋回事。大家有碰到么

  7. paranoia说:

    为什么animator.play()只能播放一次动画?

  8. longroads说:

    MOMO大神,我也用了你这个工具自动生成动画了,很好用,谢谢先~~ 不过还有个问题:即使文件没有修改,貌似不同的机器生成的controller和prefab也是不同的。我看了一下,主要是里面有文件的guid不同。比如在controller文件中,主要就是下面的guid在变化。 m_Motions: – {fileID: 7400000, guid: d99bf5e29c01d4e4baf15aa3467ca00d, type: 2}不知道大神有没有遇到过?或者知道怎么解决?

  9. 煎饼说:

    MOMO老师你好,我想问一下,你代码中的“AnimationUtility.SetAnimationType”,这个函数是在哪里看到的?我查官网的API时,发现完全没有关于这个函数的文档啊~

  10. 源代码说:

    用鼠标多选几个Sprites,拖到game视图,自动提示你生成帧动画。想怎么用就怎么用。简单粗暴,省事省心省力。。。。。。

    • 这样有点不太好, 因为假如有1000组动画。。 都得人手动拖,, 有可能会出错。。 最好批量自动化。。。。 而且如果想设置动画的一些参数 比如循环 什么的。 都得手动勾这~~~~

      • 这样1000组动画就要生成1000个animator了,不大妥当吧?如果每组动画有8个动作,那一个animator对应8个animtion,一共8000个animation根本吃不消。。再如一些图片资源是assetBundle动态加载的,就没法在编辑器里提前生成动画了。我是用代码解决图片循环的,这样比较可控。不知道动画能否带参数?比如动画循环的是enemy1_01 ~ enemy1_04 这是固定的,搞个变量的话 {name}_01 ~ {name}_04 这样这个animator不论什么图片都能放了,不知道这个支持不?

  11. sprite package 就可以合并图集。。

  12. 请问: 将具有保存本地文件功能的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 好像是权限问题,要怎么解决呢

  13. 陈靖说:

    我本来不想说脏话的,你个二货,懂个什么雨松大神,无私奉献,这才是程序员的典范无条件支持雨松大神,我看的Unity第一本书就是大神写的,获益良多 Unity2D研究院之自动生成动画、AnimationController、Prefab(一) - 雨松MOMO程序研究院 - 1

  14. xuhe说:

    请教一下 如何 修改生成动画的锚点呢 Unity2D研究院之自动生成动画、AnimationController、Prefab(一) - 雨松MOMO程序研究院 - 1

  15. 张洁勇说:

    为什么不用一张图做动画呢?

    • Unity那个sprite package。

      • 张洁勇说:

        嗯,我的意思的用一个有部件的人物图片,包含脚,头,身子之类的,然后使用动画曲线进行编辑,通过改变其相对位置来形成动画,这样的话,2D的动画就需要一张图就行了,Spriete Package的话他其实本质还是很多张图。至于为什么这么做,包的大小很容易就压下去了。

  16. 不错!~O(∩_∩)O说:

    Unity2D研究院之自动生成动画、AnimationController、Prefab(一) - 雨松MOMO程序研究院 - 1

  17. 张浩说:

    你的序列帧播放每次都换图集了,是不是把美术给的资源用Texture Packer打到一张图集上,后者的内存占用比前者更小?

留下一个回复

你的email不会被公开。 必填项已用 * 标注。