在虚幻引擎5中创建自定义摩托车系统

AI3D 发表于 08-24 浏览 0 分类 创作分享

引言

我是一名技术美术,也是 Trailblazer Entertainment LLC 的所有者,该公司旗下拥有 Wilder Way 品牌。过去一年,我一直在为自己的一款游戏——一部线性公路剧情游戏——编写摩托车系统。我不会剧透故事。我只想说,摩托车本质上就是第二个主角:它几乎全程出现在屏幕上,发生在它身上的一切都在为叙事服务。

这就是为什么 Chaos 物理对我来说行不通。事实证明,对于我想要达到的效果来说,它太通用了。如果我做的是汽车,我会坚持用它,也不会写这篇文章。

Creating a Custom Motorcycle System for Unreal Engine 5

我首先尝试了简单的办法

我并没有立刻着手编写自己的核心。有两三个月的时间,我试图让自己轻松一些,使用了 Chaos Vehicles。那段时间被浪费了。之后我从头试了四次,每次都在某个节点放弃了。最终让我坚持下来的是阅读:学术论文、摩托车模拟的解构文章。

从这些材料中,一个基础逐渐成形,一旦成形,就清楚了:一个人确实可以构建这套系统。在一年时间里,积累了许多这样的基础。失败像早晨一样规律,天天如此。到那时,我的系统设计已经就绪。我知道自己想要什么。唯一的问题是让它运转起来:

质量与骨架组件

我需要理解质量在机器骨架上如何分布,以及这对它的行为有什么影响。而且不仅仅是车本身的质量;我的骑手不只是一个网格;他是一个影响摩托车运动的重量。实际上,摩托车总是散架。也许我在 Chaos 中做错了什么。我完全承认这是可能的。但我无法弄清楚到底哪里错了。

而这才是最让人疲惫的地方:你一次又一次得到不是你预期的结果,而摆在你面前的是别人代码和模式的大山,你得学会它们才能提出正确的问题。某个时刻,我放弃了整个想法。

伪造所需物理的修补

为了让摩托车表现得像摩托车,而不是一根带轮子的木头,我不得不堆上各种补丁。陀螺效应、轮胎行为、悬挂响应。每个补丁单独来看都运作得不错。问题在于它们加在一起:一层补丁盖在一个如实描述另一种机器的模型之上。你最终要调试的并不是物理,而是补丁之间的相互作用,这要糟糕得多。

两个轮子上的质量与四个轮子上的表现不同

四个接触面时,重量分布赋予车辆性格;两个轮子时,它决定车辆是否倾倒。某个时刻,天平倾斜了。麻烦超过了坐在现成系统上的明显好处。那时我开始编写自己的系统。说明一下:在汽车上,这一切运行得很好,我这么说并无讽刺之意。只是我的车辆恰好是摩托车。

摩托车必须会倒,但并非总是

我花了一些时间才把这个差异用语言表达出来,而且很容易朝两个方向搞错。汽车按定义是稳定的。停止模拟它,它就那么站着。这并非车辆模型中的简化,而是对汽车的正确描述。对于摩托车,一切都取决于速度。

低速时,无论骑手做什么它都会倒。在摩托车动力学文献中,这种模式被称为“翻覆”(capsize),本质上它是一个倒立摆:地面上两个接触点用弹簧连接,所有质量都在它们上方。但一旦达到速度,情况就翻转了。大约从7–8 m/s(即25–30 km/h)开始,摩托车进入一个自身稳定的区间。

你可以把双手从车把上拿开,在侧向推动之后,它会自行回正,完全不需要人的参与。这是破除一个流行迷思的好地方,因为它很顽固。在那个区间支撑摩托车的并不是前轮的陀螺效应。陀螺效应确实存在,但它不是主角,而且在低速时它微乎其微。

早在2011年,Science 上的一篇论文描述了一辆自行车,其陀螺力矩被抵消并且具有负拖曳距,然而在推动后它仍然自行回正。稳定性来自转向几何、质量分布和轮胎行为,这些共同使机器在自身下方转向。这就是稳定器与模型之间的区别来源。

