游戏开发技术论坛

 找回密码
 立即注册
搜索
查看: 14156|回复: 0

从搭建到优化,《永劫无间》如何做游戏动作与运动系统

[复制链接]

4万

主题

4万

帖子

7万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
76813
发表于 2021-5-13 14:07:14 | 显示全部楼层 |阅读模式
五一节前,我们举办了首次「Unity 大咖作客」线上分享会,邀请到 24 Entertainment 资深客户端开发工程师姚金毅和 Unity 大中华区技术经理高川,以动作动画为主题展开分享。

微信图片_20210513140452.jpg

应粉丝们许愿,首场「Unity 大咖作客」线上分享会的文字版本来啦。本文精选了两位嘉宾分享的部分内容,如果想要观看完整版以及现场 Q&A 环节的精彩内容,欢迎前往 B 站。



《永劫无间》的动作与运动系统

大家好,我是来自 24 Entertainment 工作室的姚金毅,今天给大家带来的是《永劫无间》的动作与运动系统。

首先,我们简单介绍一下我们游戏以及我们怎么利用 Unity 引擎进行开发的。我们《永劫无间》是一款多人动作类的端游。在整个开发过程中,我们对引擎做了很多自定义的开发,Unity 官方的技术支持部门以及一些驻场工程师也对我们的开发过程给予了非常大的帮助,尤其是帮助解决一些疑难杂症的问题。

今天,我主要分享一下动作和运动系统方面的经验和心得。

01、动作系统总览

《永劫无间》的动作系统主要是使用了 Unity 里面的人型骨架系统,也就是 Humanoid 的动作。我们目前有两个英雄体型,有少量的非英雄体型的怪物,动作 state 的数量大概有 2000 多个,动作片断目前有 9000 多个,正式上线可能会突破 1 万个,因为我们还在不断地制作新英雄、新武器。

我们的动作既有动捕的,也有美术手工制作的。使用动捕比较多的是一些角色的受击和角色的死亡动画表现上面。因为这部分的动作我们希望把它做得尽量地真实、丰富、流畅一点。

我们还较多地使用了分层动画的系统。就像我们的英雄目前有 10 个 layer,可以说非常多了,包括基础层、左右手、头家左右手、上半身、手指层等等的。比较常见的比如下半身跑路,上半身做一个射击瞄准之类的。

我们在做动画系统的时候通常会涉及到一个主题是我们如何控制角色位移的。两种主流的做法,一个是用动作本身来启动,也就是开启 RootMotion;另外一个是用程序里面计算角色位移,去应用它的位移。整体上来说,在我们游戏里面绝大多数情况是使用 RootMotion 的。

首先是我们的动作美术具有非常丰富的动作游戏开发的经验,他们能把一个角色的位移比如一个简单的跑步能做得更加有节奏感,表现得更加真实,所以说这部分的位移的主动权都是交给美术的。程序位移也有小部分的一些应用。比如跳跃需要根据角色跳的长短做不同高度的跳跃,包括飞索可能有不同速度、不同方向的飞行,一些受击的位移需要配置不同的参数表现不同距离的位移,不同时间长短的硬直之类的。这部分的位移是靠程序进行驱动的。

微信图片_20210513140459.png

02、Playable API 构建底层动作控制系统

我们知道在 Unity 里面 Animator 构建一个动作状态机是非常方便的,你可以直接在 Animator Controller 里面创建一些动作 state,添加一些transation的动作过渡连线,设置一些参数和过渡条件,给每个 state 配置上具体对应的动画,基本上一个可以跑起来的动作状态就构建好了。

但是,这样的方式在我们这样一个非常注重动作的游戏项目里面应用起来也还是有一些困难的。其中非常典型的问题就是前面我们提到我们的动作 state 有 2000 多个,如果把 2000 多个节点都放到 Animator Controller 里面在我们看来是几乎无法维护的。幸好,Unity 还是提供了一个 Playable API 允许我们进自定义地重新写一套动作控制的系统。我们的项目也是用这个 Playable API 重新构建了一套动作底层的控制系统。我们今天主要聊聊使用这样的机制有些什么样的好处。

首先,它可以控制动画加载的策略,可以按需加载,也可以异步加载。第二点是可以更加灵活地控制 Playable Gragh 的数据流,可以比较方便地插入一些自定义的 AnimationJob 来做一些特殊的动作机制,动作表现。第三点是我们可以加载自定义的配置数据,让动作系统能够更加方便地和其他的游戏系统结合。最后,我们可以做一个自由度更高的 Override 机制。我们知道 Animator 本身是有一个 Override Animator 的概念,在我们项目里面其实做了一个更加高阶的 Override 的机制,能让一些动作更加方便地组合和覆盖。

这里有一些参考资料,基本上也是使用 Playable API 重新进行了对动画控制系统的构建。


[1] SimpleAnimationhttps://github.com/Unity-Technologies/SimpleAnimation
[2] Animancer:https://assetstore.unity.com/packages/tools/animation/animancer-pro-116514

03、ProBulider 工作流

