关于制作赛车游戏的一些入门知识

作者:Heyzxz 2016-01-22

GameRes游资网授权发布 前言/背景

在过去的几个月里,我拉着几个死党一起搞了一个iOS赛车游戏。由于当时还在上班,所以我一边白天上班,一边晚上+周末倒腾这个游戏。尽管这只是一个很简单的2D游戏,但我却在里面用了一些比较‘有趣’的方法来使这个游戏能看上去比较生动,这其中包括了一些真实的车辆驱动原理以及一些基础的物理受力分析。接下来我准备用几篇文章来简单介绍下这些方法,同时我也希望这些知识能够对初学的你提供一些帮助。

我总共计划会写3篇关于此的文章:

第一篇主要是对车辆的驱动原理以及车辆在直线行驶下的受力分析做一个简单的介绍;

第二篇结合第一篇的内容,再加上对车辆转向情况下的物理分析,最终得出一个更完善的解决方案;

第三篇主要是浅谈关于引擎声音模拟方面的一些方法和常用工具。(由于拖延症,此篇暂时搁浅)

当然,这几篇文章仅仅只是一个比较初级的基础教程,比较笼统,意在能够带领初学者迅速的入门。如果你的需求是进阶提高,那么这些教程并不适合你。事实上对于初学者来说,这几篇教程会让你理解到一个大致的物理原理和实现流程,这对于你日后去研究那些更成熟的赛车引擎来说,会有一些帮助。而且,如果你玩过我的游戏,你也会发现这些基础的知识在我的游戏里是完全够用的。

此外,本教程更注重的是说明一些知识和原理,而非展示实际的代码。事实上本人历来本着现实世界的原理高于编程语言的观点,始终认为五花八门的编程语言仅仅只是工具而已,相比之下,搞清楚真实世界的道理我认为更加重要。因为最终我们还是要落实到运用工具解决现实的问题这一点上。因此,只要道理搞清楚了,你可以用oc,可以用c/c++,也可以用js,等等,那仅仅只是根据平台,选个工具而已。(注:我的游戏用的是oc,游戏引擎是cocos2d-swift, 物理部分基于内置的物理引擎chipmunk)

现如今我的这个游戏已经上线了,叫3 Lanes。如果你们有兴趣的话,可以点击这里下载,它是免费的。


接下来我们进入正题

首先我们来谈谈车辆在直线行驶下的受力分析。


如上图所示,当车辆沿直线行驶时受到的合力F可表示为:

[公式1]  F = fraction + drag + rollingResistance

其中,fraction是车辆受到的牵引力,我们稍后来讲牵引力的计算;

drag是空气阻力,详细的计算方法是:

[公式2.1]  drag = -0.5 *p * A * Cd * v *|v|

这个公式看上去貌似很吓人,但仅仅只是看上去。因为在这其中,p是空气密度,常量;A是车辆的迎风面积,对于同款车来说也是一定的;Cd学名叫“流体动力阻尼系数”,管他是什么鬼,总之还是一个常数。因此,我们不妨暴力的把[公式2.1]的前面那一堆捏到一起,dragFactor=0.5 *p * A * Cd,最后得:

[公式2.2]  drag = -dragFactor * v *|v|

这样就简洁明了多了。其中dragFactor是一个参数,由于此参数里包括了迎风面积A,不同的车辆迎风面积不同,因此你可以给不同的车辆设置不同的dragFactor参数; 而v是车辆的速度,-v *|v|保证了drag的方向与速度方向始终相反。

这样空气阻力drag我们就扯完了。

接下来我们回到[公式1],再来看看rollingResistance又是什么鬼。这个叫滚动摩擦力,这是由于橡胶轮胎与地面接触的部分被挤压变形而产生的一种阻碍轮胎运动的摩擦力(注意这可不是‘滑动摩擦力’。回忆回忆当年高中老师给你讲的自行车驱动时的情形,如果在轮子与地面之间不打滑的情况下,是不存在滑动摩擦力的。轮子靠着静摩擦力的‘推动’而前进)。如果你觉得滚动摩擦力不大好理解的话,那么可以想象一下,骑自行车,在轮胎快没气的时候和刚打完气的时候,哪种情况下骑着更累?显然是快没气的时候,因为这种情况下轮胎形变更大,因此你要克服更大的滚动摩擦力。rollingResistance的计算方法如下:

[公式3]  rollingResistance = -load * g *cRR

如果单个车轮分开算,那其中load就是单个车轮的载重(kg),当然你也可以直接用车辆的总质量代替load,这样你求的rollingResistance即是4个轮胎滚动摩擦力之和;g是重力加速度;cRR叫做滚动摩擦系数,跟路面的材质有关。通常来说干燥的沥青路面cRR取值一般是0.01左右。

注意,关于滚动摩擦力公式,我也在另一些资料上还看到过另一种说法:

[公式3*]  rollingResistance = -cRR * vLong

在这种公式下,vLong是轮胎沿其滚动方向的速度分量,注意它不一定和车速v相同。我个人感觉[公式3*]是一种近似的做法,并且认为rollingResistance与轮胎的速度有关(间接与车速有关)。而且此时的cRR也和原来[公式3] 的cRR也不是一个概念,它一般取值会是空气阻力系数dragFactor的30倍。(不大理解,貌似是一种凑出来的数字。不管,之后的教程以[公式3] 为准)

到此,[公式1]的后两位就讲完了。下面我们来说说牵引力。

我们知道,车辆行驶的牵引力,是靠车轮的滚动而产生的,而车轮的滚动,又是靠发动机的转动通过传动装置而带动的。我们把这个过程稍微描述得具体一点:

首先发动机燃烧燃油做功,带动了某个连杆运动,因此产生了一个力矩(扭矩)。之后该力矩通过了一个传动装置,期间经历了一些变化(放大或缩小),然后被传导到车轮上。最后车轮在力矩的作用下发生了滚动,最终构成了车辆前进的牵引力。因此我们要做的,就是用一些公式来描述这个过程。

让我们先从车轮看起。根据力矩公式,我们知道,车轮上产生的牵引力可以表示为:

[公式4]  fraction = torqueWheel / r

其中,r是车轮的半径, torqueWheel是作用在车轮上的力矩。刚才说了,作用在车轮上的力矩torqueWheel是由发动机输出的力矩torqueEngine经传动装置传导过来的,并且torqueEngine在经过传动装置的时候发生了一些变化,这些变化具体包括如下几个部分。

1. 变速箱。本质上是一系列齿轮组。由于两个相互咬合的齿轮彼此半径大小不同,因此转动时两者角速度不同,从而导致了力矩通过该齿轮组后其大小发生了变化。一般来说,变化后的力矩 = 原始力矩 * 这两个齿轮的半径比值gearRatio。而我们知道变速箱存在多个档位,其中每一个档位对应着一个不同的齿轮比值。因此,对于你要实现的换挡逻辑来说,其本质就是切换不同的变速箱gearRatio而已。

2. 最终差速(differential,不知道中文这么说对不对,反正我不会开车...)。这是除变速箱外另一个齿轮组,其本质作用是放大发动机输出的力矩。一般用final drive ratio这个词代表它的齿轮比(本文用的是differentialRatio,中文貌似叫最终传动比)。比如,BMW m3 coupe的differentialRatio你能查到是3.38。

3. 传动效率。顾名思义,通常机械结构都会有一定的损耗。我们用efficiency来代表有效的传动效率。据说一些好车的传动效率能达到90%以上。

综合上述几点,最终我们可以把车轮上的力矩与发动机输出的力矩的关系表示为:

[公式5]  torqueWheel = torqueEngine * gearRatio * differentialRatio * efficiency

那么,问题来了,[公式5]还有一个未知量,发动机的输出扭矩torqueEngine怎么算?答案也许会让你意外:查表!

没错,每一款发动机,厂家都给出一份形式如下的表,这一般是通过实际的实验得到的,它表示了发动机输出的扭矩和发动机转速之间的对应关系。


我们假设上图是‘骡子’牌发动机的扭矩/转速示意图。图的横轴方向RPM代表发动机的转速(每分钟多少转),竖方向代表发动机输出的扭矩。那么从上图我们可以知道:当发动机转速在1000转左右时,输出的扭矩为300牛米左右;并且在转速达到4300转左右时,输出最大扭矩约405牛米;之后随着转速继续增大,输出扭矩开始减小。

如果你是一个数学疯子,那么上面这个曲线对你来说是小菜一碟。但对于我这种凡夫俗子来说,为了计算方便,通常会将此图表简化为:


甚至更近一步,会继续简化为:


也就是说当转速不足1000转的时候,我仍然认为发动机输出最小的300牛米的扭矩。这样做更有助于维护RPM和扭矩的对应关系。

总而言之,上述曲线也属于你车辆发动机的参数,你为不同的发动机设置不同的曲线即可,然后,我们可以把上述函数计作:

[公式6]  torqueEngine =f(RPM)

此外还要注意一点,一般来说为了保护发动机,厂家会把最大转速限制在某个范围内,这个最大值一般叫redline RPM,顾名思义,就是红线,别踩红线,对发动机不好,所以对于你来说,具体实现的时候你也同样需要设置一个redlineRPM并且始终保持RPM不超过这个值。

最后,我们把[公式4],[公式5] 和[公式6]结合,得到最终轮胎牵引力的公式:

[公式7]  fraction = f(RPM) * gearRatio * differentialRatio * efficiency / r

到此,也许你已经注意到,把[公式7],[公式3]和[公式2.2]再带回到[公式1],我们就能出最终结果了! 但此时先别捉急,我们还有2个东西没考虑到,油门throttle和刹车brake。

(摘抄[公式1]到此:  F = fraction + drag + rollingResistance)

我们不妨在[公式1]的基础上如此考虑油门和刹车:

[公式8]  F = fraction * throttle + drag + rollingResistance + brake * brakeForce

比较一下[公式1]和[公式8]可以看出,我们将油门throttle视为牵引力fraction的乘数,并且让throttle的取值范围是0-1,这意味着,当throttle为0时,我们认为是驾驶员完全松开了油门,此时 fraction * 0 = 0,即不产生牵引力;反之throttle为1时,意味着此时驾驶员油门踩满,牵引力fraction完全参与合力的计算。

brakeForce即刹车力,不难想象它其实就是滑动摩擦力,且是定值。而brake的取值范围同样是0-1,为0时表示刹车踏板完全松开,为1时刹车踏板完全被踩下。

通常来说,你要在刹车的时候同时设置油门松开,但有没有可能throttle和brake同时为1呢?? 貌似现实中是可以出现这个情况的,只是一般人不这么做吧?不懂。。。

最终,千回万转,我们终于又绕了回来。把[公式7],[公式3]和[公式2.2]带入[公式8]:

[公式9]  F = f(RPM) * gearRatio * differentialRatio * efficiency / r * throttle - dragFactor * v *|v| -cRR  * load * g + brake * brakeForce

[公式9] 便是车辆在直线行驶时所受合力的最终公式。(注意load是车子总质量)。

之后根据合力算加速度,a=F/m,再用加速度更新速度,v += a * delta, 整个直线行驶的物理系统你就建好了。(delta是刷新间隔,秒,一般是每帧的时间间隔)。

后记

[公式9]里面还有最后一个未知量,发动机转速RPM,这鬼怎么求?简单提示下,最后咱不是更新了车辆的速度v吗,可以靠这个速度v算更新后的车轮转速,然后利用得到的车轮转速,结合变速箱齿轮比以及最终传动比再反推发动机转速(RPM)。当然这里有个前提,那就是为什么我上面假设RPM小于1000的时候仍然取最小扭矩300而不是0。如果不这么做,貌似会出什么问题?你猜。

剩下的事情,就是你采用‘上网查’或者‘花钱买’或者‘自己编’的方式,收集到你需要的特定车辆的如下性能数据:

发动机的转速与输出扭矩的曲线图: f(…),

变速箱各个档位的齿轮比: gearRatios,

差速比: differentialRatio,

传动效率: efficiency,

轮胎半径: r,

迎风面积以计算风阻系数: dragFactor,

轮胎滚动摩擦系数: cRR(这个在卖轮胎的网站上一般能查到),

车重: load(千克),

最后是轮胎摩擦系数,并结合车重和重力加速度计算刹车力: brakeForce。

当然,这里我们还有一些忽略掉的问题,我大概提一下其中几点:

1. 车辆加减速时的“重心转移问题”,这个一般是用来模拟车辆悬挂的运动效果。主要是在3D游戏里面用的比较多,有兴趣的话不妨搜搜看。

