用JavaScript玩转游戏物理运动学模拟与粒子系统_javascript技巧_脚本之家

2019-12-05 13:09栏目:龙电竞官网
TAG:

关于作者:紫洋

图片 1

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

图片 2

Run

图片 3

矢量点积

点积是一个矢量投影矢量 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);

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

  • Math.floor + "," + Math.floor + "," + Math.floor + "," + alpha.toFixed"; ctx.beginPath(); ctx.arc(p.position.x, p.position.y, p.size, 0, Math.PI * 2, true); ctx.closePath; } } // ... } 基本粒子系统完成 以下的例子里,每帧会发射一个粒子,其位置在画布中间,发射方向是360度,速率为100,生命为1秒,红色、半径为5象素。 复制代码 代码如下: var ps = new ParticleSystem(); var dt = 0.01; function sampleDirection() { var theta = Math.random() * 2 * Math.PI; return new Vector2, Math.sin; } function step() { ps.emit(new Particle, sampleDirection, 1, Color.red, 5)); ps.simulate; ps.render; } start("basicParticleSystemCanvas", step);

武器的影响

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

粒子可以只有线性运动,而不考虑旋转运动

高射炮

在 Skytte 中高射炮武器。

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

图片 4

高射炮武器子弹锥区。

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

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

Run

在 Skytte 中的电武器。

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

Run

使用技术

粒子有生命周期,生命结束后会消失

游戏循环

重要的是要认识到游戏对象并不真正在屏幕上移动。运动的假象是通过渲染一个游戏世界的屏幕快照, 随着游戏的时间的一点点推进 (通常是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);
};

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

Stop

精灵

这些只是在游戏中代表一个对象的二维图像。精灵可以用于静态对象, 也可以用于动画对象, 当每个精灵代表一个帧序列动画。它们也可用于制作用户界面元素。

通常游戏包含从几十到几百精灵图片。为了减少内存的使用和处理这些映像所需的能力, 许多游戏使用精灵表。

粒子是独立的,粒子之间互不影响

精灵表

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

图片 5

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

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

修改代码试试看

改变起始位置 改变起始速度 改变加速度

等离子

在 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);

Stop

常见的模式

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

Stop

碰撞检测

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

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

修改代码试试看

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

火箭

图 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);
};

系列简介 也许,三百年前的艾萨克·牛顿爵士(Sir Issac Newton, 1643-1727)并没幻想过,物理学广泛地应用在今天许多游戏、动画中。为什么在这些应用中要使用物理学?笔者认为,自我们出生以来,一直感受着物理世界的规律,意识到物体在这世界是如何"正常移动",例如射球时球为抛物线 、石子系在一根线的末端会以固定频率摆动等等。要让游戏或动画中的物体有真实感,其移动方式就要符合我们对"正常移动"的预期。 今天的游戏动画应用了多种物理模拟技术,例如运动学模拟(kinematics simulation)、刚体动力学模拟(rigid body dynamics simulation)、绳子/布料模拟(string/cloth simulation)、柔体动力学模拟(soft body dynamics simulation)、流体动力学模拟(fluid dynamics simulation)等等。另外碰撞侦测是许多模拟系统里所需的。 本系列希望能介绍一些这方面最基础的知识,继续使用JavaScript做例子,以即时互动方式体验。 本文简介 作为系列第一篇,本文介绍最简单的运动学模拟,只有两条非常简单的公式。运动学模拟可以用来模拟很多物体运动,本文将会配合粒子系统做出一些视觉特效(粒子系统其实也可以用来做游戏的玩法,而不单是视觉特效)。 运动学模拟 运动学研究物体的移动,和动力学不同之处,在于运动学不考虑物体的质量/转动惯量,以及不考虑加之于物体的力。 我们先回忆牛顿第一运动定律: 当物体不受外力作用,或所受合力为零时,原先静止者恒静止,原先运动者恒沿着直线作等速度运动。该定律又称为「惯性定律」。此定律指出,每个物体除了其位置外,还有一个线性速度的状态。然而,只模拟不受力影响的物体并不有趣。撇开力的概念,我们可以用线性加速度去影响物体的运动。例如,要计算一个自由落体在任意时间t的y轴座标,可以使用以下的分析解: 当中,和分别是t=0时的y轴起始座标和速度,而g则是重力加速度(gravitational acceleration)。 这分析解虽然简单,但是有一些缺点,例如g是常数,在模拟过程中不能改变;另外,当物体遇到障碍物,产生碰撞时,这公式也很难处理这种不连续性 。 在计算机模拟中,通常需要计算连续的物体状态。用游戏的用语,就是计算第一帧的状态、第二帧的状态等等。设物体在任意时间t的状态:位置矢量为、速度矢量为、加速度矢量为。我们希望从时间的状态,计算下一个模拟时间的状态。最简单的方法,是采用欧拉方法作数值积分(numerical integration): 欧拉方法非常简单,但有准确度和稳定性问题,本文会先忽略这些问题。本文的例子采用二维空间,我们先实现一个JavaScript二维矢量类: 复制代码 代码如下: // Vector2.js Vector2 = function { this.x = x; this.y = y; }; Vector2.prototype = { copy : function() { return new Vector2; }, 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; }, add : function { return new Vector2(this.x + v.x, this.y + v.y); }, subtract : function { return new Vector2(this.x - v.x, this.y - v.y); }, multiply : function { return new Vector2(this.x * f, this.y * f); }, divide : function { var invf = 1/f; return new Vector2(this.x * invf, this.y * invf); }, dot : function { return this.x * v.x + this.y * v.y; } }; Vector2.zero = new Vector2; 然后,就可以用HTML5 Canvas去描绘模拟的过程: 复制代码 代码如下: var position = new Vector2; var velocity = new Vector2; var acceleration = new Vector2; var dt = 0.1; function step() { position = position.add); velocity = velocity.add(acceleration.multiply; ctx.strokeStyle = "#000000"; ctx.fillStyle = "#FFFFFF"; ctx.beginPath(); ctx.arc(position.x, position.y, 5, 0, Math.PI*2, true); ctx.closePath; ctx.stroke(); } start("kinematicsCancas", step); Run Stop Clear

版权声明:本文由龙竞技官网发布于龙电竞官网,转载请注明出处:用JavaScript玩转游戏物理运动学模拟与粒子系统_javascript技巧_脚本之家