关于制作赛车游戏的一些入门知识(一)
题外话
这是我的第一篇博客。话说对于一个在码界已经混迹了7年的人来说,现在才开博的确是一件令人费解的事情。这也怪我本人性格比较奇葩,生性懒惰,好浮游潜水。然而三个月前我辞职了,现如今突然有了一刻闲暇,恍然意识到在过去7年中自己敲过的代码远多于敲过的人话。为了能够顺利地回归自然界,我试着从今天开始养成隔三差五写博的习惯,练练人话水平,好让自己今后能看起来更像个生物。
前言/背景
在过去的几个月里,我拉着几个死党一起搞了一个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 * 换挡后的齿轮比 / 换挡前的齿轮比