Unity3D中角色的动画脚本的编写

http://blog.csdn.net/xy849288321/article/details/8992625


http://bbs.9ria.com/thread-211050-1-1.html


已有好些天没写什么了,今天想起来该写点东西了。这次我所介绍的内容主要是为了配合我前面所写的角色运动控制有关的文章,那就是动画。这是一个很复杂的概念,今天,我就把我所理解的有关动画方面的观点跟大家说说,不对的地方请大家指出,在下万分感谢。
老实说,我学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,第一个属性
表示停止同一层的动画,当函数中没有此参数时,默认用的就是这个属性;第二个属性表示停止所有的动画。

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


本文来自:http://www.narkii.com/club/thread-267966-1.html







在上一篇,我们介绍了有关Animation这个类中的部分方法,我后来想了想,这么介绍也不是个办法(其实有些方法我自己也没用过),该介绍点实际的东西了,毕竟我们是要做东西出来的。那好,我们就开始吧。 

            首先我们要介绍的主题是:Animation Blending ,即动画融合。我们来看官方文档上的描述:



用我自己的理解就是:在现今的游戏中,动画融合是一个必不可少的特性用以让你的的角色能够产生平滑的动画。动画设计师首先为角色创建了一些个动画片段,例如一个行走循环,跑步循环,还有站立或射击的。在你运行游戏期间,你需要从站立的动画状态转换到行走的动画状态并且看上去要足够平滑,不能突然跳转。 



此时我们就需要 有一种处理方式来完成我们所想要实现的效果。这就是“动画融合”的用处。在Unity中,一个角色可以有多种不同的动画片段,Unity可以将这些动画片段融合起来并以一种组件的形式(即Animation)进行管理,并根据具体情形来比如脚本来生成最终的动画。看来我们其实一直在运用“动画的融合”。
        那么我们来看一个例子吧,我就选官方的CharacterAnimation里的goober为例,直接给出代码:

using UnityEngine;
using System.Collections;

public class SuperMarioAnimation : MonoBehaviour {
        
        public float landBounceTime = 0.6f;//该动画在0播放到60%时之后立即结束,应该是后来根据主角在空中滞留的时间而调整的。
        
        private AnimationState lastJump;//存储“jump”的动画状态
        // Use this for initialization
        void Start () {
                
                animation.wrapMode = WrapMode.Loop;//首先设置所有的动画片段播放时的播放模式为循环播放模式,之后我们也可以修改单个动画片段播放时的循环模式,比如此函数最后一行。

                AnimationState jump = animation["jump"];//暂存“jump”的动画状态,记住,是暂存。仅仅只是用于下面三行代码中的动画设置。

                jump.layer = 1 ;//设置动画状态jump的动画层值为1,相对于默认层级值要高,为的是有更多的优先级取得动画权重。

                jump.enabled = false;//暂停该动画的播放,这个我会在后面进行相应的讲解,因为它有其独特的应用情景。

                jump.wrapMode = WrapMode.Clamp;//设置动画状态jump的循环模式为Once(单次,上一篇已经证明了)循环。
                        
        }
        void Update () {
                SuperMarioController marioController = gameObject.GetComponent<SuperMarioController>();
                //取得该角色的SuperMarioController脚本组件。                

                float currentSpeed = marioController.GetSpeed();
                //每帧获取该角色的速度并存储在临时变量currentSpeed里。
                
                if(currentSpeed > 0.1){//以速度判断该播放何种动画,淡入淡出效果的核心就在这里
                        animation.CrossFade("walk");
                        //如果当前正在播放某个动画片段,那么淡出它,淡入名为“Walk”的动画。
                }else{
                        animation.CrossFade("idle");
                }
                
                if(marioController.IsJumping()){//如果此时goober(我们的主角)还在跳跃
                        if(lastJump.time > landBounceTime){//如果goober的跳跃动画播放了60%之后
                                lastJump.speed = 0.0f;//让这个动画状态从头开始播放。
                        }
                }
                
        }
        public void DidJump(){//SendMessage方法调用的,在goober跃起时执行
                //lastJump = animation.CrossFadeQueued("Move",0.3f,QueueMode.PlayNow);
                lastJump = animation.CrossFadeQueued("jump", 0.3f, QueueMode.PlayNow);
                
        }

