不懂物理的前端不是好的游戏开发者(二)—— 物理引擎的学习之路
前言—
继第一篇文章之后已经过去了两个月,在上一篇文章中介绍了物理引擎是什么,需要掌握什么样子的基础知识才能继续往下进行开发。在这样的基础上,我们展开了第二篇,探索物理引擎的学习之路。在我们的日常开发当中,自然是用不到非常复杂的物理系统,大部分游戏都是基于刚体,再在游戏场景下进行一定的适配,最后模拟出物体在我们常规认识中的运动状态,使我们觉得这些位移,形变看起来都是理所当然,顺应规律的。其中最出名的手机游戏莫过于《愤怒的小鸟》了。那么我们如何达到《愤怒的小鸟》中的效果呢?让我们一步步来探索。
粒子在游戏中的运动—
首先我们来看一下这样的一个场景,在现实中,一个普通子弹的速度大约是 300m/s,接近音速,如果将其放在游戏当中,依然按照正常的速度,那么射出去的子弹只会在一瞬间消失不见,那射击游戏就成了玩家完全无法观察到弹道的游戏,横版过关类的游戏也变成了只看得到枪口火焰,但是看不到弹幕的恐怖游戏。
那作为“子弹”的小鸟也是同理,如果是真的按照在空中飞行的速度去计算,那么我们只能看到一个矫健的身影一闪而过,然后就像子弹一样打穿建筑飞往远方了。这显然不是我们想要的效果。
那么要如何解决这样的问题呢?显而易见的,我们应该降低游戏中高速物体的速度。一般来说,根据地图的大小,可以将速度限定在 5-25m/s。那么速度的问题解决了,但是当一个物体的速度从 300 降低到了 25 的时候,它的能量,碰撞的现象,都会有很大的不同。当一个慢悠悠的小鸟撞到一堵墙的时候,可能只会默默掉下来,而墙表示:“刚才什么东西给我挠了一下痒?”。那如何去解决这样的问题?
首先我们都知道动能和速度以及质量的关系: ,那么当速度下降的时候,我们就需要增加质量,两者的关系为,质量上升的比例等于速度下降的比例,也就是 = 。那当一个质量为 5g,速度 300m/s 的子弹加速度降低到 25m/s 的时候,质量应该上升144倍,变为 720g。这样我们便解决了速度降低导致能量降低的问题。此时我们的小鸟已经从一个普普通通的小鸡仔变成了一个结实有力的肌肉猛鸟,只需要动一动,就能把野猪撞飞。
但是又出现了新的问题,速度降低随之而来的是抛物线的变形,原本可以飞更远的,但是由于初速度过小,没多远就下坠到地面了,这下我们的肌肉猛鸟又变成了一个大秤砣。那这个问题如何解决呢?那就是将抛物线恢复成原来的样子。
当一个物体在做斜抛或者平抛运动时,他的初速度决定了抛物线的形状,其中高度和垂直方向的初速度决定了重力的作用时间,水平方向的初速度决定了水平方向的位移。当速度降低时,重力作用的时间和水平方向上的位移都会随之变化,导致轨迹和之前大不相同。我们知道水平方向的位移: ,而垂直方向上的位移: ,其中把 t 替换掉,就可以得到抛物线方程, ,其中 是物体发射的角度决定的,是一个固定值,那么影响曲线的就是 , 由此我们可以知道重力加速度的转换公式为 ,但是事实情况是这样吗?并不是,因为我们减少速度的时候,我们的场景其实也在缩小,相同时间下原本 300米的距离缩短到了 25米,所以 y 和 x 需要有一个同样的缩放比例才能得到一样形状的抛物线。那么我们可以得出结论,其实真正的重力加速度转换公式应该为 (此处略去一些联立的无聊等式)。
游戏中的合力—
力累加器
接触过简单的力学的都知道,当多个力作用在一个物体上的时候,我们可以通过合力与分力,将复杂的多种力的结合处理为我们方便计算的几个方向上的力。
首先我们明确一点,合力是多个力在矢量层面的叠加,那么就需要用到矢量的加减法。其中最常用的是平行四边形法则,而平行四边形法则在坐标轴里的处理十分的简单,就是将两个矢量的坐标相加即可。但是力本身并不一定是恒力,比如说空气阻力,可能随着速度的增加而增加;浮力,随着进入水中体积的增加而增加。所以我们需要时刻计算合力,那么在游戏中,就是在每一帧的时候都进行一次计算。
我们为此可以创建一个类,专门用于合力,称其为力累加器。累加器的概念在面向对象编程和函数式编程里面使用的比较多,在 js 里面也有对应的运行方法—— Array.reduce。它可以直接将数组中的元素进行累计,此处的累计不仅仅代表累加,可以是任何操作。那么事情就变得简单起来,我们只需要将力都汇总在一个数组中,并且给一个累加的函数即可。我们以二维平面为例来看一下:
class ForceAccum () {
constructor () { // 初始化力的数组
this.forceArray = []
}
addForce(force) { // 添加力到数组中
this.forceArray.push(force)
}
clearForceAccum () { // 清除数组用于下一次计算
this.forceArray = []
}
forceReducer (prevForce, currentForce) { // 计算合力
const x = prevForce.x + currentForce.x
const y = prevForce.y + currentForce.y
return {x, y}
}
getForce () { //获取合力
const force = this.forceArray.reduce(forceReducer)
return force
}
}
const forceAccum = new ForceAccum() //新建一个力累加器
forrceAccum.addForce(重力) // 增加重力
forrceAccum.addForce(阻力) // 增加阻力
forrceAccum.addForce(推力) // 增加推力
const force = forrceAccum.getForce() //计算合力
forrceAccum.clearForceAccum() // 清除数组用于下次计算
以上就是最简单的力累加器和其应用,往系统中增加力,最后获取合力。获取到合力之后我们便可以根据牛二定律得到加速度,最后根据加速度积分得到速度,然后根据速度积分得到位置。然后我们每一帧都重复以上操作即可。
而我们在《愤怒的小鸟》里面需要什么力呢?可以看出需要的是一个弹弓的弹力导致的推力以及自身的重力,那么我们只需要将这两个力加到我们的力累加器中即可计算出我们的肌肉猛鸟受到的力以及在水平和垂直方向上的分力。
而我们在实际的运动中,其实只有一个重力在起作用(不计空气阻力),推力在弹弓的弹性形变中最后转换成了飞行的初速度。所以如果为了简化计算,可以直接将弹簧的推力转换成初速度。当然也可以通过冲量和动量进行计算了。
那这样的每个力我们应该如何在代码中得到然后计算呢?这就需要用到另外一个东西——力发生器。
力发生器
力发生器,顾名思义,创造力的装置。因为我们在运动中,存在着各种各样的力,有的力是恒定的,例如质量不变时的重力;有些力是根据场景变化的,例如速度不断变化时的空气阻力;而有些力是根据玩家操作来变化的,比如说推力,弹力等。
那么其实我们可以根据这些力的特性,为它们注册对应的力发生器,这样可以更好的管理它们。而力发生器的原理非常简单,我们通过一个类来进行创建。但是各个力的特性不同,所以我们需要针对不同的力进行处理,这里有两种不同的方法,一种是将所有需要包括的力都写在一个类中,需要创建力的时候,使用对应的方法;另一种则是将生成力的方法和参数传入一个类中,最后返回需要的力,或者直接将力注入到力累加器中。后者的灵活性会使得我们在复杂的力学系统中更好得控制我们的系统。
class ForceRegister () {
constructor (forceAccum,func, param) {
this.forceAccum = forceAccum
this.func = func
this.param = param
this.force = null
}
createForce () { // 返回需要的力
this.force = this.func(this.param)
return this.force
}
addForce () { // 直接将力注入力累加器
this.createForce()
forrceAccum.addForce(this.force)
}
}
上述代码中的 func,param 就是我们需要生成的力 的方法和参数。例如重力,重力只需要输入物体质量(或者质量的倒数)和重力加速度,就可以得到对应的力 —— { x:0, y: mg }。阻力也是同理,阻力方程为 。其中的参数我们先不去细究,简化一下可以得到 ,a 为某个条件下的参数。这样的话我们阻力生成器的参数就是 a 和 速度 v,然后方向是运动速度的反方向。
有了力生成器和力累加器,我们可以方便地管理游戏中的力学体系。但是在游戏中每帧都需要大量的计算和刷新,对于性能的要求肯定是比较高的。所以便有了各种各样的优化方式。
比如说我们可以去掉空气阻力,水流阻力等以节省繁杂的计算,或者只给一个固定值来进行计算。对于比较重要的重力,我们可以通过内建重力的方式,直接将重力加速度存入整个物理体系,而不是将重力纳入到每个物体的每次计算当中。
那么其实我们明白了,我们只需要在系统中设置重力加速度,并且根据用户操作设置好初速度的向量,就可以快速完成一个小鸟的抛物运动了。
总结—
通过简单的力学知识再加上合适的代码,我们就可以创建出一个符合力学规律的超级简易版《愤怒的小鸟》世界了。但是这其实是远远不够的,一个游戏中除了力的简单叠加和位移,还有力矩、碰撞、旋转、角速度等等,只有加上了这些,我们才能去计算碰撞,得分,去合理的表现物体被撞后的受力、旋转、移动。这些有趣的内容请期待一下我们物理引擎系列下面的章节~
本文分享自微信公众号 - 凹凸实验室(AOTULabs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
分布式数据库--ZMP数据迁移平台
在云计算、大数据和物联网时代,数字化转型悄然在各行各业中进行。然而数据作为最基础的要素,如何进行多源异构的海量数据交换?如何保证数据的时效性和准确性?如何让海量异构数据实现云上、云下的协同?是我们长此以往关注的焦点。而ZMP数据迁移平台正致力于云间数据交换场景,支持不同数据结构,可帮助政企客户快速搭建高性能、高安全、高可靠、高稳定的数据交换平台。 1.功能 ZMP数据迁移平台不仅有基础的系统、用户和主题管理,也有可视化运维的服务器管理及消息监控等,更主要的是集成了消息服务、数据转换同步等中间件,同时加入了迁移评估和应用的智能改造。 服务器管理:提供服务器、消息服务、同步组件的安装、启停、配置和监控等功能。 主题管理:提供消息服务主题的创建、删除、编辑和监控,在数据交换中,不需要用户创建和删除主题,由数据交换任务统一管理。 数据迁移评估:提供源库数据库(Oracle9i,Oracle11g,Oracle12c,SQLserver)的数据采集,画像及目的端数据库为云溪数据库或MySQL时的兼容性评估。 数据采集:针对多个Oracle版本和SQLServer,构建了数据采集工具。 源库画像...
- 下一篇
Web3D 从入门到跑路 · 3D 初体验
本文整理自老冯于 凹凸 2022 年技术分享,带领大家从案例、应用、技术生态出发,让大家了解一下 3D 在 Web 端的现状。 3D初体验 Hey 3D what's up,最近在 Web 圈混得怎样 在“元宇宙”概念越来越火热的背景下,我们准备了一系列的 3D 元宇宙公开课及教学文章,教大家如何从 0 到 1 快速搭建一个 3D 项目,从中可以学习到 WebGL 底层原理、图形学、热门引擎的使用方法等。在入门前,我们先从案例、应用、技术生态出发,让大家了解一下 3D 在 Web 端的现状。 一、案例展示— 1.1 组成部分 先从一个基础的 DEMO 出发,一个基础的 3D 一般会有以下模块组成: (1)渲染 打开一个 3D 页面,首先会下载模型文件,然后渲染到页面中 (2)动画 逐帧渲染动画 (3)事件绑定 通过 js 的事件绑定,触发对应的渲染。比如点击地面人物移动 (4)场景切换 众所周知,游戏里有很多场景,比如游戏加载、游戏开始、游戏结束,就是三个不同的场景。如图就是从主玩法到编辑场景 1.2 完整案例 (1)PC 端 下面来看一些有趣的例子,先从PC端开始这是一名开发者博客,...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果