2. 车辆暴力起步时轮胎打滑的问题。这个也很好理解。之前我提到过车辆的前行靠的是轮胎与地面的静摩擦力“推动”的,这意味着轮胎受到的牵引力要始终小于等于轮胎与地面的最大静摩擦力,但如果在某一时刻(通常是起步时)牵引力大过最大静摩擦力的话,轮胎就会产生相对于地面的滑动,此时就打滑了。要模拟这个的话,你可以考虑在得到牵引力之后,判断牵引力是否大于轮胎与地面的最大静摩擦力,然后… …

最大静摩擦力约等于滑动摩擦力=mgu,橡胶轮胎和沥青路面的话u貌似取0.9左右,记不大清了

最后,[公式9]仅仅是车辆直线行驶的情况。在下一次的节目里,我会结合车辆转向的物理分析,最终得出一个更完善一些的解决方案。

补充:关于换挡

之前忘了说这个,简单补充一下。一般来说,手动换挡就不用考虑了,让玩家自己换挡即可;自动挡的话,我不清楚一般汽车制造商们会怎么做,也许会基于多种因素的考虑,但我猜有一个简单的策略是依据车速来判断是否该升降档。上面我们曾提到,通过车速是可以反推出发动机转速RPM的,加之RPM又存在着最大的限制,那么,如果我们反过来考虑的话,就可以计算出在各个档位下(不同的齿轮比),当RPM最大时,车辆所对应的最大速度,然后:

当加速时,你可以直接判断当RPM达到最大时,就升档;

当减速时,如果当前车速小于了上一档所对应的最大车速的话,就降档;

另外还需要注意的是,在换挡结束的一瞬间,你需要重设RPM,具体的做法是:

换挡后的RPM = 换挡前的RPM * 换挡后的齿轮比 / 换挡前的齿轮比

接下来,我们更进一步的来谈一谈车辆的转向过程。

为了更好的说明一些基础的知识,本文基于了如下几个前提:

1. 本文不考虑转向时两前轮之间的角度差异(即我假设了两前轮在任何时候始终保持平行)。实际上,真实的车辆在转向时,两个前轮一般会在角度上存在一定偏差,但我想对于大多数游戏实现来说,应该不会考虑这种偏差。但如果你对此感兴趣的话,可以搜一搜相关的关键词:Ackermann steering geometry。

2. 本文仅针对后轮驱动的车辆进行讨论。相较于四驱或前驱来说,后驱是一种相对比较简单的情况,因为牵引力仅由后轮产生,且后轮不存在转向,因此这种情况下牵引力的方向将始终指向车头(或车尾)方向,这也意味着,牵引力将仅仅用于计算车辆沿车头/车位方向的加速度, 而不会存在一个侧向的分量来影响转向的过程。但如果你要考虑前驱或者四驱的话,那么由于此时前轮上产生了牵引力,并且前轮存在转向的情况,因此你就要多考虑一点前轮牵引力的角度问题了(这也不太难,大致上多了一个力的分解过程:分解到两个方向上进行单独考虑)。

3. 我们还是不考虑重心转移的问题,至于原因后文会提到。

Ok,预备事项表完,我们开扯。

首先我们先上关键的概念:轮胎与地面之间的“侧向力”(有些地方叫横向力,侧力,侧滑力等等,我也搞不清楚中文哪个更准确,英文叫lateral force或者cornering force,本文后面的部分将用“lateralForce”表示)。这便是在转向过程中起到最关键作用的一个力。它具体是指,当轮胎的旋转方向(滚动方向,或者说车轮的朝向)与实际的速度方向不一致的时候,所产生的一个指向轮胎侧面(正侧面,垂直于旋转的方向)的一个作用力。请参看下图:


如该图所示,此刻一个车轮正在地面上滚动(俯视)。它的朝向如图上绿色箭头所示,而实际的速度方向如红色箭头v所示,并且车轮朝向与速度方向之间存在夹角为slipAngle(侧滑角),因此,图中的蓝色箭头代表的便是此刻轮胎与地面之间的侧向力,lateralForce(如果你把此图看成是一辆车的前轮的话,那么此刻的lateralForce导致了该车向右的转动)。

如果要深究lateralForce的成因的话,我看到过这样一种解释是说,当轮胎转向的时候,其与地面接触的部分并不会立即响应这种转向,因此在一个较短的时间内,该部分实际上产生了一个形变,而后为了恢复这种形变,从而引起了lateralForce(有点像弹力,神吧)。