    public void DidLand()//SendMessage方法调用的,在goober着地时执行
    {
            lastJump.speed = 1;//将当前播放的动画lastJump的帧数调到最后。
    }
        
}

        注意,要想理解好这段代码,我们必须结合另外一个加在goober上的脚本:SuperMarioController。这个例子里面出现了我们之前提到的一个非常重要的类:AnimationState,即动画状态,还涉及到了动画层的概念。所谓动画层,即AnimationState.layer,AnimationState这个类中的一个很重要的属性。在Unity的动画体系中,默认情况下,所有动画状态的layer值为0,你可以在脚本中动态的调节该动画状态的layer值。如上代码,我们在unity3d中点击运行按钮,我们什么都不做时,由于此时获得的currentSpeed的值肯定是小于0.1的,那么此时goober就会播放idle(站立)动画,见:



但是当我们按下了空格键,此时就会立即播放跳跃动画。假如我们将不设置jump的layer值为1,即注释掉这行: 



我们再运行工程,此时我们还是按下空格键,效果如下: 



错误定位到了: 





出错了,为什么呢?由于此时没有设置跳跃动画的layer,导致跳跃动画的layer与idle的layer的值都为0,拥有同样的播放优先级,所以按下空格键时准备播放跳跃动画,但是在下一帧时程序发现此时从marioController 脚本组件中得到的currentSpeed变量的值小于0.1,所以又转为播放idle动画,可是此时在此动画脚本中执行到了上面蓝先的这一行,发现lastJump此时是空的(因为此时没有播放跳跃动画嘛),于是才提示空引用错误。所以工程此时就会暂停,我们可以从上面这张图中得以发现,看,goober此时是不是停在半空中,程序是不是暂停运行了?总结一下:如果某个动画状态的layer越高,执行起来就会比layer值较低的动画状态优先,且只有此动画状态被Stop或暂停时才有可能执行其他动画状态。官方的一个说明是:

Lower layer animations only receive blend weights if the higher layers didn't use up all blend weights.
翻译过来就是:layer较低的动画只能在layer较高的动画没有占用全部混合权重时才有可能收获到混合权重,这也支持了我们的结论。
        关于权重,即AnimationState.weight, 这是一个较容易误解的概念,建议读者事先看看文档上的API解释,我在后面会着重进行讲解的。好了,这次就先讲到这了,下一篇我会重点介绍动画混合,如果允许的话,我还会介绍一下叠加。有兴趣的朋友可以关注一下,给点鼓励吧!





在上一篇,我们具体的讲解了有关动画的融合,也提到了有关动画状态的权重问题。那么这次,我来以一个例子的形式来向大家讲解动画的叠加,或许会涉及到多方面的知识,我力求一次讲清。好了,我们开始吧!

首先我们必须新建一个工程,我取名为:AnimaitonTestTuriol。我们需要一个模型,这里我刚好从从官方的CharacterAnimation这个工程中抠出了一个:Soldier。然后我将其导入到了次工程文件夹下面的,然后简单的错了一个场景,取名为:AnimationAdditive,下图就是我们的工程预览:
c0db2d37-one2a3-40c2-b57d-7one78d5454one22.png 

然后我们新建一个名为CustomScripts的文件夹,用于存放我们接下来要建立的脚本文件。


我们此次的目的是要能够在脚本中熟练运用动画的叠加方法。那么什么是动画的叠加呢?所谓动画的叠加,就是指我们可以在任意动画上面来叠加动画效果。这就非常棒了,因为这项功能可以削减美术为游戏角色创建的动画片段的数量,既减小了工程所占用空间的大小,又对简化了我们的代码编写。官方举了一个例子,大体上是这样的:加入我们有一个角色,具备基本的“站立”,“跑步”,“左倾”,“右倾”的动画,我们还想让其在跑步图中转身时有一种倾斜的效果,比如左转弯时不只是播放跑步的动画,而且还叠加了一个左倾的(或者右倾的)动画。如果我们不用叠加,那么我们势必会多创建两个动画效果:“跑步时左倾”,“跑步时右倾”。一旦有了动画的叠加,我们根本就不需要去做这两个动画片段,厉害吧!那么我们该怎么去实现这个呢?

我们首先要知道怎么去叠加动画,为此,我们必须了解一下这个属性:AnimationState.blendMode,即混合模式。此属性的返回值类型为AnimationBlendMode ,我们看文档:


