当前位置: 首页 > news >正文

【unity知识】OnAnimatorMove+root motion,Root Motion+Blend Tree,解决Animator动画和位移不同步问题

文章目录

  • 动画和位移不同步
  • 问题
  • Humanoid动画中的Root Motion机制及相关配置
  • unity把root motion划分成了三个部分
    • root transform rotation旋转
      • bake into pose
      • loop match
      • based upon
    • root transform position沿y轴的位移
      • bake into pose
      • based upon
    • Root Transfom Position (XZ)是沿x和z轴的位移
    • 总结:
  • 走着走着角色下沉问题修复
  • 还有一个问题就是走着走着角色歪了
  • Root Motion+Blend Tree
    • 那么我们怎么知道这里的root motion的实际位移速度是多少呢?
    • 自动计算出阈值
    • homogeneous speed自动的计算播放速度,使前进的速度和后退的速度一致
    • 我们来控制角色的移动速度
    • human scale人体的缩放导致人物移速不一样
    • root motion的移动不一定是匀速的
    • 把移动的控制权从root motion手中拿回来
    • 在使用了root motion之后rigid body上的重力不起作用
  • 参考
  • 完结

动画和位移不同步

在游戏开发中,我们常见的动画文件一般分为两种:
一种是in place动画,也就是不带位移的动画
另一种是root motion动画,也就是自带“根位移”的动画

这种所谓自带“根位移”的动画能够带来的好处是显而易见的,它有效地避免了角色动画和实际位移不同步的现象

如果在项目中使用自带位移的动画,需要勾选ApplyRootMotion即可
在这里插入图片描述
比如使用代码控制人物前进后退

public class PlayerRootMotionTest : MonoBehaviour
{// 定义Animator变量Animator animator;// Start方法在脚本开始运行时调用void Start(){// 获取Animator组件animator = GetComponent<Animator>();}// PlayerMove方法在接收到输入时调用public void PlayerMove(InputAction.CallbackContext context){// 获取输入的方向值(Vector2类型)Vector2 vector2 = context.ReadValue<Vector2>();// 如果Y轴方向的值大于0(向前)if (vector2.y > 0f){// 设置Animator的"向前进"参数为trueanimator.SetBool("向前进", true);}else{// 否则,设置"向前进"参数为falseanimator.SetBool("向前进", false);}// 如果Y轴方向的值小于0(向后)if (vector2.y < 0f){// 设置Animator的"向后退"参数为trueanimator.SetBool("向后退", true);}else{// 否则,设置"向后退"参数为falseanimator.SetBool("向后退", false);}}
}

在这个脚本里,我们完全不需要处理角色的位移

问题

角色似乎并没有走直线,我们只是按了前进和后退,她居然转向了
而且似乎角色的高度也在下降,脚都陷到了地面以下,后面我们再一起解决

在这里插入图片描述

Humanoid动画中的Root Motion机制及相关配置

root motion会把动画文件中描述的对游戏对象的坐标和角度值转换为相对位移和相对转角,并以此来移动游戏对象,而在generic动画中root motion会把动画文件中描述的根骨骼坐标值和角度值转换为相对位移和相对转角,并以此来移动游戏对象

那么到了humanoid动画里,由于使用了Avatar系统,动画文件不再包含对具体骨骼的描述我们自然也就无法通过指定根骨骼来应用roo motion

在humanoid动画中通过分析骨骼的结构,计算出模型的重心center of mass,这个重心也可以被称为body transform,在预览动画这里激活这个选项,可以在这个位置看见它的重心
在这里插入图片描述
大家可以在脚本中通过animator.bodyPosition和bodyRotation来访问它的坐标和方向
我们也在OnDrawGizmo方法把它绘制出来,方便观察

在这里插入图片描述
在这里插入图片描述
我们可以把这个unity计算出来的root transform,当做humanoid动画下代表root motion的根骨骼节点,通过这种方式,我们就可以在不同的骨骼结构上复用同一个root motion动画
在这里插入图片描述

unity把root motion划分成了三个部分

在这里插入图片描述

root transform rotation旋转

要注意一下,root motion的旋转特指绕y轴的旋转,而不是游戏对象移动的一部分,这里的bake into pose的字面意思是把旋转当做动画(pose)的一部分

bake into pose

勾选表示root motion不使用动画自带的旋转

loop match

这里的这个红色标记代表了这个动画在角度上的吻合度
在这里插入图片描述
如果这个动画在播放中的转角过大,这里就会提示红色,告诉我们这个动画如果勾选了bake into pose则会带来不好的效果,比如我们可以在这里看见的来回旋转