不管怎样,既然已经明确了概念,那接下来我们就来看看这个力的计算。

首先,lateralForce与轮胎载重和slipAngle有关,并且在载重一定的情况下,lateralForce与slipAngle存在如下关系:


没错,跟上次发动机输出扭矩与RPM的关系图类似,这又是一份由轮胎厂家出具的图表,也是根据实验结果得到的。注意,我画的比较草率,有一点未标注,它一般是指在轮胎载重为5KN(即负载质量为500kg)的情况下,lateralForce与slipAngle的关系。从图上我们可以看出,当slipAngle大概在-5到5度之间时,lateralForce与slipAngle几乎成正比;当slipAngle超出这个范围的时候,lateralForce(的绝对值)稍有降低。因此,看到这种情况,很显然我们又要开始无耻地化简了:


由图可知,我们的策略显而易见:当slipAngle在-5与5之间时,我们认为lateralForce与slipAngle严格成正比关系;当slipAngle超出这个范围时,我们认为lateralForce始终取最大(或最小)值。 当然,关于lateralForce与slipAngle的关系,如果你觉得我的近似策略太过草率的话,还有一种更精致的办法也许适合你,请搜索:Pacejka Magic Formula。但如果你欣赏我这种简化方式的话,那我们便可以愉快地玩耍,并得到关于lateralForce的公式:

[公式1] lateralForce = fn  * wheelLoad,

且:

fn = slipAngle * C, -fnMax <= fn <= fnMax

[公式1]的意思是说,先求一个“单位载重下的lateralForce”,即fn,它等于slipAngle乘以一个常数C(正比关系),并且我们限制:-fnMax <= fn <= fnMax,即一旦fn超出范围始终取端值;之后再用得到的fn乘以实际的轮胎载重wheelLoad,最终得到实际的lateralForce。

那么[公式1]中还有两个未知量,wheelLoad与slipAngle。我们首先来看wheelLoad,轮胎载重。这其实是在上一篇中提到过的一个名词,只是当时没有给出具体的计算方式。请看下图:


假设图中CG表示整车的重心位置,线段b表示前轮轴到重心的水平距离,线段c表示后轮轴到重心的水平距离,那么我们很容易知道,前后轮的载重与b和c长度成反比。所以,如果我们继续假设整车质量为m, 重力加速度为g的话,可得:

前轮载重(两前轮载重之和)等于:

[公式2] wheelLoadFront = m * g * c / (b + c)

后轮载重(两后轮载重之和)等于:

[公式3] wheelLoadRear = m * g * b / (b + c)

其中b+c即是所谓的轴距,wheelbase。一般的,如果你想更简单一些的话,你可以更进一步假设你的车辆重心位置就在轴距的中点,那么此时你理想地认为:wheelLoadFront = wheelLoadRear = m * g  * 0.5。

至于slipAngle,在此我们先挂一个问号,稍后我们再回来讲解它的计算方法。

那接下来,我们便带入lateralForce,并结合上一篇文章所讲的知识,以及从车轮受力的角度出发,重新绘制我们的受力分析图:


如上图所示,有几个需要解释的地方:

1. 为何要把所有的着力点分别画在两条轮轴的中点,而不是画在每个车轮与地面的接触点上呢?的确,如果以实际情况出发,确实应该画在各个车轮与地面的接触点上。但为了简化过程,我们这里基于了两个假设,在文章开头已提到过,一是我们认为两个前轮(或两个后轮)在任何时候始终保持平行;另一个是我们不考虑重心转移的情况,即相应的左、右二轮在任何时候我们都认为是承受了相等的载重。在这两个假设的基础上,我们很容易想到,无论前后,相应的左、右二轮的受力分析是完全相同的。所以为了简单期间,我们不妨把两个前轮(或两个后轮)看成是一个整体,即当我们说前轮受力的时候,意思是指左右两前轮的受力之和;当说到后轮受力的时候,意思是指左右两后轮的受力之和。

