`

用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):

当中,分别是t=0时的y轴起始座标和速度,而g则是重力加速度(gravitational acceleration)。

这分析解虽然简单,但是有一些缺点,例如g是常数,在模拟过程中不能改变;另外,当物体遇到障碍物,产生碰撞时,这公式也很难处理这种不连续性(discontinuity) 。

在计算机模拟中,通常需要计算连续的物体状态。用游戏的用语,就是计算第一帧的状态、第二帧的状态等等。设物体在任意时间t的状态:位置矢量为、速度矢量为、加速度矢量为。我们希望从时间的状态,计算下一个模拟时间的状态。最简单的方法,是采用欧拉方法(Euler method)作数值积分(numerical integration):

欧拉方法非常简单,但有准确度和稳定性问题,本文会先忽略这些问题。本文的例子采用二维空间,我们先实现一个JavaScript二维矢量类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Vector2.js
Vector2 = function(x, y) { this.x = x; this.y = y; };
 
Vector2.prototype = {
    copy : function() { return new Vector2(this.x, this.y); },
    length : function() { return Math.sqrt(this.x * this.x + this.y * this.y); },
    sqrLength : function() { return this.x * this.x + this.y * this.y; },
    normalize : function() { var inv = 1/this.length(); return new Vector2(this.x * inv, this.y * inv); },
    negate : function() { return new Vector2(-this.x, -this.y); },
    add : function(v) { return new Vector2(this.x + v.x, this.y + v.y); },
    subtract : function(v) { return new Vector2(this.x - v.x, this.y - v.y); },
    multiply : function(f) { return new Vector2(this.x * f, this.y * f); },
    divide : function(f) { var invf = 1/f; return new Vector2(this.x * invf, this.y * invf); },
    dot : function(v) { return this.x * v.x + this.y * v.y; }
};
 
Vector2.zero = new Vector2(0, 0);

然后,就可以用HTML5 Canvas去描绘模拟的过程:

 
Run Stop Clear 

   

修改代码试试看

  1. 改变起始位置
  2. 改变起始速度(包括方向)
  3. 改变加速度

这程序的核心就是step()函数头两行代码。很简单吧?

粒子系统

粒子系统(particle system)是图形里常用的特效。粒子系统可应用运动学模拟来做到很多不同的效果。粒子系统在游戏和动画中,常常会用来做雨点、火花、烟、爆炸等等不同的视觉效果。有时候,也会做出一些游戏性相关的功能,例如敌人被打败后会发出一些闪光,主角可以把它们吸收。

粒子的定义

粒子系统模拟大量的粒子,并通常用某些方法把粒子渲染。粒子通常有以下特性:

  1. 粒子是独立的,粒子之间互不影响(不碰撞、没有力)
  2. 粒子有生命周期,生命结束后会消失
  3. 粒子可以理解为空间的一个点,有时候也可以设定半径作为球体和环境碰撞
  4. 粒子带有运动状态,也有其他外观状态(例如颜色、影像等)
  5. 粒子可以只有线性运动,而不考虑旋转运动(也有例外)

以下是本文例子里实现的粒子类:

1
2
3
4
5
6
7
8
9
10
// Particle.js
Particle = 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;
};

游戏循环

粒子系统通常可分为三个周期:

  1. 发射粒子
  2. 模拟粒子(粒子老化、碰撞、运动学模拟等等)
  3. 渲染粒子

在游戏循环(game loop)中,需要对每个粒子系统执行以上的三个步骤。

生与死

在本文的例子里,用一个JavaScript数组particles储存所有活的粒子。产生一个粒子只是把它加到数组末端。代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ParticleSystem.js
function ParticleSystem() {
    // Private fields
    var that = this;
    var particles = new Array();
 
    // Public fields
    this.gravity = new Vector2(0, 100);
    this.effectors = new Array();
 
    // Public methods
         
    this.emit = function(particle) {
        particles.push(particle);
    };
 
    // ...
}

粒子在初始化时,年龄(age)设为零,生命(life)则是固定的。年龄和生命的单位都是秒。每个模拟步,都会把粒子老化,即是把年龄增加,年龄超过生命,就会死亡。代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function ParticleSystem() {
    // ...
    this.simulate = function(dt) {
        aging(dt);
        applyGravity();
        applyEffectors();
        kinematics(dt);
    };
     
    // ...
 
    // Private methods
     
    function aging(dt) {
        for (var i = 0; i < particles.length; ) {
            var p = particles[i];
            p.age += dt;
            if (p.age >= p.life)
                kill(i);
            else
                i++;
        }
    }
 
    function kill(index) {
        if (particles.length > 1)
            particles[index] = particles[particles.length - 1];
        particles.pop();
    }
    // ...
}

在函数kill()里,用了一个技巧。因为粒子在数组里的次序并不重要,要删除中间一个粒子,只需要复制最末的粒子到那个元素,并用pop()移除最末的粒子就可以。这通常比直接删除数组中间的元素快(在C++中使用数组或std::vector亦是)。

运动学模拟

把本文最重要的两句运动学模拟代码套用至所有粒子就可以。另外,每次模拟会先把引力加速度写入粒子的加速度。这样做是为了将来可以每次改变加速度(续篇会谈这方面)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ParticleSystem() {
    // ...
    function applyGravity() {
        for (var i in particles)
            particles[i].acceleration = that.gravity;
    }
 
    function kinematics(dt) {
        for (var i in particles) {
            var p = particles[i];
            p.position = p.position.add(p.velocity.multiply(dt));
            p.velocity = p.velocity.add(p.acceleration.multiply(dt));
        }
    }
    // ...
}

渲染

粒子可以用很多不同方式渲染,例如用圆形、线段(当前位置和之前位置)、影像、精灵等等。本文采用圆形,并按年龄生命比来控制圆形的透明度,代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ParticleSystem() {
    // ...
    this.render = function(ctx) {
        for (var i in particles) {
            var p = particles[i];
            var alpha = 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 

   

修改代码试试看

  1. 改变发射位置
  2. 向上发射,发射范围在90度内
  3. 改变生命
  4. 改变半径
  5. 每帧发射5个粒子

简单碰撞

为了说明用数值积分相对于分析解的优点,本文在粒子系统上加简单的碰撞。我们想加入一个需求,当粒子碰到长方形室(可设为整个Canvas大小)的内壁,就会碰撞反弹,碰撞是完全弹性的(perfectly elastic collision)。

在程序设计上,我把这功能用回调方式进行。 ParticleSystem类有一个effectors数组,在进行运动学模拟之前,先执行每个effectors对象的apply()函数:

而长方形室就这样实现:

1
2
3
4
5
6
7
8
9
10
// ChamberBox.js
function ChamberBox(x1, y1, x2, y2) {
    this.apply = function(particle) {
        if (particle.position.x - particle.size < x1 || particle.position.x + particle.size > x2)
            particle.velocity.x = -particle.velocity.x;
 
        if (particle.position.y - particle.size < y1 || particle.position.y + particle.size > y2)
            particle.velocity.y = -particle.velocity.y;
    };
}

这其实就是当侦测到粒子超出内壁的范围,就反转该方向的速度分量。

此外,这例子的主循环不再每次把整个Canvas清空,而是每帧画一个半透明的黑色长方形,就可以模拟动态模糊(motion blur)的效果。粒子的颜色也是随机从两个颜色中取样。

 
Run Stop 

互动发射

最后一个例子加入互动功能,在鼠标位置发射粒子,粒子方向是按鼠标移动速度再加上一点噪音(noise)。粒子的大小和生命都加入了随机性。

 
Run Stop 

总结

本文介绍了最简单的运动学模拟,使用欧拉方法作数值积分,并以此法去实现一个有简单碰撞的粒子系统。本文的精华其实只有两条简单公式(只有两个加数和两个乘数),希望让读者明白,其实物理模拟可以很简单。虽然本文的例子是在二维空间,但这例子能扩展至三维空间,只须把Vector2换成Vector3。本文完整源代码可下载

续篇会谈及在此基础上加入其他物理现象,有机会再加入其他物理模拟课题。希望各位支持,并给本人更多意见。

 

转自: http://www.cnblogs.com/miloyip/archive/2010/06/14/Kinematics_ParticleSystem.html

分享到:
评论

相关推荐

    物理模拟小资料

    【物理模拟小资料】是一个关于物理模拟的学习资源集合,涵盖了多个不同的模拟项目和工具,适合对物理引擎感兴趣的初学者和开发者。这个压缩包包含了多种类型的文件,如HTML文档、PDF教程、文本文件以及两个不同版本...

    CarSimulator使用JavaScript实现基于模糊控制遗传算法和粒子群优化的模拟车

    在本项目"CarSimulator"中,开发者利用JavaScript这一强大且灵活的脚本语言,构建了一个复杂的模拟车辆系统。这个系统结合了模糊控制、遗传算法和粒子群优化等高级技术,为用户提供了一种动态且真实的驾驶体验。接...

    JavaScript粒子动画

    7. **粒子更新与绘制**:在`update()`方法中,根据粒子的属性和物理规则(如重力、阻力等)来改变它们的位置和速度。在`draw()`方法中,使用Canvas API的`fillRect()`或`beginPath()`、`arc()`等方法绘制粒子。 8. ...

    GPGPU弹性粘性粒子模拟_JavaScript_CSS_下载.zip

    标题 "GPGPU弹性粘性粒子模拟_JavaScript_CSS_下载.zip" 提示我们这是一个关于使用GPGPU(General-Purpose computing on Graphics Processing Units,图形处理器上的通用计算)技术进行弹性粘性粒子模拟的项目,该...

    韩顺平玩转javascript(百度云链接)

    韩顺平玩转javascript,韩顺平玩转javascript

    鼠标悬停粒子系统

    粒子系统是一种常见的计算机图形学技术,用于模拟大量个体的行为,如火焰、烟雾、雪花等。在网页中,粒子系统可以被用来创建各种互动效果,比如当鼠标移动或悬停时,屏幕上的粒子会根据预设的规则运动,形成各种动态...

    JavaScript成语接龙、ball-pool小游戏.rar

    2. **物理引擎**:模拟真实世界的物理运动,如重力、碰撞检测和反弹,通常需要自定义物理引擎或使用第三方库如Box2D。 3. **事件处理**:处理用户的点击或触摸事件,确定击球力度和方向,将这些信息转化为游戏内的...

    用javascript模拟太阳系行星的圆周运动.zip

    JavaScript使用时间循环来实现行星的连续运动,模拟天体物理学中的圆周运动规则。这通常涉及到数学公式,如向量运算、加速度和角速度的概念。 为了实现行星的运动,开发者可能使用了以下JavaScript技术: 1. `...

    Html5 效果之模拟实现物理效果

    WebGL允许开发者创建更逼真的物理模拟,例如弹跳球、旋转的物体等,甚至可以实现粒子系统和软体动力学。 8. **实时更新与动画**:在HTML5中,我们通常使用requestAnimationFrame函数来实现平滑的动画效果,它会在...

    javascript 做的小游戏 弹球 利用js控制层位置

    可以使用`&lt;canvas&gt;`元素创建一个画布,然后通过JavaScript在其上绘图,或者直接使用HTML元素来模拟游戏元素。 7. **CSS样式**:CSS用于设置游戏元素的外观,如颜色、大小和位置。通过CSS,我们可以使挡板和弹球看...

    粒子与鼠标互动粒子源码.zip

    粒子系统是一种模拟复杂物理现象的技术,它通过大量简单的个体(粒子)来表现复杂的集体行为。在本例中,这些粒子可能代表点状图形,它们按照预设的规则运动,形成动态的视觉效果。粒子系统常用于创建烟雾、火焰、...

    Matterjs一个JavaScript2D的Web刚体物理引擎

    Matter.js 是一个强大的JavaScript库,专门用于创建2D Web游戏和其他实时交互式应用中的刚体物理模拟。这个库由Liabru开发,以其高效、灵活和易于使用的特点而备受推崇。在JavaScript开发领域,尤其是在游戏开发中,...

    JavaScript-Particle-System.zip

    JavaScript粒子系统是一种在Web上创建动态视觉效果的技术,它利用JavaScript编程语言来模拟真实世界中的物理现象,如重力、速度和碰撞。在这个名为"JavaScript-Particle-System.zip"的压缩包中,我们很可能会找到一...

    javascript模拟坦克大战游戏(html5版)附源码

    【标题】"javascript模拟坦克大战游戏(html5版)附源码"揭示了这是一个基于JavaScript编程语言,使用HTML5技术开发的坦克大战游戏项目。在HTML5中,Canvas元素提供了画布功能,允许开发者通过JavaScript来绘制图形,...

    javascript 游戏大全 JS特效

    5. 21点游戏:21点是一种流行纸牌游戏,JavaScript可以模拟洗牌、发牌、计算点数、玩家和庄家决策的过程。游戏规则包括不能超过21点,以及何时选择“要牌”或“停牌”。 6. 益智游戏:益智游戏种类繁多,如数独、...

    webgl喷泉粒子发射动画特效

    粒子系统是一种模拟复杂效果的技术,通过大量简单的个体(粒子)组合来呈现如火焰、烟雾、水波等视觉效果。在这个特定的“喷泉粒子发射”特效中,每个粒子代表水滴或气泡,它们被发射到空中并根据物理规则移动,形成...

    彩虹守卫游戏,使用cocos2d-x和JavaScript开发

    Cocos2d-x提供了丰富的API,包括场景管理、精灵动画、物理引擎、粒子系统等,这些都是构建彩虹守卫游戏的基础元素。 在“彩虹守卫”这款游戏中,Cocos2d-x的场景管理机制用于组织游戏的不同阶段,比如游戏开始界面...

    JavaScript游戏开发.zip

    在游戏开发中,我们可能会用到碰撞检测、物体运动物理模拟等技术来增加游戏的复杂性和趣味性。 游戏逻辑构建涉及状态管理、游戏规则定义和算法设计。这可能包括游戏对象的状态(如生命值、得分)、游戏循环(更新...

    cpp-Box2D是一个2D游戏的物理引擎

    Box2D是一个广泛应用于2D游戏开发的开源物理引擎,主要由Erin Catto开发,用C++编写,为开发者提供了模拟现实世界物理效果的能力,如重力、碰撞检测、摩擦力和弹力等。这个引擎被众多游戏开发者采用,因为它简化了...

Global site tag (gtag.js) - Google Analytics