搜索 Unity

Animation Curve(动画曲线):设计的终极控制杆

2022年7月21日 类别 游戏 | 27 分 阅读
A white truck going down on a forest road, all made in Unity editor
A white truck going down on a forest road, all made in Unity editor
涵盖的主题
分享

Is this article helpful for you?

Thank you for your feedback!

本文是克里斯托·诺布斯(Christo Nobbs)为游戏设计师撰写的系列博文第三篇。该系列将扩展《The Unity game designer playbook(Unity游戏设计师攻略)》的内容,这是一本100多页的深度设计指南,可指导游戏设计师在Unity中建立原型、制作和测试游戏性。克里斯托之前的博文链接可在文末找到。

Unity包含有几种“类型(type)”可用于保存数据,这些数据在平衡系统、游戏性、角色设置、载具概况等方面和设计控制杆一样好用。Animation Curve(动画曲线)就是这样一种组件,它能为游戏设计师和创作者提供更有趣的可能性,尤其是在原型设计上。你可以在项目中用它来控制粒子系统的动画变量,或控制音源组件的递减或其他属性。

一条曲线是一张线状图,它显示了某一输入(X轴)所产生的反应(Y轴)。Unity在多个地方都有使用曲线,尤其是在动画一块。Curve Editor(曲线编辑器)本身带有许多可以利用的选项和工具。

本文将重点介绍怎样用AnimationCurve API编辑动画曲线。修改后的曲线可用于捕捉和存储数据,辅助结果分析。曲线也可兼容ScriptableObjects,正如攻略书中所讨论的,它非常适合用来编辑游戏数据。

你可以在Inspector面板中公开或序列化变量,用动画曲线进行编辑。曲线可以在编辑模式或运行时保存、导出或加载。可编辑切线则允许你控制关键点之间的曲线形状。

An Animation Curve property in the Inspector
Inspector面板中的一个Animation Curve属性:你可以点击打开曲线编辑器(Curve Editor)进行调整,还能在齿轮菜单中保存曲线。
A tangent's key in Unity
双击一条曲线可以新建关键点,你可以用它修改曲线,还能以多种方式来控制点的手柄。

关键点创建后,你可以控制点的切线和手柄,调整形状来做出最理想的效果。

An Animation Curve with multiple tangents
动画曲线的切线可用于编辑自然的峰段、谷段和突然的斜角

为日常动作和运动增加逼真的细节

像动画曲线这样的设计拉杆可让设计师无须写出复杂的数学公式或缓冲函数就能微调游戏性。 

动画曲线的一种用法是在时间轴上为线性运动加入细微差异和细节。以角色关闭车门为例。动作起手,角色的手抓着门把关门时会出现晃动。门一开始关得很慢,然后随惯性速度加快,再突然停止;最后也许还有一次小反弹或上锁扣。所有这些动作都可以保存在一条曲线中,形成序列。

A brown door opening and closing on a green gar in the Unity editor
用时间轴动画的曲线来打开和关闭一扇门
The curve for opening a door
打开门的曲线
The curve for closing a door
关上门的曲线

如例中所示,动画曲线适用于随时间推移改变物体属性,并创造出自然的物体运动。其他用法还包括控制物体的移动或旋转方式、角色在进入或结束冲刺时的加速或减速,以及引擎向汽车动力系统的传动。

Animation curve C# code
这段概述大致介绍了如何在Inspector面板中返回给定X轴位置上的Y轴数据。底部的方法展示了如何在调用EvaluateCurve方法时传入目标曲线或位置。

当你在Inspector面板对新建动画曲线编辑时,你可以通过传入一个参数来求得曲线的值,这类参数在Unity中被称为time(时间值)。在文章后半段,我们将看看在不使用X轴表示时间时,坐标位置为何更显具体。

一条控制发动机输出功率的“伪”引擎扭矩曲线

我们来看看怎样用动画曲线来控制汽车发动机的输出功率。你可以创建一个“伪”发动机扭矩曲线,根据引擎RPM(每分钟转数)返回施加在车辆上的力。如果玩家踩下油门,扭矩便会逐渐增加。

与其划出每个挡位或每种车辆的转速范围,你可以将转速“归一化”或将其设置在0和1之间,然后用“扭矩”曲线保存配置并重新用于其他车辆和挡位。

你可以通过将原始或当前转速除以最大转速来求得曲线的值,即:

normalisedCurrentRPM = currentRPM / maxRPM

为了使数值能在Curve Editor中便于编辑,让曲线可以重复使用,输出功率(Y轴)也将以0到1之间表示。这个值在应用到车辆的动力上时可使其刚体在Z轴上向前移动。

