用JavaScript玩转游戏物理(一)运动学模拟与粒子系统

发布时间:2019-04-15  栏目:计算机教程  评论:0 Comments

HTML5 游戏开发基础的教程

2017/03/24 · HTML5 · 2
评论
·
游戏

本文由 伯乐在线
紫洋
翻译,艾凌风
校稿。未经许可,禁止转载!
英文出处:Mikołaj Stolarski & Tomasz
Grajewski
。欢迎加入翻译组

在游戏的视觉效果定义其整体外观、感觉和游戏玩法本身。玩家被好的视觉体验所吸引,从而可达到产生更多的流量。这是创建成功的游戏和为玩家提供很多乐趣的关键。

在这篇文章中,我们基于 HTML5
游戏的不同视觉效果实现,提出几个构思方案。这些示例将依据我们自己的游戏《Skytte
》所实现的效果。我会解释支持他们的基本思想,
,并提供应用于我们项目中的效果。

复制代码 代码如下:

找到最近的相邻目标

火箭和电武器找到最近的敌人,我们遍历一群活跃的敌人并比较他们的位置与火箭的位置,或此项目中电武器射击点。当火箭锁定其目标,并会飞向目标时,直到它击中目标或飞出屏幕。电武器,它会等待目标出现在范围内。

一个基本的实现可能如下所示︰

JavaScript

