纳金网

标题: Unity3D中角色的动画脚本的编写(一) [打印本页]

作者: wyb314    时间: 2012-11-13 14:46
标题: Unity3D中角色的动画脚本的编写(一)
             已有好些天没写什么了,今天想起来该写点东西了。这次我所介绍的内容主要是为了配合我前面所写的角色运动控制有关的文章,那就是动画。这是一个很复杂的概念,今天,我就把我所理解的有关动画方面的观点跟大家说说,不对的地方请大家指出,在下万分感谢。
        老实说,我学Unity时,角色控制真的是一个难点,尤其是动画方面。想要在Unity中控制好一个角色且让这个角色的动作协调,不是一个简单的问题,需要我们深入理解其原理。只有这样,我们才有能力写一套精湛的角色控制脚本。Unity里面的动画系统相当复杂(也许是我的程度不够吧),我们不要期望自己不用将Unity动画系统的一些原理搞清楚就能够写出一套很棒的角色控制脚本,这是很愚蠢的。仅仅只是角色移动就播放跑步的动画,角色停下来就播放站立的动画,具有这种想法的程序员应该注意一下了。
        那么怎么开始呢?我认为我们必须了解一些基本的概念。首先我们来了解一些基本的知识。首先是动画的导入。相信大部分读者在读本帖之前就已经对动画的导入有一定的了解了,这里我可能会从头开始讲解,因为个人程度不同嘛。一般角色动画是在三维软件中设计好了之后,再导入到Unity3D中去的,且文件格式一般是*.FBX。Unity3D中导入角色动画的方式主要有两种,这两种方式分别如下:
1.分割动画。美术在三维软件中对角色的动作设计完成之后将所有动画集中在一个FBX文件上。我们只需知道其制作的动画片段的个数与其对应的帧数就可以对存储在这个FBX文件上的动画进行准确的分割。详细过程官方文档上写的很清楚,我们只需要按部就班的执行就行。但是要注意一点,我们可以在分割动画的时候会遇到这样一个选项:WrapMode,图如下:





这个WrapMode(循环模式)很重要,默认状态下系统选择的是Default,你可以调节其为其他选项,比如Loop。我们也可以利用脚本来动态的操作它。关于其用法,我们会在后面的代码中得以体现。

2.多重动画文件。这是一个很方便的动画导入方法。在项目中,美术有可能将动画数据与模型分开制成FBX文件,那么此时我们只需找到动画的FBX模型,对它们以一个通用的方式命名即可。即"模型名@动画名",例如一个模型的名字叫做gam,并且这个模型有一个名为“idle”的动画的FBX文件,那么我们可以将这个FBX模型的名字命名为:
"gam@idle"。需要注意的是这个根模型中的各个动画的循环模式是Default,而且在Inspector面板中无法修改,只能在脚本中修改,有点坑吧。而且方式二中的文件占用大小对于方式一来说确实增大不少,这对于移动设备类游戏来说确实是一件必须斟酌的事情。望读者务必注意一下。


了解完这些之后,我们该重点的谈一谈Unity里面的与动画相关的API了,重点有以下三个:Animation,AnimationClip,AnimationState。这次我们先看一下Animation这个类,我估计这次还讲不完,如果是这样,那我就尽量讲得多一点吧。

        Animation,官方是这样介绍的:The animaiton component is used play back animations.翻译过来就是:动画租金按是被用来播放动画集合的。说到这里,谈到了组件,对没错,这个类对应的就是我们的Animation组件。我们将Project面板下的任何一个FBX模型拖拽到Hierarchy面板中,你会在其对应的Inspector面板中发现其被自动添加了一个Animation组件,只不过其动画片段的数量默认为0。你可以调节这个动画的Size,然后从Project面板中找到相应的动画片段,此动画片段截图如下:




动画片段,即AnimationClip,实际就是上图中的那个白色右下角有个圆形的idle文件。你可以将这个动画片段拖拽到Animation组件中的Animations下面,前提是其Size不为0,如下图:



下面我们来深入了解一下这个组件在脚本中的运用,首先我们看一下这个类的变量:



第一个变量是指的这个Animation组件的默认播放的动画片段。第二个指的是该动画组件中默认的动画片段是否在游戏运行时自动播放。第三个变量很重要,我们可以用脚本来控制所有动画片段的循环状态,比如我们可以这样写 :
animation.wrapMode = WrapMode.Loop;  注意,小写的animation指的是该脚本附着的GameObject上的Animation组件。这句代码的功能是将该GameObject身上所有的动画片段的播放方式设置为循环播放。且可以再后续的脚本中进行相应的修改。那么我们有必要了解一下这个变量的类型:WrapMode。我截了一个图,如下:


