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,原文地址http://www.cnblogs.com/Esfog/p/Unity_WriteDefaults.html 

Unity的Write Defaults->从一个例子谈起的更多相关文章

  1. spring笔记--使用springAPI以及自定义类 实现AOP的一个例子

    Spring的另一个重要思想是AOP,面向切面的编程,它提供了一种机制,可以在执行业务前后执行另外的代码,Servlet中的Filter就是一种AOP思想的体现,下面通过一个例子来感受一下. 假设我们 ...

  2. unity行为树制作AI简单例子(1)

    用行为树来制作AI是非常方便的,今天就给大家简单介绍一下行为树的强大之处. 所用插件 Behavior Designer v1.421 最开始 我使用过Rain插件,不过用过Behavior Desi ...

  3. ReCap 360 photo照片建模技术的又一个例子

    这是我做的又一个利用Autodesk ReCap 360 照片建模技术做的一个例子.你可以下载模型自己把玩,或者下载原始照片自己试一试. 拍摄工具: 小米手机 照片数量:约120张 后期处理工具: p ...

  4. 从一个例子中体会React的基本面

    [起初的准备工作] npm init npm install --save react react-dom npm install --save-dev html-webpack-plugin web ...

  5. 用thinkphp写的一个例子:抓取网站的内容并且保存到本地

    我需要写这么一个例子,到电子课本网下载一本电子书. 电子课本网的电子书,是把书的每一页当成一个图片,然后一本书就是有很多张图片,我需要批量的进行下载图片操作. 下面是代码部分: public func ...

  6. Erlang 程序引发共享内存 bug 的一个例子

    虽然 Erlang 的广告说得非常好,functional.share-nothing.消息传递,blah blah 的,好像用 Erlang 写并发程序就高枕无忧了,但是由于 Erlang 信奉高度 ...

  7. 对Jena的简单理解和一个例子

    本文简单介绍Jena(Jena 2.4),使用Protégé 3.1(不是最新版本)创建一个简单的生物(Creature)本体,然后参照Jena文档中的一个例子对本体进行简单的处理,输出本体中的Cla ...

  8. 使用flume的一个例子

    新项目中需要使用到hadoop和vertica,使用flume把数据加载到hadoop中,我做了一个例子, 即监控一个sharefolder,如果里面有文件,则会文件load到hadoop. 开启Fl ...

  9. php部分--面向对象三大特性-封装(另加连续调用的一个例子)、继承(重写、重载的例子)、多态;

    一.封装性: 目的:为了使类更加安全. 做法:1设置私有成员 2在类中建方法,访问私有成员 3在方法里边加控制(if) 私有成员访问的两种方法: 方法一:set(可写) get(可读)做方法(可读可写 ...

随机推荐

  1. zuul1.3源码扒一扒(1)

    先开个头吧 作为偶尔点进源码的时候看到东西,或是学到,或是不解,或是惊讶,之后的一些记录.从springcloud各个组件开始吧,计划文段保持间断,只道出核心点,不过各个文段保持连续. zuul作为s ...

  2. Including R code in perl

    #example: use Statistics::R;#use R in perlmy $R = Statistics::R->new() ;$R->startR ;$R->sen ...

  3. MHA-Atlas-MySQL高可用集群2

    MHA脚本管理方式 (1)获取管理脚本master_ip_failover 提示:yum安装的manager是没有这个脚本的. 我们需要从manager的源码包里复制一个.   [root@mysql ...

  4. myEclipse出现cannot paste the clipboard contents into the selected elements报错

    导入jar包报错,cannot paste the clipboard contents into the selected elements,查阅资料让重新打开工程,但依然报错. 最后在本地路径复制 ...

  5. Python中serial的使用

    一.概述     pyserial模块封装了对串口的访问. 二.特性     在支持的平台上有统一的接口.     通过python属性访问串口设置.     支持不同的字节大小.停止位.校验位和流控 ...

  6. django模型二

    django模型二 常用模型字段类型 IntegerField   →    int CharField   →   varchar TextField  →    longtext DateFiel ...

  7. Appium环境搭建——安卓真机调试注意点

    1.安卓设备连接失败 通过adb devices命令 查看安卓设备的连接情况,如图,未成功连接 解决方法: (1)关闭360安全卫士和360手机助手(2)查看5037端口是否被占用 netstat - ...

  8. 闲记 单元格与单元格之间的边 ///字体属性都是font开头,除了颜色属性 ///文本属性都是text开的,除了设置行高。

    这两天一直在做网页没有什么太大的问题,期间也考了一场试,对答案的时候老师讲了一些小知识,因此来记录一下. 单元格与单元格之间的边距(cellspaling) list-type-image可以使用图像 ...

  9. Python练习八

    1.获取移动平均值. def generator(): sum = 0 num = 0 count = 0 avg = 0 while 1: num = yield avg sum += num co ...

  10. robotframework中的清除输入框输入值

    业务需求 当该输入框输入之后,联动某一个按钮高亮,输入框为空的时候,该按钮置灰 需要将输入框清空,清空的办法 1.直接将输入框赋值为${empty} 如:input Text ${loactor} $ ...