0e94ccf3-f02a-4f7d-962d-3f8e62774554.jpg 

这里只有两个变量:Blend和Additive。在默认状态下,Animation组件所管理的每个动画状态的混合模式都是:


AnimationBlendMode.Blend,即融合模式,我们只有在程序中才能对其进行相应的修改。如果你将此动画模式修改成了Additive,那么你的这个动画状态就具备叠加到其他动画上的功能了,此时你只需动态的调节这两个动画状态之间的权重就可以产生相应的叠加动画了。下面我来编写一个试验性的脚本来证明我们的猜想,我为这个脚本取名为:


AdditiveAnimaitonTest1,代码如下:


  1. using UnityEngine;
  2. using System.Collections;
  3. public class AdditiveAnimaitonTest1 : MonoBehaviour {

  4.         private AnimationState***n;
  5.         private AnimationState leanLeft;
  6.         
  7. void Start () {
  8.         animation.wrapMode = WrapMode.Loop;//声明各动画状态的播放方式为循环模式

  9.        ***n = animation["***nSlow"];//实例化成员变量
  10.         leanLeft = animation["leanLeft"];

  11.         if ***n)
  12.         {
  13.            ***n.weight = 1f;//设定动画状***n的混合权重为1
  14.            ***n.layer = 10;//让这个动画状态的layer为最高层10
  15.            ***n.enabled = ***e;//启用此动画状态
  16.         }

  17.         if (leanLeft)
  18.         {
  19.             leanLeft.weight = 1f;
  20.             leanLeft.layer = 10;//让此动画状态***n为同一层,又由于此动画状体的混合权重也为1,那么其获得动画混合权重的几率***n一样
  21.             leanLeft.enabled = ***e;//启用此动画状态
  22.             leanLeft.blendMode = AnimationBlendMode.Additive;//将此动画状态设定为叠加模式
  23.         }
  24.       }
  25.     
  26.     void OnGUI() 
  27.     {

  28.         GUILayout.BeginHorizontal();//利用拖动条来控制每种动画状体的权重

  29.        ***n.weight = GUILayout.VerticalSlider***n.weight,1.0f,0.0f);
  30.         
  31.         leanLeft.weight = GUILayout.VerticalSlider(leanLeft.weight, 1.0f, 0.0f);

  32.         GUILayout.EndHorizontal();
  33.       
  34.     }
  35. }
复制代码


上面的代码比较少,可是里面所涉及到的内容却并不那么容易理解。因为这次我们不是靠明显代码来播放动画,而是依靠调节权重来实现的。为了理解这个方法的原理,我们必须明白一个结论:假如一个动画状态被激活了,例如:animation["idle"].enable = ***e ,那么此动画就会处于播放状态(此时没有执行类似:


animation.Play或者animation.CrossFade之类的语句)。不信的话你可以在上面的Start()函数中写下以下语句:


  1. if(animation.IsPlaying("idle")){
  2.        Debug.Log("idle is***nning!");
  3. }


  4. if(animation.IsPlaying("leanLeft ")){
  5.        Debug.Log(" leanLeft  is***nning!");
  6. }
复制代码
我相信第二个if语句中的Debug会被打印出来,而第一个则不会,不信你试试。

好了,我们运行一下游戏吧!


80d8eone3d-cd72-47f8-8d27-d32cb4cone89d6.jpg 

我们试着调节一下Game视图中的垂直滚动条,可以发现,动画的效果改变了:

5d49onef32-boneonea-49aa-a7d8-33262done4dd40.jpg 

我想读者一定按捺不住现在的心情了。我们成功的将左倾动画与跑步动画给叠加起来了,虽然例子很简单,但是我想我们应该可以叠加更多的动画了。希望读者能够认真体会。还有一点,官方CharacterAnimation工程中的第四个场景中的代码完整的展现了动画叠加的精髓,我的这个Solider就是从这个场景中抠出来的,感兴趣的读者不妨分析一下里面的代码,保证获益颇丰。如对本篇难以理解的不让留个言,或者加qq:1597725465,本人一定亲囊相受。下一篇,我准备介绍一下“动画混合”,他是可以将某个动画片段加到角色的某个部位的,非常神奇,但也有一些限制因素。敬请期待我的下一篇文章! 



原文链接:http://www.narkii.com/club/thread-267672-1.html

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值