An example script to normalize the RPM
这是一段转速“归一化”的示例脚本,它会根据给定转速取得扭矩曲线的Y值,再乘以发动机功率来求得输出的动力。

你可以通过在曲线的(0,0)到(1,1)数值范围内为其他车辆取值并产生不同的效果。

An upwards green curve
根据曲线所示,这辆车在开始移动时功率输出稳定,但在输出到达峰值时,发动机会失去动力,鼓励玩家在恰当的曲线位置换挡。

你可以为汽车发动机添加一条基本的挡速比方程,让车辆在换挡并切换到不同的动力曲线(或是同一曲线的不同位置)时产生速度变化或抖动,使其看起来更逼真。这时汽车本身必须是一个刚体(Rigidbody)。汽车的后备箱或后座在装载其他刚体时也会使车身产生摇晃,这可以让定时送货任务变得更有意思。

如果你有一个自定义的汽车控制器,不想使用Unity的物理系统,你可以创建空载和载重的曲线变体,在运行时交替切换,让车辆按照你想要的方式行驶。

一条不太常用的提示:你可以在运行时用Unity API在曲线上添加修改(删除和新添)或除去关键点。你可以借此更全面地控制曲线,在需要时用切线平滑曲线

Curves for a heavy vehicle, such as a truck with automatic gears that starts up slowly but then doesn’t lose power after reaching peak RPM: This curve shows the truck without a load.
重型车辆的曲线,自动挡的卡车起动缓慢,但发动机在达到最高转速后不会失去动力:这条曲线是无负载的卡车。
This curve shows a truck with a heavy load. By adding another key and a power reduction after the vehicle has started moving, you can make the truck feel like it has a drawn-out second gear, for instance, giving it more personality.
而这条曲线表示了一辆装载重物的卡车。通过在车辆起动后增加另一个关键点再限制动力,你可以给卡车一个延长的二挡,使其更有个性。
A single curve for a vehicle that has multiple gears
为同一车辆的多个挡位设置一条曲线可以形成一种自动挡的感觉,不必再额外应用变速箱机制:曲线显示了处于第二、第三和第四挡的动力,其中第三、第四和第五挡曲线较长,第五挡在持续一段时间后便会达到最大速度。图中调整了多条切线来创造出所需的结果。

动画曲线可以抽象化表达复杂的系统,让你能可视化地控制系统。在发动机的例子中,你可以用上图曲线(代表“伪”变速箱)在加速时添加动力,营造出汽车换挡的感觉——在动力输出时抬高曲线,在挡位转速到顶、动力减弱时再次降低曲线。这对于设计车辆运动的原型是非常理想的,你可以用一条曲线绘制出所有挡位的汽车动力,从第一挡的起点(x0,y0)到第五挡的顶点(x1,y1)。

汽车加速时,发动机的转速通常会逐渐积累,所以本质上你是在制定一段时间内的数值变化,类似于让一个物体进行线性运动。请在游戏设计者攻略的时间和动画部分了解更多相关内容。

Three futuristic looking cars are racing on a large grid road with neon blue lights surrounding the road
“伪”物理学在不遵守现实物理规则的游戏中非常有用,比如未来风Unity赛车游戏《Antigraviator》。

我们再举一个例子:汽车在不平坦的地形上行驶时、在拐弯时、或在车轮上传动时的悬空和运动。你可以用动画曲线来做出逼真的转向、侧向力控制或轮胎打滑,并可视化地微调。

如果你需要为悬浮或轮式载具建立基于射线的机制,在发动机中产生“悬停”效果的常见选择是在每条射线上施加一个向上的力,并使用PID(比例-积分-微积分,proportional–integral–derivative)控制器算法来控制反弹,或者用胡克定律求得阻力。你可以在Unity的“Hover Racer Live 7/21 Cycle 4.2”中看到一个例子,后文还会有一个基于此建立的PID例子。

PID算法是一种控制回路反馈机制,也是一种控制器,它广泛应用于各行各业需要响应性矫正的地方,包括游戏开发。PID控制器会计算出一个误差值,用于表示实际测量的过程变量与期望目标间的差异,同时考虑到弹性方面(或我们给出的例子),它可用于替代胡克定律。游戏中的PID可以用于:

  • 在巡航模式下维持一个目标速度,控制其他不可预测因素的影响,如载重、玩家输入或崎岖的地形。
  • 控制敌对AI代理在射击玩家时的精准度,同时避免自身被击中。
  • 多人游戏中的延迟预测。

PID可以用在游戏开发中的任何地方,特别是在需要“准确和优化的自动控制”的沙盒和模拟。Squad的《坎巴拉太空计划(Kerbal Space Program)》使用了PID来让航天器向一个方向推进。

