本章节,我们继续讲解Unity的 Mecannim 动画系统。在上一章节中,我们设置了动画过渡,但是还没有为这些动画过渡设置过渡条件。这个过渡条件需要在“Animator”窗口中设置。这个窗口的左边是用来编辑“动画层”和“动画参数”的。这里的“动画参数”就是我们所指的“过渡条件”,也就是说这个条件是通过“动画参数”来实现的,我们先点击“Parameters”,看看它的样子,如下所示:
这是一个参数列表,目前是空的,我们需要点击右上角的“+”来添加参数。
我们点击之后,会有一个弹框,看名字能猜出来,这就是四种数据类型。Float是小数类型,Int是整数类型,Bool是布尔类型,Trigger是触发类型(也可以理解为布尔类型)。那我们选择哪一种呢?我们先选择第一个Float小数类型试试吧。
我们给这参数变量重新命名为“walk_run”,它的默认值是0。有了这个“walk_run”的过渡条件(动画参数)后,该如何设置过渡呢?我们点击idle2到walk2的过渡线(蓝色显示),然后查看Inpector检视视图,
两个状态机(idle2和walk2)的切换默认是动画播放完毕,也就是ExitTime。但是这个时间是可以修改的。也就是说,当动画A切换到动画B的时候,我们可以控制动画A播放到那个进度的时候切换到动画B,不用等到动画A完全播放完毕再切换到动画B。这个控制可以在检视面板中清楚的可视化修改。也就是ExitTime后的数值。当然我们有时候,也希望idle2和walk2可以随时切换,而不是必须等待播放ExitTime时间。此时我们就需要取消HasExitTime勾选项。也就是说,默认情况下是,播放完一个动画之后才能切换另一个动画。但是如果我们去掉HasExitTime选项后,可以随时打断当前动画立即执行下一个动画。我们举一个例子,大家就明白了。假如存在Run(跑步动画)到Attack(攻击动画)的动画过渡,一般情况下是,播放完Run之后才播放Attact。但是这种设定显然不太合理。玩家发送攻击后,就应该立刻攻击的,不应该等Run动画播放完毕才执行攻击动画。因此,我们就需要去掉HasExitTime选项。而Attack到Run的动画过渡,则应该勾选HasExitTime选项。因为必须攻击动作全部完成,才能切换到Run状态。我们不可能攻击动画执行一半就切换到Run状态,这个是不合理的。没有那个玩家攻击动作完成一半的时候,就切换到Run状态。
动画过渡检视面板中时间帧中,两个动画片段之间有重叠,其实是为了让两个片段有一个淡入淡出的过渡效果,而不是硬生生的从一个动画突然变成另一个动画。重合部分就是淡入淡出的效果。当然,我们可以手动拖动,改变这个淡入淡出的范围。
在上面截图中,我们主要关注一个“Conditons”项目的设置,它是一个列表,可以通过右下角的加减号来进行编辑,我点击“+”号试试。
点击完毕后,我们就明白了,这里就是选择我们刚刚添加的“动画参数”变量的。同时,我们还可以在这里对“动画参数”变量进行逻辑判断。例如,我们可以设置“walk_run”等于“1.0”的时候,使得我们当前的idle2到walk2的过渡生效,也就是从idle2动画切换到walk2动画。如何设置呢?在上面的新增加的条件中,有三个项目可以设置,第一个就是“动画参数”变量的选择,我们默认“walk_run”即可;第二个是操作符,我们也默认“Greater”(大于)即可;第三个就是操作值,我们也默认0即可。这三个设置就表示当“walk_run”变量值大于0(例如数值1)的时候,就可以从idle2动画切换播放walk2动画了。
接下来,我们在点击idle2到run2的过渡线(蓝色显示),然后在Inpector检视视图中设置条件,
这里我们设置的操作符是Less,也就是小于的意思,也就是说当“walk_run”变量值小于0(例如数值-1)的时候,就可以从idle2动画切换播放run2动画了。到目前为止,我们已经设置完了“过渡”以及“过渡条件”,关于这里的“Conditons”,我们再补充一点。我们可以添加多个“动画参数”变量共同完成这个过渡条件,这些变量直接是逻辑且的连接关系哦。
接下来,我们创建一个“GenericAnimationScript.cs”的脚本文件。然后使用代码来控制“walk_run”变量值来进行动画片段的切换播放了。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenericAnimationScript : MonoBehaviour
{
// 新版动画播放组件
private Animator animator;
// Start is called before the first frame update
void Start()
{
// 获取Animator组件
animator = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
animator.SetFloat("walk_run", 1);
}
if (Input.GetKeyDown(KeyCode.S))
{
animator.SetFloat("walk_run", -1);
}
}
}
代码非常简单,我们同样需要获取Animator组件,然后使用它的方法来设置“动画参数”变量值。因为我们的变量是Float类型的,因此使用SetFloat方法。如果我们是Int类型的,就需要使用SetInt方法,依次类推。接下来,将脚本挂载到游戏对象Elf上,并运行工程。
当我们按下A键的时候,就完成了idle2到walk2的动画过渡,我们的模型就会循环播放walk2的动画。如果此时我们按下S键,它是不会播放run2动画的。这是为什么呢?因为我们按下S键的作用是从idle2到run2的过渡,而不是walk2到run2的过渡。那么,问题又来了,我们得添加walk2到run2的过滤线,并为此过渡线添加过渡条件(动画参数)。而且,我们还得需要设置walk2到idle2的过渡,因为角色一定存在走路(walk2)完毕后,进入休闲(idle2)状态的时候。这样的话,我们的“动画控制器”中,就可能形成一个网状结构,每一个“动画状态机”之间来来回回的建立过渡线,并且还需要为每一条过渡线设置条件。但是,好在这个过程是可视化的,我们只需要拖拖拽拽就完成了,不需要通过代码来完成。
接下来,我们再来说一说动画播放的其他内容。如何获取当前播放的动画信息。我们可以通过Animator组件来获取AnimatorStateInfo对象,该对象包含了动画状态(动画片段)的所有信息。代码如下:
if (Input.GetKeyDown(KeyCode.A))
{
animator.SetFloat("walk_run", 1);
AnimatorStateInfo current = animator.GetCurrentAnimatorStateInfo(0);
bool isWalk = current.IsName("walk2");
if(isWalk) Debug.Log("walk2");
}
运行结果如下
代码animator.GetCurrentAnimatorStateInfo(0); 方法的参数就是动画层的索引,从0开始,默认层的索引就是0。如果添加了新的层,则索引就依次加1。current.IsName("walk2"); 判断当前播放的动画是不是walk2动画,walk2就是行走动画片段名称。另外,AnimatorStateInfo对象还有一个speed只读属性,用来表示动画播放的速度,默认值为1,表示正常播放。因为它是只读属性,因此,我们不能通过它来改变动画播放速度。我们可以通过Animator的speed属性来控制动画播放速度。同样,1 为正常播放速度。如果我们设置大于1的话,动画会加速播放,小于1的话,会减慢播放。如果是0的话,会停止播放。如果-1的话,会倒序播放。例如,我们想要动画播放速度增加1倍,可以这样设置:
animator.speed = 2.0f;
最后,我们来对比一些新旧两套动画系统的区别。就的动画系统使用的是Animation组件,新的动作系统使用的是Animator组件。这是第一个大的区别。其次,旧的动画系统比较简单,只是在Animation组件中设置所有的动画片段即可,然后由代码来控制各个动画片段的切换逻辑。而新的动画系统则是将这个“代码切换逻辑”放到了“动画控制器”中完成,并且提供了Animator窗口来进行可视化的编辑。在Animator窗口中,我们可以编辑所有动画片段之间的过渡以及过渡条件,这个过渡条件也比较多简单,基本的就是通过“动画参数”变量来实现的。很明显,这种设计取代了我们之前的“纯代码”的控制方式,使用图形化来展示各个动画片段是如何切换过渡的。虽然,这种设计并不能大量减轻研发人员的工作,但是这种清晰可见的设计方式,却可以帮助我们梳理切换逻辑,最大程度的减少出错的几率。
本课程涉及的内容已经共享到百度网盘:https://pan.baidu.com/s/1e1jClK3MnN66GlxBmqoJWA?pwd=b2id