Write Defaults是什么?
在Unity的Animator中点击任何一个手动创建的State,我们就会在Inspector面板中看到下图的WriteDefaults选项
(图1,AnimatorState面板)
先说说Write Defaults的作用(文档中语焉不详,下面解释掺杂一些本人的理解,不一定完全正确):
任何一款引擎内部处理动画,都是处理的一个个动画曲线,这些曲线描述了一个某个属性的值随着时间变化的情况。而对于Unity,每个动画曲线只改变一个浮点数,比如位置的修改,其实是包含了三条动画曲线,这里就不展开了,日后有机会写一些相关的Blog。 每个动画文件相当于一个动画曲线的集合,描述了他要修改哪些物体的哪些属性.Unity中的Animator会统计整个状态机中所有的状态一共修改了哪些属性。如果各个动画状态之间修改的属性不一致的时,比如动画A修改了属性X,Y,Z.动画B修改了属性X,Y,W.当状态机从动画A过渡到动画B的时候,对于X,Y属性由于两个动画都包含相应的动画曲线,那么正常插值就可以,对于属性Z,由于动画A含有相应曲线,而动画B不含有,此时对于Z属性的处理就是Write Defaults来决定的。若其勾选(默认勾选)则播放动画B的时候,相当于动画B有一条Constant的属性Z值动画曲线,这个Constant值会被设置为该属性在Animator组件运行前的值(如绑定姿势),此时A和B都有了Z的动画曲线,就可以正常插值了(此处是猜测,具体Unity是否把其当做曲线处理不得而知,从Animator组件上看到的信息上没有看出来)。若不勾选则属性Z会继承动画A播放后修改的值,不进行插值。例子描述
先描述我遇到的问题:项目中有一个弓箭手,正常情况下从它从出生到开始攻击的动画表现应该如下图:
(图2,弓箭手正常动画表现)
偶尔会出现小怪弓箭位置出错的问题,出错的程度也不一致。
(图3,弓箭手错误动画表现)
问题分析
现在就来分下一下原因吧,这个小怪的简化版状态机如图4,其中只有AnyState->Attack01的条件是一个Trigger,其他状态间切换都是正常过渡:
(图4,简化状态机)
1.在我们的项目中所有动画状态默认勾选的WriteDefault都被手动的去掉了。这是因为有些功能需要从前一个状态继承一些当前状态没有的属性值。
2.通过检查max文件发现美术在制作的时候除了Birth动作以外,小骷髅的其它动作针对弓箭挂点的骨骼都没有相应的动画曲线。这就意味着,这些动作的弓箭挂点的位移和旋转是依赖于Birth状态的。3.可见如果从Birth状态到后面状态过渡出现问题的话,就会导致弓箭挂点永远错下去,因为Birth只会播放一次,并且后续状态都不包含对弓箭挂点的属性修改。针对第三点我再多说些:
我发现Stand动作也没有弓箭挂点的相关动画曲线,但是Birth->Stand的过渡是没有问题的。为什么呢?来看一眼图5:(图5,Birth->Stand的融合配置)
可以看到Birth->Stand的动画过渡区间配置的比较合理,保证了两个动画在插值过程覆盖了Birth动画的全部长度。单独观察Birth动作发现,当动画到达上图白线位置的时候,弓箭挂点就已经到达了正确的旋转和位移,之后不再有变化。所以理论上只要动画过渡区间覆盖到白线就能保证不出错。
既然弓箭丢失不是Birth->Stand过渡引起的,那就应该是从AnyState到Attack01这个状态过渡的问题了,由于AnyState的特殊性,所以没有勾选HasExitTime,这导致此时的过渡情况就很微妙,在图6中所看到的过渡区间并不是真正的过渡时间,由于官方没有相关的说明,自己做了些测试,假设图6中过渡区间占了Birth动作条的15%,而在触发AnyState->Attack01切换条件的时候,Birth已经播放到了10%.那么实际上接下来Birth动作参与动作融合的部分是其动作条的10%到25%的阶段,即实际的融合区段取决于前置状态的播放进度。
(图6,AnyState->Attack01的融合配置)
因为Attack01状态的WriteDefaults是没有勾选的,那就意味着它对于弓箭挂点的位置和旋转完全依赖于前置动作在融合过程中最后一帧的值。这就能合理的解释为什么有时候弓箭不会脱手,有时候脱手,且脱手程度又不一致的问题了。正是因为AnyState->Attack01的触发条件Attack01的触发时间并不完全一致导致的。如果碰巧触发的时机合适,其融合区间包含了弓箭挂点到达正确位置和朝向的部分,那么看上去就是对的,
以上的这些分析都是基于一个前提,就是Birth动作没有播放到合适的时机就被切换了。你也许会奇怪为什么不保证Birth播放完了再进行状态切换呢。这就得问策划了。在我们的游戏里,小怪有一个出生的范围,到达了出生范围就会生成小怪,状态机自动播放出生动作;还有一个攻击范围,如果进入了攻击范围,就会播放攻击动作。很显然如果角色的移动速度很快,或者生成范围和攻击范围相差太小,就会导致小怪刚一播放出生动作还没播完,Attack01的触发器就会被激活。也就可能出现弓箭脱手的问题了。
多说一句,这个问题只出现在远程兵身上,近战兵从来没有发现过武器脱手的。大家可以想想为什么。
解决方案
既然为分析出来了,解决的办法有很多,但是如果要治本,还是要从动作资源本身上解决这个问题。勾选WriteDefault是可以解决这个问题的,但又会导入新的问题,这个不行,我们在文章开始解释WriteDefault的那个例子中,我们只要让动作B也包含Z属性的曲线,这样WriteDefault这个选项就失去了意义,不过这样会让Attack01动作引入新的动作曲线。但由于在这个本例中,我们并不需要在Attack01动作过程中对弓箭挂点有什么修改,所以只要让它有一个绑定姿势的常量值就好了。Unity也会对常量曲线进行优化,所以问题并不大。
啰嗦了这么多,不知道有没有把这个问题讲清楚。Unity的Animator这块内容,用起来上手很快,但它实际涉及的内容量还是很大,Unity尝试对个各个概念去包装,让大家能容易理解,但就我的经验来看,他们做的还不够,无论是文档,还是使用界面都还有待优化。本文中很多分析依赖于我个人的实验和总结,不保证完全正确,希望大家留言批评指正。
尊重他人智慧成果,若要转载,请注明作者esfog,原文地址