unity的新动画系统叫Mecanim,使用Animator来取代旧系统Animation,按Unity文档的惯例:知识点主要分2部分:unity manual和unity script,读者可以边看文章边查阅文档,最好能动手测试。 文章的开始之前,先讲几个基本的知识的: 1.创建动画的一个基本步骤是设置一个unity3d可理解的简化后的骨骼到骨架中实际骨骼的映射;在Mecanim的术语中,这个映射称为Avatar,即avatar是骨骼到骨架的映射。 Avatar主要用于类人骨骼模型,可以实现角色之间的Retargeting。非类人模型可以认为骨架就是骨骼。 2.构建模型的基本步骤: modelling->rigging->skinning(建模->构建骨架->蒙皮) 1.modelling 建模: 1.Observe a sensible topology(遵循合理的拓扑结构),一个合理的标准是动画带动的网格变形是漂亮的; 2.注意网格的缩放比例。最好做一下各个建模软件模型的导入测试,来设置好正确的缩放比例(不同建模软件导入比例不一样) 3.安放角色使得角色的脚站在坐标原点或者模型的“锚点”。角色通常是竖直地走在地面上,如果角色的锚点(也就是他的变换中心)在地面上会更容易控制。 4.如果是类人模型,则尽量使用T字姿态建模(Unity为类人模型提供了许多功能和优化) 5.整理你的模型,去掉垃圾。只要可能的话,覆盖孔洞,焊接顶点并且移除隐藏的面,这会对蒙皮有帮助,特别是自动蒙皮过程。 2.Rigging 搭骨架:创建骨架上的关节来控制你的模型进行运动。 非类人模型的话,可以认为没有骨架,只有骨骼,骨骼直接控制动画,类人模型是骨架控制动画。步骤1中的模型已经有脚,手,头,武器等骨骼,还有受击骨骼等,这些可以用来控制模型或者悬挂额外物件。 3.Skinning 蒙皮:给骨架附加网格。 1.把网格中的顶点绑定到骨骼,包括硬绑定(一个顶点指定一个骨骼,不是一一对应,可能多个顶点指定的是一个骨骼)和软绑定(一个顶点指定多个骨骼,每个骨骼有一定权重) 3.动画文件导入unity后,我们对它的处理和设置,这些在Inspector面板:Animation Importing settings 分3个页签: Model:这里的参数基本由美术来定,其他的使用默认就好了,这里只提4个参数: 1.scale factor 模型缩放比例,不同建模软件导入比例不一样,unity的物理单位是1m,这个根据不同建模软件设置 2.readable/writeable和UITexture一样,如果开了unity就必须拷一份到内存,尽量不开。 Rig:这里Animation Type包括(Generic/Humanoid/Legacy/None) Generic 用于非类人模型;Humanoid用于类人模型 Avatar Definition:可以使用现有的avatar,也可以create from this model,一般地,模型网格文件选create,这个时候,Avatar子资产被增加到模型资产的下面,即Avatar是unity根据网格文件生成的,对Humanoid类型,还会自动匹配骨骼到骨架,不符合会报错;而动画文件使用copy,使用已有资源。 Animations:对一个Animation Clip动画片段进行设置 前面部分的设置这里就不讲了,下面的clip片段我们看到Start和End参数,这表示从一个fbx动画中截取一段给clip使用,多个clip共用一个动画资源,所以,可以一个模型的所有动作都搞到一个动画文件中,各个动作去里面取一段即可;也可以一个模型每个动作都有一个动画文件,分开管理。 中间的参数比较烦躁,略过,先提一下Animations页签最下面的几个有意思的参数:curves,events,mask,这2个简单的参数可以带来许多有趣的功能,在后面会讲到。 讲完上面几个基本知识点后,下面我们来分点看几个有趣的应用: 1.AnimationEvent Unity manual部分: 这个在animations页签的下面,可以给clip加帧事件,即播到某帧时触发某个事件: 1.给clip的某些帧上加event,这个Function就是事件的名字,其他的是这个函数的参数 2.定义一个脚本来接受这个事件,比如这个图需要定义一个脚本,并且脚本里定义了void ani(xxx){}函数 3.参数的处理:根据脚本的函数定义格式传参数,比如void ani(int a),则传Int那个参数,ani(Object a)则传Object那个参数,ani(float a, string b)则传Float和String这2个参数,而,ani(AnimationnEvent a)则传整个event进去(包括所有参数和当前event对应的clip的信息) ps:添加了event的clip的animator物体必须挂上定义了该事件名Function的函数的脚本,否则会报错 Unity Script部分:可查看class AnimationEvent,主要是获得该event所在clip的一些信息:与此event相关的stateinfo,clipinfo,和该event本身的信息:event调用的函数名functionName、函数调用的参数:float/int/string/Object(采用哪个看函数定义式)、time(事件的触发时间) 实际应用:这个event机制可以在播到某些帧执行一些事件。那么我们可以,在某个点播某个特效,在某个点播某个声音,在某个点进行一些画面特效,在某个点进行对敌人“击退”“击飞”“击浮空”,非常有利于实现各种节奏效果!还可以加入技能打断机制:当播到某2个帧之间可以被打断:比如我远扑某个玩家,结果我在空中被打断,我就被弹回来。这种效果一定很爽吧。读者可以在一个专门的脚本中定义各种接受事件的函数,并进行相应处理来进行使用此event机制。 2.AnimationCurve: Unity manual部分: 添加curve,这个curve和event有点像但又不同,event是几帧,curves 则是每帧,curves可以配合OnAnimatorMove使用,比如每帧不同速度前进: void OnAnimatorMove() { Animator animator = GetComponent<Animator>(); Vector3 newPosition = transform.position; newPosition.z += animator.GetFloat("Runspeed") * Time.deltaTime; //RunSpeed是Curves的曲线变量,控制移动, transform.position = newPosition; //由此类似方法可以让角色一个动画各种移动。 } 官方文档给出一个例子是: 比如人在冰冷环境呼吸时,呼气的水雾由粒子系统控制,那么就可以在播呼吸动画或者站立动画时,通过Animator.Get参数(一个Curve的name)获得当前数值,控制水雾大小。 Unity Script部分: 属性:keys(关键帧key集合),length(the num of keys),postWrapMode(最后一帧循环类型),preWrapMode(第一帧循环类型),this[int]获取关键帧 接口:Evaluate(time):计算某个time时曲线的value 总结:curves感觉就是:边播动画边干点其他事情,event则像播到一些帧就干点事情。2者配合使用能让你的动画系统变得丰富起来,好好使用这两个小小的利器吧。 3.Animation Layers和遮罩 实现:边走边吃苹果 1.给吃苹果动画加遮罩:在animation tab中的Mask中加遮罩,只勾选吃苹果的那部分骨骼,3种情况添加: A.加现有的遮罩文件,可以通过Assets->Create->Avatar Mast创建一个遮罩 2.创建新Layer:EatApple Layer, 把第1步的遮罩拖到这里的Mask参数中,设置该layer设置比走路动画Layer高,并设置该Layer的Blending为override,这样,播走路动画和播吃苹果动画就可以同时进行并且播苹果动画override了走路动画的上半身动画。 4.Animator Override Controller: 顾名思义,它就是Override “Animator Controller”的,先简单说一下Animations Controller: Animation Controller可以认为它就是动画状态机, Animator动画系统是通过Animator Controller来控制动画播放的,里面存着指向各个动画片段Animation Clip的引用,和播放动画的逻辑,比如状态转移等. 而Animator Override Controller则用于拓展一个已存在的Animtor Controller,它只是在后者基础上在某些状态播新的动画Clip而已,保持后者的状态机逻辑,结构等等:retaining the original’s structure, parameters and logic. 所以在应用方面,Animator Override Controller能做到一个状态机(Animator Controller)实现多套动作,非常利于维护。按官方说法就是: 如果一类模型可以共用动画状态机,则可以弄一个基础Animtor Controller,里面是基本的动画状态逻辑,然后给某个模型使用就弄一个Animtor Override Controller,然后把其中不同的动画Clip换成自己的,这样就只需维护一份动画状态机了,省心省力。比如NPC系统的各个NPC。许多怪物也可以共用,主角更不用说。 5.1种换装系统 (复习一个知识点:Skinning 蒙皮:给骨架附加网格,把网格中的顶点绑定到骨骼) 先加载2个模型出来,第1个模型是基础模型,skinmesh/动画/Animator等都有,第2个模型则是一个带skinmesh的几乎空的模型,第一个是源模型,第二个是目标模型,我们现在要做的就是用第2个模型的skinmesh替换掉第1个模型的skinmesh,做法如下: a.获得第一个模型的skinmesh old_meshrender,获得第2个模型的skinmesh dst_meshrender,获得第一个模型的所有骨骼 Transform[] bones. b.目的是获得dst_meshrender中各个顶点映射到bones的各个骨骼列表,即为dst_meshrender网格中各个顶点找到第1个模型中对应的骨骼,即为“新皮”重新指定骨骼,然后把old_meshrender的网格sharemesh换成dst_meshrender的sharemesh,把old_meshrender的骨骼bones换成新的骨骼列表(当然还是第1个模型的),简而言之,就是:为第1个模型重新指定网格(第二个模型的),因为网格是新的,所以需要重新映射网格顶点到骨骼,看代码可能更容易理解: SkinnedMeshRender dst_meshR = newModel.GetComponent<SkinnedMeshRender>(); SkinnedMeshRender old_meshR = oldModel.GetComponent<SkinnedMeshRender>(); Transform[] bones = old_meshR.bones; Transform[] newBones = new Transform[dst_meshR.bones.Length]; for (int i = 0; i < dst_meshR.bones.Length;++i) { for (int j = 0; j < old_meshR.bones.Length; ++j) { if (old_meshR.bones[j].name == dst_meshR.bones[i].name) { newBones[i] = old_meshR.bones[j]; break; } } } old_meshR.sharedMesh = dst_meshR.sharedMesh; old_meshR.bones = newBones; old_meshR.sharedMaterials = dst_meshR.sharedMaterials; 这种换皮技术原理清晰、简单,也方便使用,并且只换皮(网格)不换模型骨架,所以对动画系统等非常方便,只需把那些东西绑定到原始模型即可。 但需要注意的是,各个换装模型的骨骼要规范,尽量骨架同原始模型一致,至少需要做到骨骼“可以多不可以少”。 6.混合树 状态转移和混合树,虽然两者都是用来制作平滑动画,但区别是大大的(读者可只看前3点即可): 8.之前一直不知道怎么移动浏览Animator 窗口的各自状态:Alt+鼠标左键! 9.animator的TargetMatching技术 假如你有一个跳跃的动画,要实现跳跃到某个物体上的效果,可以考虑用animator的TargetMatching技术: get到模型的Animator ani,然后 animator.MatchTarget(jumpTarget.position, jumpTarget.rotation, AvatarTarget.LeftFoot, new MatchTargetWeightMask(Vector3.one, 1f), 0.141f, 0.78f); 10.IK控制骨骼 步骤: 11.根运动 根运动是动画本身自带的,比如行走动画,如果不apply root motion则“原地踏步”(in-place),如果应用则向前行走。 实际项目中一般,不apply root motion,而通过代码来设置transform postion,总结而言: 1.不apply root motion并且该animator组件所在obj上的控制脚本不实现OnAnimatorMove,且所有脚本也不控制该模型position,=>模型原地踏步; PS1:上述只提了位置,根运动也包括rotation,且控制逻辑也是同样的。 官方说明:Root motion is the effect where an object‘s entire mesh moves away from its starting point but that motion is created by the animation itself rather than by changing the Transform position. Note that applyRootMotion has no effect when the script implements a MonoBehaviour.OnAnimatorMove function.Changing the value of applyRootMotion at runtime will re-initialize the animator. 11.一些优化建议 1.The Animator doesn’t spend time processing when a Controller is not set to it 12.Animator Component Unity Manual:参考这篇:http://www.cnblogs.com/Tearix/p/6941156.html Unity Script:属性和接口太多了,看官方文档吧,有个翻译可以参考一下,但那个只是作者自己的理解,不保证准确: http://www.cnblogs.com/hont/p/5100472.html?utm_source=tuicool&utm_medium=referral 13.一个坑 Play(A);Play(B);则最终Play了A;以前好像遇到过有概率不播某个动画的情况,没查明是否是因此造成的。解决方法是: Play(A);Update(x);Play(B);则最终Play了B,用Update更新一下就可以覆盖了。如果是CrossFade,情况又复杂一些了,这个问题我专门和一位网友讨论过,这里不提。 标签:amp 锚点 end skin ble 动作 actor bre 函数定义 原文地址:http://www.cnblogs.com/Tearix/p/6939948.html |
|
来自: 勤奋不止 > 《游戏引擎unity》