而对于动画过程中转角较小的动画,比如说行走这里就会提示呈绿色,告诉我们可以勾选

总结:
要不要勾选这个选项取决于我们需不需要动画来,驱动游戏对象的旋转 而至于能不能勾选,则要参考这里的红色或绿色提示

based upon

这里说的是游戏对象在动画开始时对准的方向是哪里

Generic和humanoid动画这里的参数有所区别,唯一区别是,Generic动画里的root motion是基于根骨骼root node的,所以这里可以把根骨骼的朝向当做动画的朝向,而humanoid的动画并没有根骨骼,所以Unity就把计算出来的重心body transform的方向放在这里供我们选择
在这里插入图片描述
由于humanoid的方向是Unity计算出来的,所以一般情况下也不太准,所以大家如果是使用Mixamo上的动画的话,这里可以选择original
在这里插入图片描述

root transform position沿y轴的位移

bake into pose

与上面类似

based upon

是垂直方向上把模型的哪个位置对齐到游戏对象的原点上,默认是original,也就是3D美术设置的动画原点,我们一般就选择这个,这个center of mass就是重心

在这里插入图片描述
也就是我们之前一直提到过的用一体计算出的那个body transform,选择它的话可以看到游戏对象的位置对齐到了重心的位置
在这里插入图片描述
Feet就是模型的脚,或者说Avatar系统下的脚,在generic动画中是没有脚这个慨念的,它只是处理单纯的骨骼而在humanoid动画中,借助Avatar我们是可以明确的知道每一块的骨骼对应人体的结构

我们选择feet试一下,可以发现它和original的差别并不大
在这里插入图片描述
那么这个feet和original的区别在哪,以及我们应该选择哪一个呢
我们可以相信Mixamo中的original,因为这个original就是动画美术为我们提供的原点
但是那仅限于generic动画,而在humanoid动画中,因为动画的复用会使得动画发生一系列的变形
简单来说,动画原本是针对A模型制作的,那么这个original或者说原点就是A模型播放动画时的原点
当我们使用B模型复用这个动画时,我们没有办法保证Avatar转移后的原点,还能够保持其原来的准确性
所以当我们选择original时,如果发现效果不佳,除了调节下面的offset之外还可以考虑这个feet选项

比如我们当前选择的这个idle动画,在选择original时,角色的脚已经陷到地面以下
在这里插入图片描述
我们改成feet试一下,这样就好很多了
在这里插入图片描述

Root Transfom Position (XZ)是沿x和z轴的位移

就是root motion在水平平面上移动的设置,如果我们不希望这个动画控制角色在水平方向上的位移就勾选这里,那么比如待机动画,原地转身动画,原地起跳动画都可以勾选
在这里插入图片描述
再来看看最后一个root transform position XZ的based upon这里的center of mass指的就是重心在水平平面的投影位置
在这里插入图片描述
当使用Mixamo上的动画时,这里也可以选择original

总结:

旋转、垂直方向的位移和水平平面上的位移我们可以通过这里的bake into pose来选择哪些部分会被当做root motion来控制游戏对象,那些部分仅被当做动画的一部分,不控制游戏对象,在选择完这里之后,还可以通过下面的配置来获得我们希望的动画效果,比如调整方向或者对齐的模式

走着走着角色下沉问题修复

这就是不该处理垂直位移的动画在控制角色对象的垂直位移
把他们的root transform position Y的bake into pose给勾上
在这里插入图片描述
我们开始游戏看一下,问题已经解决了

还有一个问题就是走着走着角色歪了

这就说明不该处理旋转的动画在处理游戏对象的旋转或者说它初始的方向就是错误的,我们观察一下前进动画发现果然是歪着走的
在这里插入图片描述
那么我们先把它的root transform rotation的bake into pose给勾上
在这里插入图片描述
看一下,它现在偏移的角度没有原来那么大了
在这里插入图片描述
但是我们可以发现它初始的方向其实就已经是错误的,在这里把他的based upon修改为origina
在这里插入图片描述
现在问题就解决了
在这里插入图片描述
如果切换成original还是有点歪的话,大家可以手动调整一下这里的offset
在这里插入图片描述
我们可以看到现在角色不会走着走着方向就偏
在这里插入图片描述

Root Motion+Blend Tree

这里最合理的参数值应该是动画自己的移动速度
在这里插入图片描述
比如前进的root motion动画的位移速度如果是1.8,那么我们就应该把这里的阈值设置为1.8
这样在我们代码里的参数就可以直接反映出root motion的最终移动速度