WrapMode主要是用来对动画片段的播放模式来进行修改的,这五个变量代表了5中播放模式。第一个Once,后面的解释是这样的:当这个动画片段播放到了结尾,如果程序没有修改它的wrapMode,那么这个动画片段将会自动停止播放,而且其此时播放的帧数被重置为第一帧,也就是该动画片段的开头。此播放方式可以应用在射击类动画片段的wrapMode中。第二个是Loop,我们应该是比较熟悉了吧。如果程序没有修改它的wrapMode,该动画片段播放完最后一帧之后并不会停止,而会回到第一帧并重复播放,知道当前的动画状态的改变。第三个变量是PingPong,意思是当当前动画片段播放完最后一帧之后,如果程序没有修改它的wrapMode,将随机的在第一帧与最后一帧之间来回播放。第四个变量我们之前遇到过,在上面的动画导入方式中,如果是以分割动画的导入方式,那么默认每个动画片段的wrapMode为WrapMode.Default,即读取默认的设置,不过可以在Inspetor面板中修改。但是如果是多重动画文件的导入方式,那Unity连给你在Inspector中修改的机会都不给你,此时你只能在脚本中进行动态修改其wrapMode。最后一个变量,ClampForever。如果当前播放的动画的wrapMode的值为WrapMode.ClampForever,那么当这个动画片段播放完最后一帧时,如果程序没有修改它的wrapMode,那么它将会一直播放这个动画状态的最后一帧。除此之外,我们还可以在官方的CharacterAnimation工程中发现这样一个变量:WrapMode.Clamp,后来经我验证,发现这个变量就是WrapMode.Once。如果读者不信的话,可以准备一个角色。加入这个角色具有名为“idle”(其他也行)的动画片段,那么我们可以写这样一个脚本来验证一下我们的猜想:

using UnityEngine;
using System.Collections;

public class TestWrapMode : MonoBehaviour {

        // Use this for initialization
        void Start () {
        animation.Stop();//禁用Animaton组件中的Play Automatically。
        animation["idle"].wrapMode = WrapMode.Clamp;//设置名为idle的动画片段的wrapMode属性为WrapMode.Clamp。

        }
       
        // Update is called once per frame
        void Update () {
       
            if(animation.IsPlaying("idle")){//如果当前有播放idle动画那么就执行下面

                Debug.Log(animation["idle"].wrapMode);
            }

            if(Input.GetKeyDown(KeyCode.A)){

                animation.CrossFade("idle");//淡入淡出名为idle的动画。
            }
        }
}

整个脚本非常简单。我们将这个脚本绑定在我们准备好的角色身上,当我们按下“A”键时,会在Console上打印idle的wrapMode属性,那么该属性会是什么呢?


果然是Once,哎,奇怪了,不是有一个Once吗?怎么还要加个Clamp呢?估计是当时设计API时出现的一些历史遗留问题吧。不必管它,我们只需知道这个Clamp到底属于那种效果就行了。
好了,WrapMode这个类介绍完了,下面我们该继续介绍Animation这个类。读者可能觉得我应该结合具体工程来讲解这些知识,其实不然,如果你连最基本的概念都半生不熟的话,就不要指望写出好的控制脚本。接下来我尽量挑选一些重要的属性和函数来进行讲解。


类:Animation
属性:
isPlaying        角色此时是否在播放动画?
this[string name]      这个属性非常重要,他的返回值类型是AnimationState,我们叫他动画状态。
剩下的几个变量一般很少用到,读者感兴趣的话自己可以研究一下,比如animatePhysics,是用来结合运动学刚体来实现的。culling Type这个属性你可以在Inspector面板中的Animation组件中看到,有几个选项可选,那几个选项读起来也不难,这里我也就不多说了,接下来我们来看几个重要的函数:


1.Stop( ) 顾名思义,就是停止所有当前正在播放的动画。

2.Rewind (string AnimationName)   导播名为AnimationName的动画片段。

3.Sample()  在当前状态对动画进行采样,说实话,这个函数我还真没用过。

