发布时间:2026/8/25 10:54:32
用JavaScript玩转游戏物理(一)运动学模拟与粒子系 系列简介也许三百年前的艾萨克·牛顿爵士(Sir Issac Newton, 1643-1727)并没幻想过物理学广泛地应用在今天许多游戏、动画中。为什么在这些应用中要使用物理学笔者认为自我们出生以来一直感受着物理世界的规律意识到物体在这世界是如何正常移动例如射球时球为抛物线(自旋的球可能会做成弧线球) 、石子系在一根线的末端会以固定频率摆动等等。要让游戏或动画中的物体有真实感其移动方式就要符合我们对正常移动的预期。今天的游戏动画应用了多种物理模拟技术例如运动学模拟(kinematics simulation)、刚体动力学模拟(rigid body dynamics simulation)、绳子/布料模拟(string/cloth simulation)、柔体动力学模拟(soft body dynamics simulation)、流体动力学模拟(fluid dynamics simulation)等等。另外碰撞侦测(collision detection)是许多模拟系统里所需的。本系列希望能介绍一些这方面最基础的知识继续使用JavaScript做例子以即时互动方式体验。本文简介作为系列第一篇本文介绍最简单的运动学模拟只有两条非常简单的公式。运动学模拟可以用来模拟很多物体运动(例如马里奥的跳跃、炮弹等)本文将会配合粒子系统做出一些视觉特效(粒子系统其实也可以用来做游戏的玩法而不单是视觉特效)。运动学模拟运动学(kinematics)研究物体的移动和动力学(dynamics)不同之处在于运动学不考虑物体的质量(mass)/转动惯量(moment of inertia)以及不考虑加之于物体的力(force )和力矩(torque)。我们先回忆牛顿第一运动定律:当物体不受外力作用或所受合力为零时原先静止者恒静止原先运动者恒沿着直线作等速度运动。该定律又称为「惯性定律」。此定律指出每个物体除了其位置(position)外还有一个线性速度(linear velocity)的状态。然而只模拟不受力影响的物体并不有趣。撇开力的概念我们可以用线性加速度(linear acceleration)去影响物体的运动。例如要计算一个自由落体在任意时间t的y轴座标可以使用以下的分析解(analytical solution):当中和分别是t0时的y轴起始座标和速度而g则是重力加速度(gravitational acceleration)。这分析解虽然简单但是有一些缺点例如g是常数在模拟过程中不能改变另外当物体遇到障碍物产生碰撞时这公式也很难处理这种不连续性(discontinuity) 。在计算机模拟中通常需要计算连续的物体状态。用游戏的用语就是计算第一帧的状态、第二帧的状态等等。设物体在任意时间t的状态位置矢量为、速度矢量为、加速度矢量为。我们希望从时间的状态计算下一个模拟时间的状态。最简单的方法是采用欧拉方法(Euler method)作数值积分(numerical integration):欧拉方法非常简单但有准确度和稳定性问题本文会先忽略这些问题。本文的例子采用二维空间我们先实现一个JavaScript二维矢量类:1234567891011121314151617// Vector2.jsVector2 function(x, y) {this.x x;this.y y; };Vector2.prototype {copy :function() {returnnewVector2(this.x,this.y); },length :function() {returnMath.sqrt(this.x *this.x this.y *this.y); },sqrLength :function() {returnthis.x *this.x this.y *this.y; },normalize :function() {varinv 1/this.length();returnnewVector2(this.x * inv,this.y * inv); },negate :function() {returnnewVector2(-this.x, -this.y); },add :function(v) {returnnewVector2(this.x v.x,this.y v.y); },subtract :function(v) {returnnewVector2(this.x - v.x,this.y - v.y); },multiply :function(f) {returnnewVector2(this.x * f,this.y * f); },divide :function(f) {varinvf 1/f;returnnewVector2(this.x * invf,this.y * invf); },dot :function(v) {returnthis.x * v.x this.y * v.y; }};Vector2.zero newVector2(0, 0);然后就可以用HTML5 Canvas去描绘模拟的过程:Run Stop Clear修改代码试试看改变起始位置改变起始速度(包括方向)改变加速度这程序的核心就是step()函数头两行代码。很简单吧粒子系统粒子系统(particle system)是图形里常用的特效。粒子系统可应用运动学模拟来做到很多不同的效果。粒子系统在游戏和动画中常常会用来做雨点、火花、烟、爆炸等等不同的视觉效果。有时候也会做出一些游戏性相关的功能例如敌人被打败后会发出一些闪光主角可以把它们吸收。粒子的定义粒子系统模拟大量的粒子并通常用某些方法把粒子渲染。粒子通常有以下特性:粒子是独立的粒子之间互不影响(不碰撞、没有力)粒子有生命周期生命结束后会消失粒子可以理解为空间的一个点有时候也可以设定半径作为球体和环境碰撞粒子带有运动状态也有其他外观状态(例如颜色、影像等)粒子可以只有线性运动而不考虑旋转运动(也有例外)以下是本文例子里实现的粒子类:12345678910// Particle.jsParticle function(position, velocity, life, color, size) {this.position position;this.velocity velocity;this.acceleration Vector2.zero;this.age 0;this.life life;this.color color;this.size size;};游戏循环粒子系统通常可分为三个周期:发射粒子模拟粒子(粒子老化、碰撞、运动学模拟等等)渲染粒子在游戏循环(game loop)中需要对每个粒子系统执行以上的三个步骤。生与死在本文的例子里用一个JavaScript数组particles储存所有活的粒子。产生一个粒子只是把它加到数组末端。代码片段如下:123456789101112131415161718//ParticleSystem.jsfunctionParticleSystem() {// Private fieldsvarthat this;varparticles newArray();// Public fieldsthis.gravity newVector2(0, 100);this.effectors newArray();// Public methodsthis.emit function(particle) {particles.push(particle);};// ...}粒子在初始化时年龄(age)设为零生命(life)则是固定的。年龄和生命的单位都是秒。每个模拟步都会把粒子老化即是把年龄增加年龄超过生命就会死亡。代码片段如下:12345678910111213141516171819202122232425262728293031functionParticleSystem() {// ...this.simulate function(dt) {aging(dt);applyGravity();applyEffectors();kinematics(dt);};// ...// Private methodsfunctionaging(dt) {for(vari 0; i particles.length; ) {varp particles[i];p.age dt;if(p.age p.life)kill(i);elsei;}}functionkill(index) {if(particles.length 1)particles[index] particles[particles.length - 1];particles.pop();}// ...}在函数kill()里用了一个技巧。因为粒子在数组里的次序并不重要要删除中间一个粒子只需要复制最末的粒子到那个元素并用pop()移除最末的粒子就可以。这通常比直接删除数组中间的元素快(在C中使用数组或std::vector亦是)。运动学模拟把本文最重要的两句运动学模拟代码套用至所有粒子就可以。另外每次模拟会先把引力加速度写入粒子的加速度。这样做是为了将来可以每次改变加速度(续篇会谈这方面)。12345678910111213141516functionParticleSystem() {// ...functionapplyGravity() {for(variinparticles)particles[i].acceleration that.gravity;}functionkinematics(dt) {for(variinparticles) {varp particles[i];p.position p.position.add(p.velocity.multiply(dt));p.velocity p.velocity.add(p.acceleration.multiply(dt));}}// ...}渲染粒子可以用很多不同方式渲染例如用圆形、线段(当前位置和之前位置)、影像、精灵等等。本文采用圆形并按年龄生命比来控制圆形的透明度代码片段如下:12345678910111213141516171819functionParticleSystem() {// ...this.render function(ctx) {for(variinparticles) {varp particles[i];varalpha 1 - p.age / p.life;ctx.fillStyle rgba( Math.floor(p.color.r * 255) , Math.floor(p.color.g * 255) , Math.floor(p.color.b * 255) , alpha.toFixed(2) );ctx.beginPath();ctx.arc(p.position.x, p.position.y, p.size, 0, Math.PI * 2,true);ctx.closePath();ctx.fill();}}// ...}基本粒子系统完成以下的例子里每帧会发射一个粒子其位置在画布中间(200,200)发射方向是360度速率为100生命为1秒红色、半径为5象素。Run Stop