2. 图中红色的几个箭头代表的全是在上一篇中提到的几个力,牵引力fraction,空气阻力drag以及滚动摩擦rollingResistance。它们本质上与前文提及的并无差别,此处仅仅是把它们分摊到了前、后轮上进行单独的考虑。具体包括:(一)由于我们仅考虑后轮驱动,因此fraction仅仅产生于后轮,并且永远指向车头或车尾方向;(二)为了表示起来方便,我们把drag也标在了后轮上,并且它的方向始终与速度方向相反(图示绿色箭头为速度方向);(三)由于转向期间前轮与后轮的角度不同,因此它们的滚动摩擦力方向也不同,这里用Front与Rear后缀以示区分。

为了更便于计算,我们不妨以车辆自身的坐标系为准,以车头的指向为long方向,以右侧面为lat方向,然后将上图所有的力都逐个分解到这两个方向上来,之后我们便得到了分解之后的图示:


虽然此图并不清晰,各个箭头并未进行文字标注,但事实上这也并不重要,你只需要知道这些箭头都是由上上图所标示的那些力,沿着long和lat方向分解而来。且此图的目的仅仅是为了方便你看出一个最终的结论,那便是:分解后所得到的所有沿long方向的力,它们形成的合力最终影响了车辆在long方向上的加速度;而所有沿lat方向的力嘛… 作用有两个:

一是和long方向的合力一样,lat方向的合力也最终形成了车辆在lat方向上的加速度(侧滑);

另一个作用是,也许你已经看出来了,它造成了车身绕其重心进行转动。

这便是整个转向过程的实质。

如果你很难想象这种转动的话,想象一根筷子嘛。。。是吧。


根据力矩公式,假设前轮上所受的沿lat方向合力为netForceLatFront,那么你可以计算它所形成的作用于车身旋转的力矩:

[公式4]torqueFront = netForceLatFront * b

同理,后轮上的力矩为:

[公式5]torqueRear = -netForceLatRear * c

(注意[公式5]的符号)。

那么,作用在整个车身上的合力矩就是torqueFront + torqueRear,即:

[公式6]torqueBody = netForceLatFront * b - netForceLatRear * c

之后可以再根据转动惯量公式,角加速度=合力矩/转动惯量inertia, 你可以得到车身转动的角加速度angularAcc公式为:

[公式7]angularAcc = (netForceLatFront * b - netForceLatRear * c)/ inertia

(关于转动惯量的定义,忘了的请自行搜索:moment of inertia。它跟物体的质量和形状有关,一般来说能查到。维基百科上列出了一些常用形状的转动惯量计算方法,总之对于特定的车型来说,转动惯量是已知量)

最后有了角加速度,你就可以计算车身旋转的角速度w,从而可以更进一步得到车身的角度(rotation)。

到此,整个转向的大致物理过程我们就介绍完了。有觉得豁然开朗了吗?

下面再回到上面留的一个问号,关于slipAngle的计算,这下就简单了。

根据定义,slipAngle是车轮朝向与车轮实际的速度方向(即车速方向)之间的夹角。既然如此,我们不妨将车速v也分解到long和lat两个方向上,如图vLong,vLat所示:


你可能一眼就能看出,既然slipAngle是车轮朝向与车轮速度方向之间的夹角,对于后轮来说,它的朝向就是指向long轴,速度方向就是车速的方向,那么它的slipAngle不就等于车速度方向与long轴形成的夹角吗?而这个夹角不就等于arctan(lat方向的速度分量 / long方向的速度分量的绝对值 )吗?因此,对于后轮:

slipAngleRear = arctan(lat方向的速度分量 / |long方向的速度分量 |  )

其中long方向的速度分量即是图示的vLong,而值得注意的是lat方向的速度分量,可不仅仅只是图示的vLat。由于之前我们已经得出过结论,此时车身还在围绕重心以角速度w进行转动,并且根据角速度与速度之间的变换公式(速度等于角速度乘以旋转半径),我们能够得出另一个lat方向的速度,其大小等于w *c, 方向正好与vLat相反。因此,我们得到最终的后轮slipAngle公式为:

[公式8]slipAngleRear = arctan( (vLat - w*c) / |vLong| )

相应的,前轮与后轮基本同理,只不过由于前轮还存在一个转向角steerAngle(即前轮与long轴的夹角),这里稍微推导下可得,前轮slipAngle的公式为:

[公式9]slipAngleFront = arctan( (vLat + w*b) / |vLong| ) - sgn(vLong) * steerAngle