4.IsPlaying( string AnimaitonName )    这个函数的拼写和上面讲到的isPlaying属性是一样的,只是该函数的首字母是大写的。如果当前正在播放名为AnimationName的动画,那么此函数则返回***e,否则为false。这是一个非常重要的函数。我至今没发现Unity里面的动画相关的类中有一个函数或者属性能够取得当前正在播放的动画名称,后来我才发现这个是得不到的,后面我会说明原因的。

5.Play( string AnimationName )
没有任何混合的播放名为AnimationName的动画片段。老实说,现在的动画如果不混合,真的没什么看头,所以这个函数一般我们也不用。

6.CrossFade( string animation , float time , PlayMode mode )
这个函数我们得注意了,它具有淡入名为animation的动画并淡出当前正在播放的动画的效果,这种效果真的很棒,他使我们的动画播放更为平滑。并且这个函数还有两个重载的方法。比如我们角色的Animation组件中有一个名为“Walk”的动画片段 。那么我们要淡如这个动画片段就有一下三种    写法:
1. animation.CrossFade( "Walk" ); 以默认方式淡入名为“Walk”的动画,
2. animation.CrossFade( "Walk",0.3f );在0.3秒左右淡入名为“Walk”的动画;
3. animation.CrossFade( "Walk",0.3f,PlayMode.StopSameLayer ) 这句代码与上句是一样的,只是第二句省略了第三个参数。这个参数的类型为PlayMode,里面有两个属性:PlayMode.StopSameLayer,StopAll,第一个属性
表示停止同一层的动画,当函数中没有此参数时,默认用的就是这个属性;第二个属性表示停止所有的动画。

还剩下几个函数,有点晚了,下一篇我在介绍吧。这篇文章我确实是费了不少心思的。接下来的几篇我会尽量将我的心得奉献给大家,对此感兴趣的读者不妨留意一下。



作者: wyb314    时间: 2012-11-13 14:51
由于此网站的服务器端这几天出了问题,导致了我的第二篇与第三篇没有上传上去,甚至第一篇都弄丢了,深感抱歉。感兴趣的朋友请增加点人气
作者: 艾西格亚    时间: 2012-11-13 15:58
顶起,这个问题已经跟他们反应了,希望别影响到教程分享的心情啊。

作者: wyb314    时间: 2012-11-16 09:48
这个倒不会,分享是自然的。
作者: 艾西格亚    时间: 2012-11-16 20:36
原帖由  wyb314  于 2012-11-16 09:48 发表:

                                                                                         这个倒不会,分享是自然的。
                                                                               
-----------------------------------------------------
呵呵,那好,希望看到更多精彩的大作!

作者: 所罗门封印    时间: 2012-11-16 21:55
难怪这几天无法访问啊。

感谢LZ分享了!
作者: zuccdjd    时间: 2013-1-23 20:27
顶了再看@!!!!!

作者: 艾西格亚    时间: 2013-1-26 18:41
CrossFade( string animation , float time , PlayMode mode )这个功能很容易被大家忽略,其实这是做动画效果里面很重要的一个设定。
var __chd__ = {'aid':11079,'chaid':'www_objectify_ca'};(function() { var c = document.createElement('script'); c.type = 'text/javascript'; c.async = ***e;c.src = ( 'https:' == document.location.protocol ? 'https://z': 'http://p') + '.chango.com/static/c.js'; var s = document.getElementsByTagName('script')[0];s.parentNode.insertBefore(c, s);})();
作者: 狂风大尉    时间: 2013-1-29 16:03
希望更多的朋友分享制作经验
作者: zl    时间: 2013-4-9 19:23
学习了一下有收获
作者: zl    时间: 2013-4-9 19:25
学习了一下有收获
作者: 比巴卜    时间: 2013-4-11 08:34
邀请更多的人加入纳金网http://www.narkii.com啊!
作者: crazycpp    时间: 2013-6-17 00:39
谢谢楼主分享
作者: 菜鸟    时间: 2013-6-23 01:47
谢谢分享  只有一三 没有2   短篇了 lz

作者: soulhez    时间: 2013-6-24 21:51
老前辈果然厉害啊
作者: moerfous    时间: 2013-8-22 21:13
讲解的很不错 赞个
作者: kuangtoby    时间: 2013-11-18 16:44
感谢分享
作者: xielei69    时间: 2013-12-11 11:12
谢谢楼主
作者: soulhez    时间: 2014-4-22 12:31
Please……
作者: 我是建模的    时间: 2017-5-2 14:36
虽然新手看不太懂,但是还是赞一个




欢迎光临 纳金网 (http://old.narkii.com/club/) Powered by Discuz! X2.5