function nearest(position, entities) { /* * Given position and an
array of entites, this function finds which entity is closest * to
`position` and distance. */ var distance, nearest = null,
nearestDistance = Infinity; for (var i = 0; i < entities.length; i++)
{ // Allow list of entities to contain the compared entity and ignore it
silently. if (position !== entities[i].position) { // Calculate
distance between two points, usually centers of mass of each entity.
distance = point2.distance(position, entities[i].position); if
(distance < nearestDistance) { nearestDistance = distance; nearest =
entities[i]; } } } // Return the closest entity and distance to it, as
it may come handy in some situations. return {‘entity’: nearest,
‘distance’: nearestDistance}; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function nearest(position, entities) {
  /*
   * Given position and an array of entites, this function finds which entity is closest
   * to `position` and distance.
   */
  var distance, nearest = null, nearestDistance = Infinity;
 
  for (var i = 0; i < entities.length; i++) {
    // Allow list of entities to contain the compared entity and ignore it silently.
    if (position !== entities[i].position) {
      // Calculate distance between two points, usually centers of mass of each entity.
      distance = point2.distance(position, entities[i].position);
 
      if (distance < nearestDistance) {
        nearestDistance = distance;
        nearest = entities[i];
      }
    }
  }
 
  // Return the closest entity and distance to it, as it may come handy in some situations.
  return {‘entity’: nearest, ‘distance’: nearestDistance};
}

总结
本文介绍了最简单的运动学模拟,使用欧拉方法作数值积分,并以此法去实现一个有简单碰撞的粒子系统。本文的精华其实只有两条简单公式(只有两个加数和两个乘数),希望让读者明白,其实物理模拟可以很简单。虽然本文的例子是在二维空间,但这例子能扩展至三维空间,只须把Vector2换成Vector3。本文完整源代码可下载
续篇会谈及在此基础上加入其他物理现象,有机会再加入其他物理模拟课题。希望各位支持,并给本人更多意见。

冲击波

https://player.vimeo.com/video/135389040

在 Skytte 的冲击波武器。

这种武器是更复杂一点。它也绘制简单精灵作为子弹,但却有一些代码,一点点传播开,并应用随机速度。这给这个武器带来了更具破坏性的感觉,,所以玩家觉得他们可以施加比血浆武器更大的伤害,
并且在敌人中间有更好的控制人群。

该代码工作方式类似于血浆武器代码,但是它生成三发子弹,每个子弹都有一个稍微不同的方向。

JavaScript

// BlaserProjectile inherits from Entity class var topBullet = new
BlasterProjectile(); // This bullet will move slightly up. var
middleBullet = new BlasterProjectile(); // This bullet will move
horizontally. var bottomBullet = new BlasterProjectile(); // This bullet
will move slightly down. var direction; // Angle 0 is pointing directly
to the right. // We start with the bullet moving slightly upwards.
direction = vector2.direction(radians(-5)); // Convert angle to an unit
vector topBullet.applyImpulse(direction, 30); direction =
vector2.direction(radians(0)); middleBullet.applyImpulse(direction, 30);
direction = vector2.direction(radians(5));
middleBullet.applyImpulse(direction, 30);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BlaserProjectile inherits from Entity class
var topBullet = new BlasterProjectile();  // This bullet will move slightly up.
var middleBullet = new BlasterProjectile();  // This bullet will move horizontally.
var bottomBullet = new BlasterProjectile();  // This bullet will move slightly down.
var direction;
 
// Angle 0 is pointing directly to the right.
// We start with the bullet moving slightly upwards.
direction = vector2.direction(radians(-5));  // Convert angle to an unit vector
topBullet.applyImpulse(direction, 30);
 
direction = vector2.direction(radians(0));
middleBullet.applyImpulse(direction, 30);
 
direction = vector2.direction(radians(5));
middleBullet.applyImpulse(direction, 30);

上面的代码需要一些数学函数来实现:

JavaScript

function radians(angle) { return angle * Math.PI / 180; } // Note that
this function is different from `point2.direction`. // Please don’t
confuse them. vector2.direction = function(angle) { /* * Converts an
angle in radians to a unit vector. Angle of 0 gives vector x=1, y=0. */
var x = Math.cos(angle); var y = Math.sin(angle); return vector2(x, y);
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function radians(angle) {
  return angle * Math.PI / 180;
}
 
// Note that this function is different from `point2.direction`.
// Please don’t confuse them.
vector2.direction = function(angle) {
  /*
   * Converts an angle in radians to a unit vector. Angle of 0 gives vector x=1, y=0.
   */
  var x = Math.cos(angle);
  var y = Math.sin(angle);
  return vector2(x, y);
};

游戏循环
粒子系统通常可分为三个周期:
发射粒子
模拟粒子(粒子老化、碰撞、运动学模拟等等)
渲染粒子
在游戏循环(game loop)中,需要对每个粒子系统执行以上的三个步骤。
生与死
在本文的例子里,用一个JavaScript数组particles储存所有活的粒子。产生一个粒子只是把它加到数组末端。代码片段如下:

https://player.vimeo.com/video/135389040

在 Skytte中雷武器。

这很有趣。武器射激光射线,但它在每个帧的程序生成
(这将在稍后解释)。为了探测命中, 它会创建一个矩形对撞机,
它会在与敌人碰撞时每秒钟造成伤害。

复制代码 代码如下:

火箭

https://vimeo.com/135389043

图 8︰ 在 Skytte中火箭武器。

这种武器射导弹。火箭是一个精灵,
一个粒子发射器附着在它的末端。还有一些更复杂的逻辑,比如搜寻最近的敌人或限制火箭的转弯值,
使其更少机动性。。此外,火箭就不会立即寻找敌方目标 — —
他们直接飞行一段时间, 以避免不切实际的行为。

火箭走向他们的相邻的目标。这是通过计算弹丸在给定的方向移动所需的适当力量来实现的。为了避免只在直线上移动,
计算的力在 skytte不应该太大。

假设,火箭从前面所述的实体类继承的类。

JavaScript

Rocket.prototype.update = function(elapsed) { var direction; if
(this.target) { // Assuming that `this.target` points to the nearest
enemy ship. direction = point2.direction(this.position,
this.target.position); } else { // No target, so fly ahead. // This will
fail for objects that are still, so remember to apply some initial
velocity when spawning rockets. direction =
vector2.normalize(this.velocity); } // You can use any number here,
depends on the speed of the rocket, target and units used.
this.applyForce(direction, 10); // Simple inheritance here, calling
parent’s `update()`, so rocket actually moves.
Entity.prototype.update.apply(this, arguments); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Rocket.prototype.update = function(elapsed) {
  var direction;
 
  if (this.target) {
    // Assuming that `this.target` points to the nearest enemy ship.
    direction = point2.direction(this.position, this.target.position);
  } else {
    // No target, so fly ahead.
    // This will fail for objects that are still, so remember to apply some initial velocity when spawning rockets.
    direction = vector2.normalize(this.velocity);
  }
 
  // You can use any number here, depends on the speed of the rocket, target and units used.
  this.applyForce(direction, 10);
 
  // Simple inheritance here, calling parent’s `update()`, so rocket actually moves.
  Entity.prototype.update.apply(this, arguments);
};

复制代码 代码如下:

等离子

https://vimeo.com/135389044

在 Skytte中的等离子武器。

这是我们游戏中最基本的武器,
每次都是一枪。没有用于这种武器的特殊算法。当等离子子弹发射时,
游戏只需绘制一个随着时间推移而旋转的精灵。

简单的等离子子弹可以催生像这样︰

JavaScript

// PlasmaProjectile inherits from Entity class var plasma = new
PlasmaProjectile(); // Move right (assuming that X axis is pointing
right). var direction = vector2(1, 0); // 20 meters per second.
plasma.applyImpulse(direction, 20);

1
2
3
4
5
6
7
8
// PlasmaProjectile inherits from Entity class
var plasma = new PlasmaProjectile();
 
// Move right (assuming that X axis is pointing right).
var direction = vector2(1, 0);
 
// 20 meters per second.
plasma.applyImpulse(direction, 20);

您可能感兴趣的文章:

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

美高梅娱乐场网站 1
美高梅娱乐场网站 2

2 赞 2 收藏 2
评论

<button
onclick=”eval(document.getElementById(‘kinematicsCode’).value)”
type=”button”>Run</button>
<button onclick=”stop();” type=”button”>Stop</button>
<button onclick=”clearCanvas();”
type=”button”>Clear</button>
<table border=”0″ style=”width: 100%;”>
<tbody>
<tr>
<td><canvas id=”kinematicsCancas” width=”400″
height=”400″></canvas></td>
<td width=”10″> </td>
<td width=”100%” valign=”top”>
<h4>修改代码试试看</h4>
<li>改变起始位置</li>
<li>改变起始速度(包括方向) </li>
<li>改变加速度</li>

你会学到什么

在我们开始之前, 我想列出一些我希望你能从本文中学习的知识:

  • 基本的游戏设计
    我们来看看通常用于制造游戏和游戏效果的模式:
    游戏循环、精灵、碰撞和粒子系统。
  • 视觉效果的基本实现
    我们还将探讨支持这些模式的理论和一些代码示例。

在函数kill()里,用了一个技巧。因为粒子在数组里的次序并不重要,要删除中间一个粒子,只需要复制最末的粒子到那个元素,并用pop()移除最末的粒子就可以。这通常比直接删除数组中间的元素快(在C++中使用数组或std::vector亦是)。
运动学模拟
把本文最重要的两句运动学模拟代码套用至所有粒子就可以。另外,每次模拟会先把引力加速度写入粒子的加速度。这样做是为了将来可以每次改变加速度(续篇会谈这方面)。

武器的影响

现在我要解释一下, 在我们的 HTML5 游戏中, 某些武器效果是如何射击的

// 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;
};

高射炮

https://vimeo.com/135389047

在 Skytte 中高射炮武器。

高射炮被设计为射击许多小子弹 (象猎枪),
是小斑点精灵。它有一些在锥形区域内的点的位置用特定的逻辑来随机生成这些。

美高梅娱乐场网站 3

高射炮武器子弹锥区。

在一个圆锥形的区域中生成随机点︰

JavaScript

// Firstly get random angle in degrees in the allowed span. Note that
the span below always points to the right. var angle =
radians(random.uniform(-40, 40)); // Now get how far from the barrel the
projectile should spawn. var distance = random.uniform(5, 150); // Join
angle and distance to create an offset from the gun’s barrel. var
direction = vector2.direction(angle); var offset = vector2(direction.x
* distance, direction.y * distance); // Now calculate absolute
position in the game world (you need a position of the barrel for this
purpose): var position = point2.move(barrel, offset);

1
2
3
4
5
6
7
8
9
10
11
12
// Firstly get random angle in degrees in the allowed span. Note that the span below always points to the right.
var angle = radians(random.uniform(-40, 40));
 
// Now get how far from the barrel the projectile should spawn.
var distance = random.uniform(5, 150);
 
// Join angle and distance to create an offset from the gun’s barrel.
var direction = vector2.direction(angle);
var offset = vector2(direction.x * distance, direction.y * distance);
 
// Now calculate absolute position in the game world (you need a position of the barrel for this purpose):
var position = point2.move(barrel, offset);

函数返回两个值之间的一个随机浮点数。一个简单的实现就像这个样子︰

JavaScript

random.uniform = function(min, max) { return min + (max-min) *
Math.random(); };

1
2
3
random.uniform = function(min, max) {
  return min + (max-min) * Math.random();
};

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

游戏循环

重要的是要认识到游戏对象并不真正在屏幕上移动。运动的假象是通过渲染一个游戏世界的屏幕快照,
随着游戏的时间的一点点推进 (通常是1/60 秒),
然后再渲染的东西。这实际上是一个停止和运动的效果, 并常在二维和三
维游戏中使用。游戏循环是一种实现此停止运动的机制。它是运行游戏所需的主要组件。它连续运行,
执行各种任务。在每个迭代中, 它处理用户输入, 移动实体, 检查碰撞,
并渲染游戏 (推荐按这个顺序)。它还控制了帧之间的游戏时间。

下面示例是用JavaScriptpgpg语言写的非常基本的游戏循环︰

JavaScript

var lastUpdate; function tick() { var now = window.Date.now(); if
(lastUpdate) { var elapsed = (now-lastUpdate) / 1000; lastUpdate = now;
// Update all game objects here. update(elapsed); // …and render them
somehow. render(); } else { // Skip first frame, so elapsed is not 0.
lastUpdate = now; } // This makes the `tick` function run 60 frames
per second (or slower, depends on monitor’s refresh rate).
window.requestAnimationFrame(tick); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var lastUpdate;
 
function tick() {
  var now = window.Date.now();
 
  if (lastUpdate) {
    var elapsed = (now-lastUpdate) / 1000;
    lastUpdate = now;
 
    // Update all game objects here.
    update(elapsed);
    // …and render them somehow.
    render();
  } else {
    // Skip first frame, so elapsed is not 0.
    lastUpdate = now;
  }
 
  // This makes the `tick` function run 60 frames per second (or slower, depends on monitor’s refresh rate).
  window.requestAnimationFrame(tick);
};

美高梅娱乐场网站,请注意,上面的例子中是非常简单。它使用可变时间增量
(已用的变量),并建议升级此代码以使用固定的增量时间。有关详细信息,
请参阅本文。

系列简介
也许,三百年前的艾萨克·牛顿爵士(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):
美高梅娱乐场网站 4
当中,和分别是t=0时的y轴起始座标和速度,而g则是重力加速度(gravitational
acceleration)。
这分析解虽然简单,但是有一些缺点,例如g是常数,在模拟过程中不能改变;另外,当物体遇到障碍物,产生碰撞时,这公式也很难处理这种不连续性(discontinuity)

在计算机模拟中,通常需要计算连续的物体状态。用游戏的用语,就是计算第一帧的状态、第二帧的状态等等。设物体在任意时间t的状态:位置矢量为、速度矢量为、加速度矢量为。我们希望从时间的状态,计算下一个模拟时间的状态。最简单的方法,是采用欧拉方法(Euler
method)作数值积分(numerical integration):
美高梅娱乐场网站 5
欧拉方法非常简单,但有准确度和稳定性问题,本文会先忽略这些问题。本文的例子采用二维空间,我们先实现一个JavaScript二维矢量类:

碰撞检测

碰撞检测是指发现物体之间的交点。这对于许多游戏是必不可少的,
因为它用来检测玩家击中墙壁或子弹击中敌人, 诸如此类等等。当检测到碰撞时,
它可以用于游戏逻辑设计中;例如, 当子弹击中玩家时, 健康分数会减少十点。

有很多碰撞检测算法, 因为它是一个性能繁重的操作,
明智的选择最好的方法是很重要的。要了解有关碰撞检测、算法以及如何实现它们的更多信息,
这里有一篇来自MDN 的文章。

var ps = new ParticleSystem();
ps.effectors.push(new ChamberBox(0, 0, 400, 400));
var dt = 0.01;
var oldMousePosition = Vector2.zero, newMousePosition = Vector2.zero;
function sampleDirection(angle1, angle2) {
var t = Math.random();
var theta = angle1 * t + angle2 * (1 – t);
return new Vector2(Math.cos(theta), Math.sin(theta));
}
function sampleColor(color1, color2) {
var t = Math.random();
return color1.multiply(t).add(color2.multiply(1 – t));
}
function sampleNumber(value1, value2) {
var t = Math.random();
return value1 * t + value2 * (1 – t);
}
function step() {
var velocity =
newMousePosition.subtract(oldMousePosition).multiply(10);
velocity = velocity.add(sampleDirection(0, Math.PI *
2).multiply(20));
var color = sampleColor(Color.red, Color.yellow);
var life = sampleNumber(1, 2);
var size = sampleNumber(2, 4);
ps.emit(new Particle(newMousePosition, velocity, life, color, size));
oldMousePosition = newMousePosition;
ps.simulate(dt);
ctx.fillStyle=”rgba(0, 0, 0, 0.1)”;
ctx.fillRect(0,0,canvas.width,canvas.height);
ps.render(ctx);
}
start(“interactiveEmitCanvas”, step);
canvas.onmousemove = function(e) {
if (e.layerX || e.layerX == 0) { // Firefox
e.target.style.position=’relative’;
newMousePosition = new Vector2(e.layerX, e.layerY);
}
else
newMousePosition = new Vector2(e.offsetX, e.offsetY);
};
<button
onclick=”eval(document.getElementById(‘interactiveEmitCode’).value)”
type=”button”>Run</button>
<button onclick=”stop();” type=”button”>Stop</button>
<canvas id=”interactiveEmitCanvas” width=”400″
height=”400″></canvas>

产生弯曲的线条

为了制造激光束效应和电子武器,
我们开发了一种计算和变换玩家的舰船和敌人之间的直线距离的算法。换句话说,我们测量的两个对象之间的距离,找到中间点,并在这一段距离随机移动它。我们为每个新场景创建重复此操作。

若要绘制这些部分我们使用 HTML5 绘制函数
lineTo()。为了实现发光颜色我们使用多行绘制到另一个更不透明的颜色和更高的描边宽度。

美高梅娱乐场网站 6

程序上弯曲的线条。

要查找并偏移其他两个点之间的点︰

JavaScript

var offset, midpoint; midpoint = point2.midpoint(A, B); // Calculate an
unit-length vector pointing from A to B. offset = point2.direction(A,
B); // Rotate this vector 90 degrees clockwise. offset =
vector2.perpendicular(offset); // We want our offset to work in two
directions perpendicular to the segment AB: up and down. if
(random.sign() === -1) { // Rotate offset by 180 degrees. offset.x =
-offset.x; offset.y = -offset.y; } // Move the midpoint by an offset.
var offsetLength = Math.random() * 10; // Offset by 10 pixels for
example. midpoint.x += offset.x * offsetLength; midpoint.y += offset.y
* offsetLength; Below are functions used in the above code:
point2.midpoint = function(a, b) { var x = (a.x+b.x) / 2; var y =
(a.y+b.y) / 2; return point2(x, y); }; vector2.perpendicular =
function(v) { /* * Rotates a vector by 90 degrees clockwise. */
return vector2(-v.y, v.x); }; random.sign = function() { return
Math.random() < 0.5 ? -1 : 1; };

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
32
33
34
35
36
37
38
39
var offset, midpoint;
 
midpoint = point2.midpoint(A, B);
 
// Calculate an unit-length vector pointing from A to B.
offset = point2.direction(A, B);
 
// Rotate this vector 90 degrees clockwise.
offset = vector2.perpendicular(offset);
 
// We want our offset to work in two directions perpendicular to the segment AB: up and down.
if (random.sign() === -1) {
  // Rotate offset by 180 degrees.
  offset.x = -offset.x;
  offset.y = -offset.y;
}
 
// Move the midpoint by an offset.
var offsetLength = Math.random() * 10;  // Offset by 10 pixels for example.
midpoint.x += offset.x * offsetLength;
midpoint.y += offset.y * offsetLength;
 
Below are functions used in the above code:
point2.midpoint = function(a, b) {
  var x = (a.x+b.x) / 2;
  var y = (a.y+b.y) / 2;
  return point2(x, y);
};
 
vector2.perpendicular = function(v) {
  /*
   * Rotates a vector by 90 degrees clockwise.
   */
  return vector2(-v.y, v.x);
};
 
random.sign = function() {
  return Math.random() < 0.5 ? -1 : 1;
};

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

常见的模式

让我们从游戏开发中常用的大一些模式和元素开始

简单碰撞
为了说明用数值积分相对于分析解的优点,本文在粒子系统上加简单的碰撞。我们想加入一个需求,当粒子碰到长方形室(可设为整个Canvas大小)的内壁,就会碰撞反弹,碰撞是完全弹性的(perfectly
elastic collision)。
在程序设计上,我把这功能用回调方式进行。
ParticleSystem类有一个effectors数组,在进行运动学模拟之前,先执行每个effectors对象的apply()函数:
而长方形室就这样实现:

精灵表

这些都用来在一个图像中合成一套单个精灵。这减少了在游戏中文件的数量,从而减少内存和处理电源使用。精灵表包含许多单精灵堆积彼此相邻的行和列,和类似精灵的图像文件,它们包含可用于静态或动画。

美高梅娱乐场网站 7

精灵表例子。(图像来源: Kriplozoik)

下面是Code + Web的文章, 帮助您更好地理解使用精灵表的益处。

复制代码 代码如下:

欧拉积分

欧拉积分是运动的积分方程的一种方法。每个对象的位置计算基于其速度,质量和力量,并需要重新计算每个
tick
在游戏循环。欧拉方法是最基本和最有用的像侧滚动的射击游戏,但也有其它的方法,如Verlet
积分和 RK4积分,会更好地完成其他任务。下面我将展示一个简单的实现的想法。

你需要一个基本的结构以容纳对象的位置、
速度和其他运动相关的数据。我们提出两个相同的结构,但每一个都有不同的意义,在世界空间中︰
点和矢量。游戏引擎通常使用某种类型的矢量类,但点和矢量之间的区别是非常重要的,大大提高了代码的可读性
(例如,您计算不是两个矢量,但这两个点之间的距离,这是更自然)。

<button
onclick=”eval(document.getElementById(‘collisionChamberCode’).value)”
type=”button”>Run</button>
<button onclick=”stop();” type=”button”>Stop</button>
<canvas id=”collisionChamberCanvas” width=”400″
height=”400″></canvas>

关于作者:紫洋

美高梅娱乐场网站 8

除非这世界如我所愿,开启更好的应用开发定制之旅:设计:用户旅程故事板,线性原型图,信息架构,交互流程设计,高保真原型确认研发:产品调研、竞品分析、可用性测试、渐进式迭代设计工具:Sketch
3, Photoshop, Illustrator, Keynote,Axure开发语言:HTML5, CS…

个人主页
·
我的文章
·
13
·
     

美高梅娱乐场网站 9

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();
}
// …
}

