小生涉猎不深,写下此文仅方便自己记忆和入门,深知学艺不精,如有错误还请不吝赐教!
一维混合树(Blend Tree)
在前面的文章中有提到利用BlentTree来对相似状态进行整合,进而通过一个parameter来控制,最终呈现出两个动画间柔和过渡 / 缓动的效果,并且提高了FSM的可读性,降低了维护复杂度。
Behaviour
在几乎绝大部分的情况下,通过Animator,我们希望实现的当然不仅仅是Animation的切换。例如现在有一个从“ground”到“jump”的切换,我们希望实现的不仅仅是从地面移动的动画切换到跳的动画,还会希望给人物增添一个向上的速度,不然完全就是原地做了个跳跃的动作,高度却没有任何变化惹,很怪。
在以前的时候,常用的做法就是判断跳跃键按下的同时,一方面SetTrigger实现状态切换,另一方面给角色增加一个速度Vector3。这样的做法在Animator不复杂时还是可行的,然而一旦FSM复杂起来,这样的写法说白了就是写得太死了,很容易出现未定义行为(哈哈就是BUG啦)。
于是这里用到的就是Animation中的Behaviour啦!我事先声明,这是一个低效但是方便管理的方法,高效且可维护性高的方法小生暂时还没学到。
当创建一个Behaviour Script后,会看到已经提示我们有五个函数可以配合FSM使用,这里主要用到其中的三个,它们的功能分别是:
- OnStateEnter:当进入到这个状态(State)时,执行一次该函数
- OnStateExit:当离开这个状态时,执行一次该函数
- OnStateUpdate:进入这个状态后,在OnStateEnter和OnStateExit被执行之间,每Update一次,这个函数就执行一次
这三个接口很直观,以至于很容易想到刚刚说的起跳要怎么实现:重写“jump”状态中的OnStateEnter接口,获得角色的rigidbody,给它增加一个向上的速度嘛。
对,但不完全对。
问题出在什么地方呢?可维护性。如上文所说,这是在FSM逐渐变得庞大的情况下给出来的一个解决办法,而Behaviour是绑定在某个状态上的。那么假如所有的Behaviour都涉及到了具体的数据,出了BUG打算怎么办?一个个状态点开,一个个Behaviour点开,人型DEBUG机吗?很显然这样是不行的。于是想出了以下这个很笨但却是有所改善的方法。
SendMessage。
对,具体需要执行的函数写在另外的,相关的scripts中,而不是这样零散地分散在Behaviour中,Behaviour中的函数只负责实现“通知执行某个函数”的功能,像这样:
没错!你的质疑很对,SendMessageUpwards / SendMessage 由于利用到了反射的实现机制,因此开销异常大,但至少!在可维护性上,它成功了。先实现,再优化嘛~
Layers
假如FSM已经够复杂了,但是距离你要实现的效果还差很多状态怎么办?好说,再来一个FSM!可是一个模型不能只能挂载一个Animator,该怎么再加FSM?我猜你可能想到了子状态机吧。但是这里要介绍的是另一个东西:Layers。
这感觉其实有点像搞卫生……桌面看起来太乱了怎么办?拿张纸给它铺上,在纸上办公好了……
像这里,Base Layer已经够乱了,但我还完全没有做人物攻击的FSM诶,那就再给他加一层,在新的一层里负责攻击动画的切换好了。
看起来很赞。但是问题又来了:两个状态机都有Entry,都有各自的Default状态,那我的人物怎么知道自己现在要用到那一层的FSM?这里给出的一个建议是:不要在一层FSM里放入不相关的状态,这样就能够实现一个效果,当我需要人物执行某一类的动画时,再启用那一层的FSM好了。
没错这就是不同Layers之间切换的逻辑。点选某个Layer上的小齿轮,会看到一个叫做“Weight”的属性,姑且翻译为权重好了。如果权重为0,说明这一层的FSM完全被无视;如果权重为1,那么这一层的FSM就完全投入使用啦。
但是,很重要的一点是,不同层之间的weight并不是一个互斥的关系,也就是说,所有层的权重各自独立。那么假如两个层的权重同时为1怎么办?此时要注意到Blending属性,来设置不同layer间的混合关系,是覆盖还是相加呢?看你的需求啦。
插一个小tip:如果希望在两个layer切换时实现缓动的效果要怎么做?答案就是对Weight进行lerp啦,不要一下拉满就好了。
动画切换的一些小知识
-
设置动画切换的优先级
有时候状态切换不如意,可能仅仅是因为多个转态条件同时满足了,而没有设定好优先级噢。 -
interruption source
比起转态同时满足,更棘手的情况是在转态过程中,另一个转态条件又满足了,而偏偏这个新的转态优先级更高。举个例子,角色在翻滚的过程中受到了攻击,此时总不能翻滚结束了再做出受击动画吧?因此要使用到interruption source这个属性,来设置当发生冲突时,状态应该作何改变。