一个稳定力矩能在任何地方、任何速度下同样地提供稳定性,无论是在停车场还是以一百公里时速行驶。一辆由稳定器支撑的摩托车感觉正是如此:骑手没有什么可平衡的,因为没有任何东西失去平衡。一个诚实的模型会给你三种不同的状态。在停车场,机器会倒;在速度中,它自行保持;在两者之间,有一个你能通过车把感受到的过渡。我没有分别编写这三种状态。它们自行涌现。

Creating a Custom Motorcycle System for Unreal Engine 5

车与骑手作为同一系统

我不想要一个上面“坐着”人体模型的摩托车;我想要一种共生。这里的骑手是同一机械系统的一部分:他的重量、他的位置、他的动作都输入到物理中。碰撞又是另一回事。引擎在这里有其局限,硬碰硬行不通。

我在一个意想不到的地方找到了解决方案——自动驾驶汽车:一种不是对已经发生的碰撞做出反应,而是为场景中即将发生的事情准备好物理的方法。它几乎不加改动地移植了过来。

Creating a Custom Motorcycle System for Unreal Engine 5

所有非骑行状态

这是让我下定决心的论据。某个时刻,我坐下来列出了摩托车在游戏过程中必须经历的所有状态。我得到的清单上,大部分条目与驾驶无关:

  • 停在侧支架上
  • 停在侧支架上并被撞倒
  • 骑行中摔倒
  • 侧躺在地面上,平地或坡上,滑动或不滑动
  • 被站在旁边的人从地面扶起
  • 熄火状态下,用骑手的脚向后退出停车位
  • 发动机熄火时滚动,推行启动

车辆运动系统对上述任何状态都没有意见,这并非缺陷,而是范围问题。它的职责是移动一辆四轮机器,而且它做得很好。但摩托车所做的事情中有一半根本不是移动。所以我遇到的并不是物理的局限,而是模型最初打算描述的范围边界。这是不同的问题,其中只有一种可以通过调参解决。

Creating a Custom Motorcycle System for Unreal Engine 5
Creating a Custom Motorcycle System for Unreal Engine 5
Creating a Custom Motorcycle System for Unreal Engine 5
Creating a Custom Motorcycle System for Unreal Engine 5

编写自己的核心带来了什么

完全的自由和一大堆集成问题。我必须编写自己的碰撞求解,而且这不是一次性任务;它是整个项目的背景噪音。另一方面,此后的每个系统在不需要和别人的模型讨价还价的情况下就位了。以下就是结果。

摩托车在伸出的任何东西上停靠

一个细节胜过它之上一半的物理。跌倒后,车把转向地面并保持转向。当摩托车被扶起时,它们不会弹回,这与真实摩托车上的表现相符。没有人会自觉地注意到这一点,但每个人都能感觉到。

但要让这起作用,你必须考虑摩托车的形状,而诚实的形状是昂贵的。我找到了一个更简单的方案,而且我喜欢它的效果。摩托车会停在任何实际伸出的东西上,并且每次都以相同的方式停靠。

Creating a Custom Motorcycle System for Unreal Engine 5

滑行

第一个版本很简单:摩托车倒下,沿曲线减速。在平坦地面上看起来还行,在其他任何地方都显得荒谬。在 2% 坡度和 20% 坡度上,制动距离竟然相同。我换成了一个更加真实的方案:斜坡施加拉力,地面施加阻力,哪一方赢了,结果就是哪样。仅凭这一条规则,就自然衍生出三种我从未单独编写过的行为:

  • 在平坦沥青路面上,摩托车滑行几米后停下
  • 在缓坡上,它会明显滑得更远,但最终仍会停下
  • 超过一定角度后,它根本停不下来,会一直向下滑,直到地形变得平坦

这正是做整套系统的意义所在。当模型真正描述出对象时,有趣的案例会自己浮现出来。同一套设定让我免费获得了一个意料之外的细节:静摩擦系数高于动摩擦系数,这意味着让倒下的摩托车开始移动,比让它保持移动更难。

于是,它开始缓慢移动的角度会比它最终停下的角度略陡一些。存在一条很窄的坡度区间,摩托车会在那里安静地躺着,直到被什么东西轻轻推动。一旦被推动,它就会滑下去,并且再也不会停下。

Creating a Custom Motorcycle System for Unreal Engine 5
Creating a Custom Motorcycle System for Unreal Engine 5

扶车应当费力