美高梅娱乐场网站 10

矢量点积

点积是一个矢量投影矢量 b 上的长度。返回的值为 1
表示两个矢量指向同一方向。值为-1 意味着矢量方向相反的矢量 b 点。值为 0
表示该矢量是垂直于矢量 b。

这里是实体类的示例,以便其他对象可以从它继承。只描述了与运动相关的基本属性。

JavaScript

function Entity() { … // Center of mass usually. this.position =
point2(); // Linear velocity. // There is also something like angular
velocity, not described here. this.velocity = vector2(); // Acceleration
could also be named `force`, like in the Box2D engine.
this.acceleration = vector2(); this.mass = 1; … }

1
2
3
4
5
6
7
8
9
10
11
12
function Entity() {
  …
  // Center of mass usually.
  this.position = point2();
  // Linear velocity.
  // There is also something like angular velocity, not described here.
  this.velocity = vector2();
  // Acceleration could also be named `force`, like in the Box2D engine.
  this.acceleration = vector2();
  this.mass = 1;
  …
}

您可以在你的游戏中使用像素或米为单位。我们鼓励您使用米,因为在开发过程中,它更容易平衡的事情。速度,应该是米每秒,而加速度应该是米每秒的平方。

当使用一个第三方物理引擎,只是将存储在您的实体类的物理主体(或主体集)
的引用。然后,物理引擎将在每个主体内存储所述的属性,如位置和速度。