现在来分享一下我们具体是怎么进行离线标注的,这里必须要提一个非常棒的 Unity 工具, ProBuilder,它在我们项目里面发挥了非常大的作用。大概有三个方面:一个是关卡的原型;第二个是构建测试场景;第三个是辅助特殊运动触发区的设置。

首先讲如何使用 ProBuilder 来设计关卡原型呢?在我们项目里面一个场景的构建过程大概有这样三步。第一个是关卡策划来搭建场景白模,第二个是通过迭代不断地验证玩法,这个场景白模是能符合我们游戏玩法的,等整个场景的拓扑结构大概定下来之后,最后我们美术才会进场对场景进行一个美化。

这个工作流程也是现在游戏开发里面,尤其是 3D 游戏开发里面比较规范、比较科学的一套开发流程。

微信图片_20210513140500.png

这里面场景的白模搭建基本上都是用 ProBuilder 来实现的。

第二个 ProBuilder 的功能就是搭建一些测试的区域。我们所有的运动触发区,比如像障碍,我们的策划会搭建不同规格的障碍在测试场景里面。不管是程序开发还是 QA 测试都会提供非常大的便利性。

微信图片_20210513140501.png

第三个是我们前面提到的一个运动触发区的离线标注系统,这个我们也是通过 ProBuilder 插件的方式提供了一些辅助。

比如说这个爬树的触发区,我们可以选一些树的面,把这个 trigger 给刷出来。屋檐的触发区可以选屋檐的线,把 trigger 刷出来。比如说这个天花板,这个索梁,都可以选对应的面把 trigger 刷出来。

整体上我们可以通过 ProBuilder 比较方便地选取模型中的点线面,辅助通过一些运算来直接把这个 trigger 刷出来,这样就比人肉手动地摆放一些 trigger 对齐场景有更高的效率。

微信图片_20210513140502.png

姚金毅老师还分享了《永劫无间》如何在运行时检测周边环境,如何做物理数据管理、动作录制和回放工具、离线动作采样工具等内容,完整版已上传至 B 站。

Animator 定制优化方案

刚才感谢《永劫无间》的小伙伴为我们带来了非常精彩的分享,今天咱们捡一点干的聊一聊,讲一个 Animator 的定制方案。

今天着重要讲的是一个热点问题——Animator 里面的 Override,首先从原理的角度分析,我们了解一下这个热点是如何形成的。

现在我的 demo 里有两个 U 酱。这两个 U 酱现在都是一个 Tpose 的状态,分别叫 Unity Normal,Unity Mass。她们是同一个 Avatar,但是我给了她们每一个人不同的 Controller。

Normal 的 Unity 酱加了一个标准的 Unity Controller,有两层的 state。我们现在一层的 state 里面加了一个新的状态叫 test,它对应了我自己做的一个空动画;另外一层就是 face,这一层在 demo 里面原来是用来控制小姐姐的动作表情的 state。

我们再看一下 Mass,Mass 的上一层就是我们的 base layer,和刚才的 Normal 是一模一样的。这个地方我们也加了一个 state test,这个地方我们加了一个 demi。我们在 face 这一层做了茫茫多的 state,其实就是把上面的 state 复制粘贴。

微信图片_20210513140503.png

能看到在我们的屏幕上有两个按钮,一个叫 Override Normal,一个叫 Override Mass,这两个操作几乎做的是同一件事,就是我们会对 Controller 里面 dummy 的节点给它同样 Override 一个 wait 动画进去。

当我去 Override Normal 和 Override Mass 的时候,花在 Override 这件事情上的时间,Mass 会更多。

为什么 Mass 多?

在 Unity 这个 Override 里面,我们发现会有相当长的一段时间,这段时间都在做什么?这个是很神奇的事情,因为 Unity 在 Tag 里面没有看到更详细的信息了。其实,这个地方 Unity 会尝试把你的 Animator Controller 里面所有的 state 合并到一个数据结构叫做 Animation set,它会把数据结构都放进去。所有的数据意味着你里面所有的 Animation Clip,注意,再乘上所有 clip 用的曲线都要经过一系列的运算。

什么意思?我们可以看一下这个曲线,我们在里面找一个 Animation Clip。当我们点这个的时候会有一些统计出的曲线数。这些曲线数对于优化来讲是有意义的。在这个里面大家会看到有一个叫 const 的曲线,如果 const 曲线的比值越高,在我们刚才说的做那段计算的时候所需要进行的计算就会相对少一些。

上面还有很多曲线,比如 curvepose,还有欧拉、scale 等等。这些曲线是要经过一系列的合并运算的,也就是说这些曲线在后期你们采样的时候会真正地参与运算的。const 曲线就是一个数,大家可以认为是一个常数的形式参与的。所以 const 的曲线带来的性能消耗并不大。像我这种百分之百的还好说。但是,我们在真正实际的项目中会看到有很多的曲线数大概在 20% 到 10%,剩下 Stream 的曲线占比会非常非常高。