摩托车倒下,在沥青路面滑行,最终停下来。到这一步,失败状态逻辑通常会触发黑屏并重生。我想要的恰恰相反:这是游戏中唯一能让玩家真切感受到机器重量的时刻,错过就太可惜了。

从玩家的角度来看,过程是这样的:走上前,抓住车,快速按动按钮。屏幕上有一个量表,它会自行消耗;你持续按压多久,车子就抬起到相应的高度。停止按压,车子就会重新落下。于是,这变成了一场短小却相当费力的互动,并且第一次尝试很可能失败。它考验的是毅力,而非反应速度,这更适合这个场景。

Creating a Custom Motorcycle System for Unreal Engine 5

车轮

车轮是最让我头疼的部分,实际难度远超我的预想。基础是 Pacejka 模型,但经过大量修改,已经只剩下核心思想还在。有一件事我刻意避免:许多人会用动画蓝图来掩盖物理上的空缺,让视觉效果表现出模型本身并不具备的东西。我走了另一条路,把真实的物理数据输入 AnimBP,而不是用动画取代物理。这条路更昂贵,耗时更长,也无法在截图中体现。但它能在运动中体现出来。

Creating a Custom Motorcycle System for Unreal Engine 5

看见不可见之物

其中几乎没有任何一部分可以靠肉眼调试。一辆摩托车哪怕偏差一厘米,看起来也完全正常,直到阴影暴露出问题。所以,这一年里有相当长的时间没有花在模拟上,而是花在让自己能够看到系统所相信的状态上:它认为地面在哪里,它认为什么正在接触,重心在哪里,它是真的静止了,还是只是在极其缓慢地移动。

这是一件很少有人会在建议你自研系统时提到的事情。没有任何内置工具能够反向解释你自己的模型,所以你必须把诊断视为工作的一部分,而不是制作前的准备。我为“看见”所投入的每一个小时,最终都获得了数倍回报。

Creating a Custom Motorcycle System for Unreal Engine 5
Creating a Custom Motorcycle System for Unreal Engine 5

老实说,这项工程的工作量如此之大,对于一个人来说,与其写成文章,不如做一次演讲,甚至写一本书。

我会再这么做吗?

对于这款游戏,答案是肯定的。这里的摩托车拥有自己的性格,而且它有一半行为都超出了车辆模型的边界。如果它只是任务之间用来代步的载具,那么答案是否定的。那种情况下,使用 Chaos 加上稳定器就是正确的工程答案,我本可以在两周内完成这项任务,而不是花上一年。

这是我会推荐给任何面临同样选择的人的测试:把你对象需要经历的所有状态写下来。如果其中大部分只是从 A 到 B,那就使用引擎自带的系统。如果不是,那么单纯调参无法解决问题。我还会在开始时告诉自己以下几点:

  • 工具要在模拟之前编写,而不是等到第一个无法解决的 Bug 之后。
  • 失败状态从第一天起就要作为一个完整功能来设计。我的失败状态最初只是骑行代码之上的补丁,直到它们成为独立的功能后才真正生效。
  • 最后一厘米的成本,高于最初的一米。
  • 不要相信自己代码里那些自信满满的注释。我曾因为相信一条自己写下的注释,花了几个月时间在错误的地方寻找。

结语

以上所述只是整个工作的一小部分。与它共存于同一系统的还有悬挂、两级减震器、前叉和后摇臂、轮胎、转向与反向转向、侧倾、漂移、带电池的燃油系统、握把上的油门,以及一个采用真实物理曲线的引擎。外加大量我已经忘记的小细节。

骑手(包括我自己)是另一类修改意见的来源。这个系统中的某些东西并非来自图纸或公式,而是来自“哪里不对劲”的感觉——必须先捕捉到这种感觉,再向自己解释清楚。

有一件小事情没能写进文章,但值得一提:油门绑定在鼠标滚轮上。在你试过之前这听起来很奇怪,但试过之后,普通的模拟扳机反而显得粗糙了。不过这是另一个话题。

项目正在积极开发中。

Ihor Fridman,Trailblazer Entertainment LLC/Wilder Way 技术美术师兼创始人

相关链接

原文链接:https://80.lv/articles/creating-a-custom-motorcycle-system-for-unreal-engine-5

喜欢这篇内容吗?