基本的欧拉积分看起来像这样︰

JavaScript

acceleration = force / mass velocity += acceleration position +=
velocity

1
2
3
acceleration = force / mass
velocity += acceleration
position += velocity

上面的代码必须在游戏中每个对象的每个帧中执行。下面是在 JavaScript
中的基本执行代码︰

JavaScript

Entity.prototype.update = function(elapsed) { // Acceleration is usually
0 and is set from the outside. // Velocity is an amount of movement
(meters or pixels) per second. this.velocity.x += this.acceleration.x *
elapsed; this.velocity.y += this.acceleration.y * elapsed;
this.position.x += this.velocity.x * elapsed; this.position.y +=
this.velocity.y * elapsed; … this.acceleration.x =
this.acceleration.y = 0; }

1
2
3
4
5
6
7
8
9
10
11
12
13
Entity.prototype.update = function(elapsed) {
  // Acceleration is usually 0 and is set from the outside.
  // Velocity is an amount of movement (meters or pixels) per second.
  this.velocity.x += this.acceleration.x * elapsed;
  this.velocity.y += this.acceleration.y * elapsed;
 
  this.position.x += this.velocity.x * elapsed;
  this.position.y += this.velocity.y * elapsed;
 
  …
 
  this.acceleration.x = this.acceleration.y = 0;
}