这项研究所说,在游戏开发以外,“PID调节是最成熟和应用最广泛的连续系统技术”。你可以在这个Control System讲座:PID控制简介中了解更多。

A space rocket is in a hangar, with an interface open on the left, showing customization options for the rocket
Squad的《坎巴拉太空计划》就使用了PID算法。

PID算法的编写可能不需要太多时间,但它们需要一定的时间调试才能平衡;这个耗时还会根据车辆的增多而倍增。但在制作原型时,你可以先用动画曲线代替之,从而省下不少时间,免于编写并调试多个PID控制器(曲线最终还是要用PID来取代才能实现最佳的控制效果)。

曲线非常适用于建立原型,它可用于与真实世界的参考实例相比对。在涉及空间计算时,曲线在游戏引擎中呈现出“数学上的完美”,除非额外添加或启用默认重力,世界中不会存在对立的运动力。因此,你可以很轻松地用一条简单的曲线来控制力的变化并产生强大的效果。

在创建车辆悬空时,你可以使用曲线来根据弹簧的压缩程度施加相应反作用力(归一化为0到1之间的数值),不必使用PID来产生阻尼。再结合刚体和少量的阻力,车辆的弹跳摆动会被抑制,悬空也会随负载的变多或变少而变化。

要确定弹簧的压缩程度,你可以用1.0f减去弹簧射线的碰撞距离。不管长度,若弹簧被压缩了25%,则压缩程度将是0.25。你可以将压缩度设为曲线的X值,将其乘以所需的弹簧力(把归一化的值还原成原数值),然后应用到AddForceAtPosition,根据悬空点的数量,在整个循环中施加向上的力。这时,除了Unity默认的-9.81f重力外,你不需要添加额外的向下力。

公式是这样的:

upwardsForce = forceMultiplier * forceCurve.Evaluate (springCompressionNormalized);

rigidBody.AddForceAtPosition(hitNormal * upwardsForce, point.transform.position);

将质量:力的比设为13:110,并应用下方曲线。

A white van without wheels falling down on a road in front of a forest, in Unity
这辆没有轮子的面包车悬空长度被延长了(弹簧长度达1米):面包车以不平的角度直直地落下,然后前后摇晃。
A double sigmoidal curve
我们可以看到一条双曲线,其中第一段是一条反曲线,曲线随后在第二段开头出现了轻微向下的倾斜。在这条动力曲线上,X为归一化的弹簧压缩量,Y为力度(0到1),这条曲线被用于在下坠的刚体(如上图的车辆)上产生一种阻尼效果来模拟悬空。

载具静止在了压缩度约50%的弹簧上,有恰当的质量值、向上的力和少量的线性和角度阻力来抑制摆动。车会出现反弹和静止,但不会触底,除非从极高的高度掉落或因玩家增加的质量而超载。

为了找到合适的数值,你需要先从y = b^x曲线(看起来类似四分之一的圆)找起。先设置较低的阻力,并将车辆的质量设定为现实中的质量。然后调整向上的力,直到车辆静止在约50%的弹簧压缩量。你可以反复使车辆掉落,检测底盘是否会触地,并观察其在反弹后的静止位置。如果车辆会在不平坦的地形上行驶,你可以用此方法求得悬空系统的弹力,并设置每个牵引点可启用或禁用,快速做出一个可控的悬空系统。

在悬空模型上使用动画曲线可以保证不同的车辆(汽车、货车或悬空较差的卡车)能做出不同的运动,比如那些一直触底的载具可以像街机游戏里那样弹跳,或在拐弯处滚动,在加速和刹车时晃动。如果你还未使用过Unity的刚体系统或自己的悬空方法,可以先将曲线与现有系统结合使用。你可以用曲线进行转向,或增强发动机的动力、悬空、阻力、轮胎抓地力、制动力等。动画曲线是Unity一个非常方便和泛用的工具,可用于在车辆设计过程中添加控制杆,放到Inspector面板中直观地控制各项特征。

Grey balls falling off with different characteristics and curves in Unity
在每个刚体上使用不同的曲线来了解其在坠落时的特征
The curves for each sphere in the previous image
上一张图片中每个球体的曲线
A code snippet in C# for dropping objects
扔下球体的代码片断

上图的一排球体是一串摆放好的刚体,其X和Z Freeze Position属性被设为受限。球体会接受一个基于弹簧压缩量计算的向上力,但每个球体的曲线各不同,我们将其并排放置以更好地进行视觉对比。你可以用该方法为物体找到理想的弹力水平,或者通过调整来平衡自带的反弹性。作为一名设计师,你可以通过操控向上的力来创建抽象化的复杂功能。