那么我们怎么知道这里的root motion的实际位移速度是多少呢?

可以看这里的compute thresholds自动计算阈值,它的意思就是根据root motion中某些数值自动计
算这里的阈值,打开之后,我们可以看见六个选项

在这里插入图片描述
接下来就简单介绍一下剩下的这几个:

speed是root motion的移动速度或者说它的速度的绝对值,也就相当于下面这个三维向量velocity的长度

angular speed则是root motion的旋转速度。第一个单位是弧度每秒,第二个单位是角度每秒,如果你的角色需要使用root motion控制转身的话,会用到他们

Velocity这三个分别代表了root motion在xyz三个方向上的位移速度,我们这里用的动画是待机、前进以及后退,所以我们只关心Z方向,也就是“前后”方向上的位移速度

自动计算出阈值

所以我们可以选择第四个velocity Z,现在这里已经为我们自动计算出阈值了
在这里插入图片描述
我们已经知道这里的阈值其实就是这个动画在z方向上的速度
那么也就是说这个前进动画root motion的速度大约是1.565,后退大约是-1.3,待机这里可以看到是3.6以10的6次方,基本上就是零

这样一来在代码里只需要将移动速度设置到1.565或者-1.3,那么理论上root motion就会以相应的速度带着我们的角色前进或者后退
在这里插入图片描述
现在我们已经可以顺利的在blend tree中使用root motion了
在这里插入图片描述

homogeneous speed自动的计算播放速度,使前进的速度和后退的速度一致

现在这里的前进和后退速度是有一定差异的
假设我现在希望前进的速度和后退的速度一致,那么我可以调整后面的这个播放速度

比如这个后退的移动速度比较慢,那么我们就把它的播放速度调快一点,我们需要挨个计算这里的播放速度应该设置为多少,好在unity在下面为我们提供了自动的计算功能
在这里插入图片描述
homogeneous speed,unity就会自动为我们计算出这里的播放速度,待机动画因为不会位移,所以是不需要计算这个数值的,我们可以把它的播放速度设置回1

Reset Time Scale可以把播放速度都调回1,也就是重置
在这里插入图片描述
当然我们也可以把待机动画从blend tree里面删掉,再计算一次,然后把它拖回来.这样排除了原地不动的idle,计算出的播放速度的数值会更合理一些
在这里插入图片描述
再开始游戏试一下,前进的速度慢了一点,后退的速度则加快了一点,这样前进和后退的速度就一致了
在这里插入图片描述

我们来控制角色的移动速度

角色现在的移动速度不是由我们决定的,而是由root motion决定的,如果我希望前进的速度是2或者1,那我该怎么做呢?

聪明如你也许已经想到了解决的方法,我们可以通过调整这里的播放速度来控制移动速度
比如,如果我希望前进的速度是1,那么只需要把这个播放速度改成1除以1.565即可
在这里插入图片描述
那么当我把状态机里的speed参数设置为1.565时,角色就可以以1的速度前进

human scale人体的缩放导致人物移速不一样

很遗憾,问题要比我们想象的更复杂
我们先把播放速度调整回,实际上当这里的参数为1.565时root motion的移动速度也极有可能不是1.565
在这里插入图片描述
这是为什么呢?,因为humanoid动画是在不同的人物骨骼上复用动画的一种机制,同样的行走动画在小一点的角色身上自然是要走的慢一点,再大一点的角色身上就会走的快一点

这里放一个矮子,可莉和明日香使用相同的状态机和同样的控制脚本
在这里插入图片描述
在播放同一个前进动画时,明日香走的比可莉要快多了

最稳妥的方式就是为不同的人物制作属于自己的状态机,然后针对性的设置动画状态

但是今天我们就是要让可莉和明日香共用一个状态机

第一步我们先来找到这两个人速度不同的原因
在animator下有一个human scale属性
root motion会考虑物体的缩放值scale
Scale越小,root motion的位移就会越小
也是由于两者的大小不同或者说缩放值不同而导致的

human scale记录了Avatar在制作骨骼映射时,对人体或者说骨骼的缩放

那么为了解决这个问题,我们只需要把它们的动画播放速度除以相应的值就可以了
在这里插入图片描述
可以看出通过human scale修正动画的播放速度之后,现在两人的移动速度已经一致了

在这里插入图片描述
但是我们不希望整个animator的播放速度都受到影响,那么我们就可以在动画只进入这个blend tree之后才改变speed

怎么做呢,还记得动画状态有这个multiplayer属性吗
在这里插入图片描述
我们只需要把这个值传给multiplayer就好了
在这里插入图片描述
这样,只有这个blend tree播放的速度会受到影响,其他的动画则正常播放