经过的是自最后一个帧 (自最近一次调用此方法) 所经过的时间量
(以秒为单位)。对于运行在每秒 60 帧的游戏,经过的值通常是 1/60 秒,也就是
0.016 (6) s。

上文提到的增量时间的文章也涵盖了这个问题。

要移动对象,您可以更改其加速度或速度。为实现此目的,应使用如下所示的两个函数︰

JavaScript

Entity.prototype.applyForce = function(force, scale) { if (typeof scale
=== ‘undefined’) { scale = 1; } this.acceleration.x += force.x * scale
/ this.mass; this.acceleration.y += force.y * scale / this.mass; };
Entity.prototype.applyImpulse = function(impulse, scale) { if (typeof
scale === ‘undefined’) { scale = 1; } this.velocity.x += impulse.x *
scale / this.mass; this.velocity.y += impulse.y * scale / this.mass; };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Entity.prototype.applyForce = function(force, scale) {
  if (typeof scale === ‘undefined’) {
    scale = 1;
  }
  this.acceleration.x += force.x * scale / this.mass;
  this.acceleration.y += force.y * scale / this.mass;
};
 
Entity.prototype.applyImpulse = function(impulse, scale) {
  if (typeof scale === ‘undefined’) {
    scale = 1;
  }
  this.velocity.x += impulse.x * scale / this.mass;
  this.velocity.y += impulse.y * scale / this.mass;
};