相关新闻

2026/8/25 22:28:45

宁波搬家公司怎么选?内行分享避坑技巧,拒绝临时加价

在宁波准备搬家的朋友,大概率都遇到过低价引流的搬家套路。前期报价很低,上门后加收楼层费、大件拆装费、远距离搬运费,一趟搬家多花几百元。作为本地搬家从业者,给大家整理 3 个选搬家团队的核心要点。 1、优先本地固定搬家团队 …

2026/8/25 21:35:11

LINQ to SQL、NHibernate比较(一)-- LINQ和NHibernate初体验

研发与数据库打交道的系统的时候,最过于繁琐的莫过于没有编程快感的使用ADO.NET对后台数据库进行操作,因为所有的数据库连接、读取、操作千篇一律,编程成为了体力活。 虽然我们可以设计自己的类作为数据库访问的持久层,但是每一个…

2026/8/26 7:20:00

烹饪机器人矩阵首发:餐饮后厨数字化与标准化升级解析

1. 这篇文章真正要解决的问题后厨正在变贵,这是所有餐饮从业者都绕不开的现实。过去五年,餐饮行业的成本结构发生了剧烈变化:房租刚性上涨,人员工资持续走高,更关键的是,厨师越来越难招。年轻人不愿意进后厨…

2026/8/26 7:20:00