其中sgn(vLong)代表取vLong的符号(结果可为1,-1或0)。

到此,尽管整个过程可能让你有些眼花缭乱,但在不知不觉当中,截至[公式9],我们对整个物理情景的分析,就算是圆满结束了。

在本文的最后,结合之前的所有内容,我最终整理了一份比较完善的有关车辆物理逻辑实现的基本算法流程,具体如下:

在你的update方法里:

1.  首先根据车辆相对于父级坐标系的速度v,结合车辆相对于父级的旋转角度rotation,计算出车辆相对于自身坐标系的速度分量vLong和vLat:

vLong = -sin(rotation) * v.y + cos(rotation) *v.x;

vLat =  cos(rotation) * v.y + sin(rotation) * v.x;

2. 获取此时前轮的转向角度:steerAngle;

3. 根据vLong与vLat,计算风阻drag:

dragLong = -dragFactor * vLong *|vLong|;

dragLat = -dragFactor * vLat *|vLat|;

(这是算的drag在long和lat两个方向上的分量,下同。如果你够讲究,两式的dragFactor可能稍有不同,因为正面和侧面迎风面积不同)

4. 分别计算前后轮的滚动摩擦力:

前轮:

rollingResistanceFrontLong = -wheelLoadFront * cRR * cos(steerAngle);

rollingResistanceFrontLat = -wheelLoadFront * cRR * sin(steerAngle);

后轮:

rollingResistanceRearLong =  -wheelLoadRear * cRR;

(后轮不存在lat方向的滚动摩擦力分量,即无rollingResistanceRearLat)

5. 根据油门throttle,发动机当前转速RPM,以及刹车力brakeForce和刹车情况brake,计算牵引力fraction:

fraction = f(RPM) * gearRatio * differentialRatio * efficiency / r * throttle + brake * brakeForce

(后轮驱动,fraction也不存在lat方向上的分量,之前讲过了)

6. 计算前轮slipAngle,并以此计算前轮lateralForce:

slipAngleFront = arctan( (vLat + w*b) / |vLong| ) - sgn(vLong) * steerAngle;

fnFront = slipAngleFront  * C;

fnFront = clamp(-fnMax, fnMax);

lateralForceFrontLong = fnFront *  wheelLoadFront * sin(steerAngle);

lateralForceFrontLat = fnFront *  wheelLoadFront * cos(steerAngle);

7. 计算后轮slipAngle,并计算后轮lateralForce:

slipAngleRear = arctan( (vLat - w*c) / |vLong| ) ;

fnRear = slipAngleRear  * C;

fnRear = clamp(-fnMax, fnMax);

lateralForceRear= fnRear *  wheelLoadRear ;

(与前轮有所不同,lateralForceRear完全指向lat方向,因此不存在long方向的分量)

8. 综合上述得到的所有力,分别计算整车long和lat方向上受到的合力netForce(前后轮所有long方向与lat方向的合力分别相加):

netForceLong = rollingResistanceFrontLong + lateralForceFrontLong + rollingResistanceRearLong + dragLong + fraction;

netForceLat = rollingResistanceFrontLat + lateralForceFrontLat + dragLat + lateralForceRear;

9. 再根据合力和车辆质量,计算最终加速度,并根据刷新间隔时间delta,更新vLong,vLat:

aLong = netForceLong/ m;

aLat = netForceLat/ m;

vLong += aLong * delta;

vLat +=  aLat * delta;

10. 用更新后的vLong,vLat,参照第1步倒推,最终更新车辆相对于父级的速度v,并且更新坐标x,y。

...

x += v.x * delta;

y += v.y * delta;

11. 根据前后轮lat方向的受力算作用于车身上的合力矩(torqueBody = torqueFront + torqueWheel):

torqueBody = (lateralForceFrontLat + rollingResistanceFrontLat ) * b - lateralForceRear *c;

(注意这里貌似有点问题,应该还要考虑侧向的空气阻力…)

12.  根据合力矩,以及转动惯量inertia,算角加速度:

angularAcc = torqueBody / inertia;

13. 根据角加速度更新角速度w,并更新车身的旋转属性:

w += angularAcc  * delta;

rotation += w * delta;

14. 更新rpm,如果是自动档的话检测是否该升降档。

最新评论
暂无评论
参与评论

商务合作 查看更多

编辑推荐 查看更多