另外,我们要看它这些线的总数。比如说我在这个地方有 10 个 AnimationClip,每个 AnimationClip 大概有 300 条曲线,你可以简单地认为它要做 3000 次运算,这些运算当然不是一个线性比例,但是大致上是这样的线性关系。

也就是说在我们的 state 里面,你基础的 state 越多,每一个 state 用的曲线越复杂,无论这个节点最终有没有参与到你运行时的最终表现中,它都会在 Override 的时候产生性能消耗的。

当然,我们在现实工作中如果用 Animator 这套系统,它的整体数量可能比我 demo 里面的数量还要大,甚至大出几个数量级。包括在一些主机项目等等更大型的项目里面如果用 Animator 的话也会面临同样的问题。

下面看一下如何优化?

优化也很简单。第一种方式就是我们尽量地减少整个状态机的复杂程度,比如说我们可以在里面尽量多地使用 dummy。比如同样一个状态,我们在这个地方要 Override,我们就在基础状态机里面尽量少地使用很复杂的动画,不要一上来就把它都放在基础状态机里面用。如果你的基础状态机非常复杂,比如有上千个,你每次至多是 Override 其中的两三个。这种相对来讲性能比较亏,因为你每次要把这些不动的全要运算一次。

所以说这种情况下我们建议你可以拆成多个 Controller 来控制,或者有一些开发者会把 Animator 拆分成两个或者是三个 Animator 来控制,也可以的。总而言之,就是要减少你基础状态机中 Animation 或者是动画数量的大小。

另外一个,当我们做一个动画的时候,我们检查一个动画的时候重点要看到这个曲线数,尽量增加它 const 的曲线数。有的同学说我导入的时候它就不是 const 怎么办呢?在 Unity 导入的时候实际上是有导入选项的,大家如果做过一些常规优化都知道,在我们 Animation Compression 里面有一个 Keyframe reduction,或者是 optional,一般来讲上面的选项更直接一点。

还有一种情况是怎么样优化的?比如说我们这个地方有一个 Keyframe,我们会发现它有一个偏差值,但是这个偏差值不是个绝对值,是个比。比如说 0.5,当我K出来的动画本身运动幅度非常非常小,比如前一个 Keyframe 是 0.000000……1,下一个帧多一个 0 的 1,那它的比值实际上是比较大的。但实际上在肉眼观察,这个东西几乎没有什么变化。

所以,大家也会经常看到一些资料上说我们可以尝试给 FBX 里面动画的精度做一个精度的削减。比如我们只保留小数点之后 5 位或者 3 位的动作,从而让我们的Keyframe reduction 可以压缩更多的本来就很相近的关键帧,从而减少这个曲线的数量。这样可以减少一部分内存,因为你所有曲线 keyframe 的数据最终在 Animationset 的时候起来要填充到一个内存块里面,这个东西本身是比较费的。另外,它可以产生更多的 const 的动画曲线,从而减少你整个 Override 的消耗。

当然,还有一种方式是像我们《永劫无间》的小伙伴们使用 timeline 的方式。因为 timeline 和 Animator 最大的区别就是,Animator 在设计上是把整个 Controller 视为一个整体的。什么意思?在 runtime 看来,我今后所有的操作是一整个的 Controller,而并不是大家在图里看到的 A 动画、B 动画。从 Unity 编辑器的角度看,做完这些合并的工作之后只有一个 Animator Controller,里面是一大堆的数据。所以,你任何一次 Override,任何一次对 Controller 的修改都是对整体数据集的修改,这个修改是非常庞大的。

而 Unity 在做 timeline 的时候就避免了这个问题,timeline 实际上是基于你每一个 Clip 修改的。当我 Override 一个 Clip 或者是更换一个 Clip 的时候,实际上要更新的数据就只有 Clip 这一部分。因此,它相对来说整体的性能消耗就会更平缓一些,不会像 Animator 这样容易出现一个峰值。

最后我还要推荐一下我自己给开发者做过的定制方案,这个优化方案的优化思路是什么?

既然我们在很多的项目组中要被 Override 的动画是个 dummy,很多开发组在实际开发过程中经常更换的话,他们的基础状态机里面就会放一个 demi,既然是这样,不如把它做一个前提约定,约定你要 Override 的动画在这个状态机里面一定是个空动画,基于这一点,我们就可以玩出很多花样了。

优化之后的效果如何呢?我之前测的比较稳定的一个数大概是 1.32,也就是说我们基于 Unity 这样一个优化了之后,从 4 点几到 1.32。

今天讲的是动画方面的。我最近有一年左右的时间一直在给各个游戏厂商做类似这样的定制方案,如果大家有兴趣,也可以到我们官方咨询更多物美价廉的定制方案,花钱不多,但是你可以拥有一个完全属于你的 Unity 引擎,听起来还挺酷的。大家可以通过扫这个二维码,或者和我们这个邮箱联系,咨询相关的优化方案。

来源:Unity官方平台
原文:https://mp.weixin.qq.com/s/OUdDJRA9YAcQ6CuTyDcahw

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2023-2-8 02:03

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表