回文数判断:从字符串转换到数学反转的算法优化与边界处理

1. 项目概述:从一道复试真题说起最近在整理一些高校计算机相关专业的复试真题,发现“回文数”这个题目出现的频率相当高,东华大学的这道“复试70”题就是典型代表。题目本身可能就一句话:“判断一个整数是否是回文数”&#xff0c…

2026/8/26 7:20:00

线性回归正则化实战:从过拟合到L1/L2/Elastic Net原理与Python实现

1. 项目概述:从“过拟合”到“正则化”的实战思考做机器学习,尤其是线性回归,新手最容易踩的一个坑就是模型在训练集上表现近乎完美,一到测试集就“翻车”。我刚入行那会儿,为了追求训练集上那99.9%的R,把模…

2026/8/26 7:20:00

软件测试面试全攻略:从核心概念到实战技巧与前沿趋势

1. 项目概述:一份持续生长的面试题库又到招聘季了,后台和社群里问测试面试题的朋友越来越多。我发现一个挺有意思的现象:很多人刷题,但刷得“很虚”。他们能背出“什么是黑盒测试”、“等价类划分的原则”,但一旦面试官…

2026/8/26 7:20:00

构建多Agent协作系统:从架构设计到权限隔离的实战指南

1. 项目概述:为什么我们需要一个多Agent协作系统?最近在折腾一个挺有意思的东西,我把它叫做“OpenCode多Agent协作系统”。简单来说,就是让一群具备不同能力的“数字员工”在一个统一的平台上,按照我们设定的规则&…

2026/8/26 7:15:00

MCP协议困境与AI工具集成新思路:从标准协议到务实方案

1. 项目概述:MCP的“死亡”与新生最近在AI开发圈里,一个话题被反复提及:“MCP Is Dead”。乍一听,这像是一个耸人听闻的标题,仿佛某个重要的技术标准一夜之间被宣判了死刑。但作为一名深度参与过多个AI Agent和工具集成…

2026/8/25 1:04:19

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 11:48:27

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 16:56:43

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 0:04:32

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 1:19:35

JSON总结

JSON概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,主要用于跟服务器进行交换数据。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C、C#、Java、JavaScr…

2026/8/26 1:19:35

保存连接sse 是什么原理,为什么不会一直请求

“保持连接”用的是 SSE(Server-Sent Events),本质是一个没有马上结束的 HTTP 请求。 过程是: 拷贝机发送一次请求: GET /api/code-sync/events服务器返回: Content-Type: text/event-stream但不关闭响应&…

2026/8/24 13:42:17

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/24 18:13:48

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/25 1:08:14

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…