要向右移动一个对象你可以这样做︰

JavaScript

// 10 meters per second in the right direction (x=10, y=0). var right =
vector2(10, 0); if (keys.left.isDown) // The -1 inverts a vector, i.e.
the vector will point in the opposite direction, // but maintain
magnitude (length). spaceShip.applyImpulse(right, -1); if
(keys.right.isDown) spaceShip.applyImpulse(right, 1);

1
2
3
4
5
6
7
8
9
// 10 meters per second in the right direction (x=10, y=0).
var right = vector2(10, 0);
 
if (keys.left.isDown)
  // The -1 inverts a vector, i.e. the vector will point in the opposite direction,
  // but maintain magnitude (length).
  spaceShip.applyImpulse(right, -1);
if (keys.right.isDown)
  spaceShip.applyImpulse(right, 1);

请注意,在运动中设置的对象保持运动。您需要实现某种减速停止移动的物体
(空气阻力或摩擦,也许)。

//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);
};
// …
}

矢量

一个矢量是一个具有长度 (或大小) 的几何对象和方向。2 D
游戏中矢量主要是用于描述力(例如重力、 空气阻力和风)
和速度,以及禁止运动或光线反射。矢量有许多用途。

JavaScript

function vector2(x, y) { return {‘x’: x || 0, ‘y’: y || 0}; }

