通常我们在游戏里面创建的精灵比如玩家主角,它在移动的过程中一般会带有一些动画的效果,比如两只脚前后地移动,那么这种动画效果的实现和控制就可以通过Unity2D的动画系统来实现。
要添加这样的动画,首先我们需要先在Hierarchy视图里面选中精灵,然后在Animation视图里面创建一个动画文件【Create New Clip】,如下图所示:
创建好动画的文件之后,就可以把精灵的各个状态拉到时间轴上,然后拖动红色的线条就可以看到动画的预览效果。
对于一个精灵可以创建多个动画,各个动画之间可以进行切换,下面我们再看一下动画之前的切换和精灵动画的管理。
创建了动画之后,在动画文件的文件夹里面会发现多了一个后缀为controller的文件,这个就是动画控制器的文件,是跟精灵搭配起来的,名字也是跟精灵的名字一致,表示是对精灵动画的控制。如图所示:
点击Palyer.controller文件,可以在Unity里面看到其可视化的界面如图所示:
在上图中我们可以看到左下角有一个添加参数的功能,这个功能一般会用来控制动画之前的切换,或者动画的终止等。动画之前是可以互相关联的,在可视化的控制器里面右键动画,就可以添加其下一个关联的动画,如上图中的两个动画是互相关联的。点击动画之间的“箭头”,然后再Inspector视图里面就可以查看或者设置动画之前的切换条件,如下图所示,通过Warking参数来控制动画之前的切换:
这时候在精灵的Inspector视图里面,已经自动给精灵添加了动画控制器的组件了,如图所示。
接下来我们就可以在精灵对应的脚本里面来获取动画控制器Animator来进行相关的操作。
private Animator playerAnimator = null;// 动画控制器 void Start() { playerAnimator = GetComponent<Animator>(); } ...... playerAnimator.SetBool("Walking", false); ...... playerAnimator.SetBool("Walking", true);
我们可以在脚本里面设置动画控制器参数的值,通过这个参数来控制动画之前的切换。
http://www.xuanyusong.com/archives/3243
国庆了,回家了。时刻还是要吃一颗学习的心,在家了也要抽出时间好好学习一下。之前MOMO一直没研究过Unity2D,今天研究了一下,还是把自己今天的研究笔记记录下来。现在网络上已经有很多Unity2D的技术分享了,我这篇主要说说自动生成先关的东西。
Unity2D的制作流程
1、拿到美术给的帧动画
2、打开Animation windows 手动创建动画文件
3、创建AnimationController 手动连线
4、创建Prefab文件。
这也太麻烦了。全都手动来美术每次给你好几十个动画资源那岂不是要累死程序员了。所以我们不能手动,必须自动。
如下图所示,先看看我生成出来的结果。
我们的目标是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 具体方法请看上面的代码。
有了自动生成动画的代码,就不怕美术一次给你多少组图片,或者更新了多少组图片都能很快的生成出来。
随便写一条测试脚本,来测试一下播放动画。
动画播放的很正常的。
代码下载地址:http://pan.baidu.com/s/1eQEe3nW
http://blog.csdn.net/rcfalcon/article/details/43539305
关于自动生成动画,雨松MOMO有一篇比较完整的文章了,链接在此 点击打开链接
生成AnimationClip、Controller以及设置State等都是参考了他提供的代码,这里我主要补充一下如何给2d texture设置锚点。
这里主要看代码吧,值得一提的是,设置自定义锚点必须将spriteAlignment设置为9(customer),否则设置的pivot将不起作用。
[MenuItem("JyGame/BuildClip")] static void BuildClip() { //AnimationXml xml = ResourceManager.Get ResourceManager.Init(); List<string> actionList = new List<string>(); foreach(var d in TargetDir.GetDirectories()) { string animationName = d.Name; Debug.Log("building " + animationName); if (ResourceManager.Get<JyGame.AnimationNode>(animationName) == null) { Debug.LogWarning("错误, 找不到xml中锚点描述!" + animationName); continue; } //调整锚点 foreach(var f in new DirectoryInfo(d.FullName + "\\rawdata").GetFiles("*.png")) { string[] paras = f.Name.Replace(".png","").Split('-'); string animation = paras[0]; string action = paras[1]; int count = int.Parse(paras[2]); AnimationImage img = null; try { img = ResourceManager.Get<JyGame.AnimationNode>(animation).GetGroup(action).images[count]; } catch { Debug.Log("找不到文件对应的描述,删除" + f.FullName); File.Delete(f.FullName); continue; } string path = DataPathToAssetPath(f.FullName); TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; TextureImporterSettings tis = new TextureImporterSettings(); textureImporter.ReadTextureSettings(tis); tis.spriteAlignment = 9; //customer tis.spritePixelsPerUnit = 1; tis.mipmapEnabled = false; tis.spritePivot = new Vector2((float)img.anchorx / img.w, (float)(img.h - img.anchory) / img.h); textureImporter.SetTextureSettings(tis); if(!actionList.Contains(action)) { actionList.Add(action); } AssetDatabase.ImportAsset(path); } List<AnimationClip> clips = new List<AnimationClip>(); //创建clip foreach(var action in actionList) { FileInfo[] images = new DirectoryInfo(d.FullName + "\\rawdata").GetFiles(string.Format("*-{0}-*.png",action)); 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]; float frameTime = 1 / 10f; if(action == "stand") { frameTime = 1 / 4f; } 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; } clip.frameRate = 10; if (action == "stand") clip.frameRate = 4; if (action != "attack") { //设置idle文件为循环动画 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, TARGET_DIR + animationName + "\\" + action + ".anim"); clips.Add(clip); AssetDatabase.SaveAssets(); } //创建controller AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(TARGET_DIR + animationName + "\\controller.controller"); AnimatorControllerLayer layer = animatorController.GetLayer(0); UnityEditorInternal.StateMachine sm = layer.stateMachine; //animatorController.AddParameter("attack", AnimatorControllerParameterType.Trigger); //animatorController.AddParameter("move", AnimatorControllerParameterType.Trigger); //animatorController.AddParameter("stand", AnimatorControllerParameterType.Trigger); State standState = null; State attackState = null; State moveState = null; foreach (AnimationClip newClip in clips) { State state = sm.AddState(newClip.name); state.SetAnimationClip(newClip, layer); if (newClip.name == "stand") { sm.defaultState = state; standState = state; }else if(newClip.name =="move") { moveState = state; }else if(newClip.name == "attack") { attackState = state; } Transition trans = sm.AddAnyStateTransition(state); trans.RemoveCondition(0); } if(attackState != null && standState!=null) { var trans = sm.AddTransition(attackState, standState); //var condition = trans.AddCondition(); //condition.mode = TransitionConditionMode.ExitTime; } AssetDatabase.SaveAssets(); //生成Prefab 添加一张预览用的Sprite FileInfo tagImage = new DirectoryInfo(d.FullName + "\\rawdata").GetFiles("*.png")[0]; GameObject go = new GameObject(); go.name = animationName; SpriteRenderer spriteRender = go.AddComponent<SpriteRenderer>(); spriteRender.sprite = Resources.LoadAssetAtPath<Sprite>(DataPathToAssetPath(tagImage.FullName)); Animator animator = go.AddComponent<Animator>(); animator.runtimeAnimatorController = animatorController; PrefabUtility.CreatePrefab(TARGET_DIR + animationName + "/sprite.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; } } }