root motion的移动不一定是匀速的

移动的速度并不稳定,这是因为root motion的移动不一定是匀速的
我们之前看到的1.565,其实是这个移动速度的平均值
在这里插入图片描述
可以查看动画曲线
在这里插入图片描述
仔细观察,我们可以看出这里的曲线并不是线性的,也就是说该root motion并不是匀速运动的,至少在我们现在关注的Z方向上并不是匀速运动

这也就是说明在使用某些root motion动画时,我们是无法保证角色匀速移动的
而且我们也可以看出该动画在x方向上多多少少也是有一些位移的

当然大多数开发者并不要求百分百的精准的移动,这种程度已经可以足够满足我们大家的需求了,适当的不精准,反而可以被当做是有灵魂的。

把移动的控制权从root motion手中拿回来

我们引入root moton是为了避免实际位移和动画表现的位移不同步,这个root moton原本要解决的问题就是同步,而不是位移

既然如此,我们就应该把移动的控制权从root motion手中拿回来了,我们给角色绑上一个刚体rigid body,rigid body的主要作用是用来模拟各种物理计算

如果我们在启用了root motion的游戏对象上使用OnAnimatorMove方法
unity就不会用动画来驱动游戏对象的移动,转而把控制权交给我们的脚本
在这里插入图片描述
我们把这个move方法从update方法里移到on animator move方法里,然后在move方法把animator.velocity赋给rig.velocity
在这里插入图片描述
它的具体调用时间在fixed update和动画系统的各回调方法之后在物理计算之前
在这里插入图片描述

在使用了root motion之后rigid body上的重力不起作用

把对应root motion动画上垂直方向的位移bake into pose就可以了
在这里插入图片描述
这个位置一定要调整成animate physics,让动画与物理引擎同步刷新

在这里插入图片描述
也可以代码控制,让它的x和z分量来自animator,而y分量则来自于rigid body
在这里插入图片描述

参考

https://www.bilibili.com/video/BV1Pr4y1q7V1/

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • jupyter 安装新内核后报找不到已安装的包
  • Java | Leetcode Java题解之第392题判断子序列
  • 【Python123题库】#通讯录(文件读取) #利用数据文件统计成绩
  • 《深度学习》OpenCV轮廓检测 轮廓近似、模板匹配 解析及实现
  • STM32常用C语言知识总结
  • ubuntu搜狗输入法取消切换繁体
  • HTTPS 协议“加密和解密”详细介绍
  • 论文精读:Dirac半金属反常能斯特效应设计
  • 【maxcompute|ODPS|SQL|HSQL】日期数据非标准日期格式(yyyy/M/d),如何转为yyyy-MM-dd HH:mm:ss标准格式
  • 基于约束大于规范的想法,封装缓存组件
  • sqlite3的db.interrupt方法深入解析
  • 台球助教APP小程序的前端交互设计
  • error:0308010C:digital envelope routines::unsupported【超详细图解】
  • 【C# ASP.NET Vue】没想到吧!怀旧小筑客栈管理系统可以这样高效,集成MySQL数据库,一键管理客房,预订不再是难题
  • 9月3日复盘日记
  • [Vue CLI 3] 配置解析之 css.extract
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 30秒的PHP代码片段(1)数组 - Array
  • C++类的相互关联
  • GitUp, 你不可错过的秀外慧中的git工具
  • HTTP--网络协议分层,http历史(二)
  • iOS | NSProxy
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • rc-form之最单纯情况
  • react-native 安卓真机环境搭建
  • 包装类对象
  • 订阅Forge Viewer所有的事件
  • 利用jquery编写加法运算验证码
  • 设计模式 开闭原则
  • 无服务器化是企业 IT 架构的未来吗?
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • #include
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (solr系列:一)使用tomcat部署solr服务
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (排序详解之 堆排序)
  • (自用)网络编程
  • ./configure、make、make install 命令
  • .chm格式文件如何阅读
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET 直连SAP HANA数据库
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • @Builder用法
  • @我的前任是个极品 微博分析
  • [ CTF ] WriteUp-2022年春秋杯网络安全联赛-冬季赛
  • [16/N]论得趣
  • [3300万人的聊天室] 作为产品的上游公司该如何?
  • [C#学习笔记]Newtonsoft.Json
  • [C][栈帧]详细讲解
  • [C++从入门到精通] 14.虚函数、纯虚函数和虚析构(virtual)
  • [Django 0-1] Core.Handlers 模块