1
2
3
function vector2(x, y) {
  return {‘x’: x || 0, ‘y’: y || 0};
}

上述函数创建了新的二维矢量和点。在这种情况下, 我们不会在 javascript
中使用 new 运算符来获得大量的性能。还要注意, 有一些
第三方库可用来操纵矢量 (glMatrix 是一个很好的候选对象)。

下面是在上面定义的二维结构上使用的一些非常常用的函数。首先,
计算两点之间的距离:

JavaScript

point2.distance = function(a, b) { // The x and y variables hold a
vector pointing from point b to point a. var x = a.x – b.x; var y = a.y

  • b.y; // Now, distance between the points is just length (magnitude) of
    this vector, calculated like this: return Math.sqrt(x*x + y*y); };
1
2
3
4
5
6
7
point2.distance = function(a, b) {
  // The x and y variables hold a vector pointing from point b to point a.
  var x = a.x – b.x;
  var y = a.y – b.y;
  // Now, distance between the points is just length (magnitude) of this vector, calculated like this:
  return Math.sqrt(x*x + y*y);
};

矢量的大小 (长度) 可以直接从最后一行的上面的函数,这样计算︰

JavaScript

vector2.length = function(vector) { return Math.sqrt(vector.x*vector.x

  • vector.y*vector.y); };