曲线作为一种强大的XY轴图表数据,虽然在技术上并不完美,但它可以帮助你快速建立阻尼解决方案的原型,并在Inspector面板中可视化地进行编辑,或在运行时保存为预设。在这篇关于阻尼艺术的博客里,Alexis Bacot列举出了所有“依赖于良好阻尼的东西。像是摄像机、动画、运动、颜色渐变、UI转变,等等等等......它的用处可多哩!阻尼是作品润色的关键。光是它本身就足以影响一款体验是好是坏。”

在文中,他用Unity的SmoothDamp演示了如何制作一种美观的淡入和淡出效果,怎样对目标的变化做出准确的反应。不过,曲线不会像“高级的弹簧减震器那样反弹,这对汽车悬空或假球物理学来说反而很好”——这是动画曲线所带来的一种优势。

当然,曲线除了能作为一种XY数据控制游戏,还有更多的用途。你也可以将其用作一种评估工具,通过调用AddKey Unity API来直观地绘制数据。如果要评估一个随时间变化的坐标值,如车辆悬空时的阻尼或掉落的球体,你可以在方法中加入AddKey(elapsedTime, currentSpringCompression),然后调用该方法并通过InvokeRepeating传入captureResolution作为记录频率。若记录分辨率被设为0.1f,则系统每隔0.1s就会在曲线上添加一个关键点。你可以在检查器中查看结果的缩略图,或打开图表查看完整数据。

A plotted curve
一个小球在应用了(0,0)到(1,1)直线弹跳曲线时所产生的数据曲线。
A plotted curve
类似上图,但弹跳曲线为非线性、双折线曲线时,小球所绘出的曲线。

回到充满弹力的货车上

我们最后再看一眼掉落的货车。动画曲线决定了多少弹簧压缩度会产生多少力,并生成一个足够贴近的结果,这在第三次反弹时将产生更多的摇晃。你可以比较用动画曲线创建的悬空与PID控制器的悬空效果(详见Unity “Hover Racer Live 7/21 Cycle 4.2”中用到的PID)。两者间唯一的区别在于PID的结果是乘以悬停力而非动画曲线的Y值得到的。

在应用PID并做出大量的平衡性微调后,车辆的悬空感觉上会更加贴近现实参照,且尾部晃动更少,对应的抑制力也更小。不幸的是,一旦车的质量有了变化,PID就需要重新平衡,这一过程会耗费大量的时间。为了设计原型,我们可以借助动画曲线快速、直观地做出效果,并用曲线图分析运动。要想评估应用的PID,你同样可以将运行结果用曲线绘制出来。结果要好得多,尽管第二次反弹略显夸张,但已经实现了轻型大货车的动作和外观。

A white van without wheels suspended in the air, in Unity
用PID最终做出的车辆悬空,基于原型阶段的动画曲线制作:悬空弹簧长度为1米,其动作仍略显夸张。
A target damping curve on the left, with a plotted result curve on the right
左边是Alexis Bacot的阻尼曲线目标成果,右边是应用PID后车辆运动的曲线绘图。

总结下来,我们在创建车辆运动时,可使用动画曲线来:

  • 设定引擎的输出功率,使功率能稳步增加。
  • 制作逼真的刹车动作,比如刹车力突然作用于车身,再逐渐减少,模拟出真实的汽车刹车;或者,像游戏《iRacing》那样,司机在轮胎承受极限时刹车,使汽车在短时间内减速,免于失控打转。
  • 模拟悬空系统,为汽车提供向上的力。
  • 模拟侧向牵引力,计算侧向反作用力防止汽车向左或向右偏离太多。
  • 用控制器模拟转向(转向角度最大为曲线的近中间位置,大约在-0.5到0.5之间,当摇杆位置接近-1或1时,转向只会逐渐变快)。
A green block floating on blue water, surrounded by mountains, in Unity
在这个原型中,curve.Evaluate(time)被用于在Y轴上逐渐移动平台的Transform。

除了车辆物理学之外,动画曲线还可以作为设计控制杆,控制原型中的玩家运动、连续攻击伤害等等。作为一个强大的原型设计工具,动画曲线使游戏设计师能够测试各种力,并找出恰到好处的机械“感觉”,还免去编写复杂算法或物理运算的必要。

若你想了解更多的指南和灵感,可在这里免费下载并查看《Game Designer Playbook(游戏设计师攻略书)》

2022年7月21日 类别 游戏 | 27 分 阅读

Is this article helpful for you?

Thank you for your feedback!

涵盖的主题
相关文章