1
2
3
vector2.length = function(vector) {
  return Math.sqrt(vector.x*vector.x + vector.y*vector.y);
};

美高梅娱乐场网站 11

矢量的长度。

矢量规范化也是非常方便的。下面的函数调整矢量的大小,所以它成为一个单位矢量;也就是说,它的长度是
1,但保持它的方向。

JavaScript

vector2.normalize = function(vector) { var length =
vector2.length(vector); if (length > 0) { return vector2(vector.x /
length, vector.y / length); } else { // zero-length vectors cannot be
normalized, as they do not have direction. return vector2(); } };

1
2
3
4
5
6
7
8
9
10
vector2.normalize = function(vector) {
  var length = vector2.length(vector);
 
  if (length > 0) {
    return vector2(vector.x / length, vector.y / length);
  } else {
    // zero-length vectors cannot be normalized, as they do not have direction.
    return vector2();
  }
};

美高梅娱乐场网站 12

矢量归一化。

另一个有用的例子是,其方向指从一个位置到另一个位置︰

JavaScript

// Note that this function is different from `vector2.direction`. //
Please don’t confuse them. point2.direction = function(from, to) { var x
= to.x – from.x; var y = to.y – from.y; var length = Math.sqrt(x*x +
y*y); if (length > 0) { return vector2(x / length, y / length); }
else { // `from` and `to` are identical return vector2(); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Note that this function is different from `vector2.direction`.
// Please don’t confuse them.
point2.direction = function(from, to) {
  var x = to.x – from.x;
  var y = to.y – from.y;
  var length = Math.sqrt(x*x + y*y);
 
  if (length > 0) {
    return vector2(x / length, y / length);
  } else {
    // `from` and `to` are identical
    return vector2();
  }
};

点积是对两个矢量 (通常为单位矢量) 的运算,
它返回一个标量的数字, 表示这些矢量的角度之间的关系。

JavaScript

vector2.dot = function(a, b) { return a.x*b.x + a.y*b.y; };

1
2
3
vector2.dot = function(a, b) {
  return a.x*b.x + a.y*b.y;
};

基本粒子系统完成
以下的例子里,每帧会发射一个粒子,其位置在画布中间(200,200),发射方向是360度,速率为100,生命为1秒,红色、半径为5象素。

简单地说, 它代表了二维空间空间中的一个元素, 它有 x 和 y 坐标,
它定义了该点在该空间中的位置。

JavaScript

function point2(x, y) { return {‘x’: x || 0, ‘y’: y || 0}; }

1
2
3
function point2(x, y) {
  return {‘x’: x || 0, ‘y’: y || 0};
}

复制代码 代码如下:

使用技术

< body>

https://vimeo.com/135389046

在 Skytte 中的电武器。

电是射击在特定半径范围内的敌人的武器。它有一个有限的范围,
但可以射击在几个敌人, 并总是射击成功。它使用相同的算法绘制曲线,
以模拟闪电作为射线武器, 但具有更高的曲线因子。

复制代码 代码如下:

结论

这些主题涵盖只支持它们的基本思路。我希望读这篇文章后,你对如何开始并持续发展游戏项目会有更好的主意。查阅下面的参考,你可以自己试着做类似的游戏项目。

打赏支持我翻译更多好文章,谢谢!


打赏译者

复制代码 代码如下:

留下评论

网站地图xml地图