中文字幕日韩一区二区_国产一区二区av_国产毛片av_久久久久国产一区_色婷婷电影_国产一区二区精品

用JavaScript玩轉(zhuǎn)游戲物理(一)運(yùn)動(dòng)學(xué)模擬與粒子系統(tǒng)

系列簡介
也許,三百年前的艾薩克?牛頓爵士(Sir Issac Newton, 1643-1727)并沒幻想過,物理學(xué)廣泛地應(yīng)用在今天許多游戲、動(dòng)畫中。為什么在這些應(yīng)用中要使用物理學(xué)?筆者認(rèn)為,自我們出生以來,一直感受著物理世界的規(guī)律,意識到物體在這世界是如何"正常移動(dòng)",例如射球時(shí)球?yàn)閽佄锞€(自旋的球可能會(huì)做成弧線球) 、石子系在一根線的末端會(huì)以固定頻率擺動(dòng)等等。要讓游戲或動(dòng)畫中的物體有真實(shí)感,其移動(dòng)方式就要符合我們對"正常移動(dòng)"的預(yù)期。
今天的游戲動(dòng)畫應(yīng)用了多種物理模擬技術(shù),例如運(yùn)動(dòng)學(xué)模擬(kinematics simulation)、剛體動(dòng)力學(xué)模擬(rigid body dynamics simulation)、繩子/布料模擬(string/cloth simulation)、柔體動(dòng)力學(xué)模擬(soft body dynamics simulation)、流體動(dòng)力學(xué)模擬(fluid dynamics simulation)等等。另外碰撞偵測(collision detection)是許多模擬系統(tǒng)里所需的。
本系列希望能介紹一些這方面最基礎(chǔ)的知識,繼續(xù)使用JavaScript做例子,以即時(shí)互動(dòng)方式體驗(yàn)。
本文簡介
作為系列第一篇,本文介紹最簡單的運(yùn)動(dòng)學(xué)模擬,只有兩條非常簡單的公式。運(yùn)動(dòng)學(xué)模擬可以用來模擬很多物體運(yùn)動(dòng)(例如馬里奧的跳躍、炮彈等),本文將會(huì)配合粒子系統(tǒng)做出一些視覺特效(粒子系統(tǒng)其實(shí)也可以用來做游戲的玩法,而不單是視覺特效)。
運(yùn)動(dòng)學(xué)模擬
運(yùn)動(dòng)學(xué)(kinematics)研究物體的移動(dòng),和動(dòng)力學(xué)(dynamics)不同之處,在于運(yùn)動(dòng)學(xué)不考慮物體的質(zhì)量(mass)/轉(zhuǎn)動(dòng)慣量(moment of inertia),以及不考慮加之于物體的力(force )和力矩(torque)。
我們先回憶牛頓第一運(yùn)動(dòng)定律:
當(dāng)物體不受外力作用,或所受合力為零時(shí),原先靜止者恒靜止,原先運(yùn)動(dòng)者恒沿著直線作等速度運(yùn)動(dòng)。該定律又稱為「慣性定律」。此定律指出,每個(gè)物體除了其位置(position)外,還有一個(gè)線性速度(linear velocity)的狀態(tài)。然而,只模擬不受力影響的物體并不有趣。撇開力的概念,我們可以用線性加速度(linear acceleration)去影響物體的運(yùn)動(dòng)。例如,要計(jì)算一個(gè)自由落體在任意時(shí)間t的y軸座標(biāo),可以使用以下的分析解(analytical solution):

當(dāng)中,和分別是t=0時(shí)的y軸起始座標(biāo)和速度,而g則是重力加速度(gravitational acceleration)。
這分析解雖然簡單,但是有一些缺點(diǎn),例如g是常數(shù),在模擬過程中不能改變;另外,當(dāng)物體遇到障礙物,產(chǎn)生碰撞時(shí),這公式也很難處理這種不連續(xù)性(discontinuity) 。
在計(jì)算機(jī)模擬中,通常需要計(jì)算連續(xù)的物體狀態(tài)。用游戲的用語,就是計(jì)算第一幀的狀態(tài)、第二幀的狀態(tài)等等。設(shè)物體在任意時(shí)間t的狀態(tài):位置矢量為、速度矢量為、加速度矢量為。我們希望從時(shí)間的狀態(tài),計(jì)算下一個(gè)模擬時(shí)間的狀態(tài)。最簡單的方法,是采用歐拉方法(Euler method)作數(shù)值積分(numerical integration):

歐拉方法非常簡單,但有準(zhǔn)確度和穩(wěn)定性問題,本文會(huì)先忽略這些問題。本文的例子采用二維空間,我們先實(shí)現(xiàn)一個(gè)JavaScript二維矢量類:
復(fù)制代碼 代碼如下:
// 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去描繪模擬的過程:
復(fù)制代碼 代碼如下:
var position = new Vector2(10, 200);
var velocity = new Vector2(50, -50);
var acceleration = new Vector2(0, 10);
var dt = 0.1;
function step() {
position = position.add(velocity.multiply(dt));
velocity = velocity.add(acceleration.multiply(dt));
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.arc(position.x, position.y, 5, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
start("kinematicsCancas", step);

<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>

</td>
</tr>
</tbody>
</table>

這程序的核心就是step()函數(shù)頭兩行代碼。很簡單吧?
粒子系統(tǒng)
粒子系統(tǒng)(particle system)是圖形里常用的特效。粒子系統(tǒng)可應(yīng)用運(yùn)動(dòng)學(xué)模擬來做到很多不同的效果。粒子系統(tǒng)在游戲和動(dòng)畫中,常常會(huì)用來做雨點(diǎn)、火花、煙、爆炸等等不同的視覺效果。有時(shí)候,也會(huì)做出一些游戲性相關(guān)的功能,例如敵人被打敗后會(huì)發(fā)出一些閃光,主角可以把它們吸收。
粒子的定義
粒子系統(tǒng)模擬大量的粒子,并通常用某些方法把粒子渲染。粒子通常有以下特性:
<li>粒子是獨(dú)立的,粒子之間互不影響(不碰撞、沒有力) </li>
<li>粒子有生命周期,生命結(jié)束后會(huì)消失</li>
<li>粒子可以理解為空間的一個(gè)點(diǎn),有時(shí)候也可以設(shè)定半徑作為球體和環(huán)境碰撞</li>
<li>粒子帶有運(yùn)動(dòng)狀態(tài),也有其他外觀狀態(tài)(例如顏色、影像等) </li>
<li>粒子可以只有線性運(yùn)動(dòng),而不考慮旋轉(zhuǎn)運(yùn)動(dòng)(也有例外) </li>

以下是本文例子里實(shí)現(xiàn)的粒子類:
復(fù)制代碼 代碼如下:// 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;
};

游戲循環(huán)
粒子系統(tǒng)通??煞譃槿齻€(gè)周期:
發(fā)射粒子
模擬粒子(粒子老化、碰撞、運(yùn)動(dòng)學(xué)模擬等等)
渲染粒子
游戲循環(huán)(game loop)中,需要對每個(gè)粒子系統(tǒng)執(zhí)行以上的三個(gè)步驟。
生與死
在本文的例子里,用一個(gè)JavaScript數(shù)組particles儲存所有活的粒子。產(chǎn)生一個(gè)粒子只是把它加到數(shù)組末端。代碼片段如下:
復(fù)制代碼 代碼如下://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);
};
// ...
}

粒子在初始化時(shí),年齡(age)設(shè)為零,生命(life)則是固定的。年齡和生命的單位都是秒。每個(gè)模擬步,都會(huì)把粒子老化,即是把年齡增加<span class="math">/Delta t</span>,年齡超過生命,就會(huì)死亡。代碼片段如下:
復(fù)制代碼 代碼如下: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();
}
// ...
}

在函數(shù)kill()里,用了一個(gè)技巧。因?yàn)榱W釉跀?shù)組里的次序并不重要,要?jiǎng)h除中間一個(gè)粒子,只需要復(fù)制最末的粒子到那個(gè)元素,并用pop()移除最末的粒子就可以。這通常比直接刪除數(shù)組中間的元素快(在C++中使用數(shù)組或std::vector亦是)。
運(yùn)動(dòng)學(xué)模擬
把本文最重要的兩句運(yùn)動(dòng)學(xué)模擬代碼套用至所有粒子就可以。另外,每次模擬會(huì)先把引力加速度寫入粒子的加速度。這樣做是為了將來可以每次改變加速度(續(xù)篇會(huì)談這方面)。
復(fù)制代碼 代碼如下: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));
}
}
// ...
}

渲染
粒子可以用很多不同方式渲染,例如用圓形、線段(當(dāng)前位置和之前位置)、影像、精靈等等。本文采用圓形,并按年齡生命比來控制圓形的透明度,代碼片段如下:
復(fù)制代碼 代碼如下: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();
}
}
// ...
}

基本粒子系統(tǒng)完成
以下的例子里,每幀會(huì)發(fā)射一個(gè)粒子,其位置在畫布中間(200,200),發(fā)射方向是360度,速率為100,生命為1秒,紅色、半徑為5象素。
復(fù)制代碼 代碼如下:
var ps = new ParticleSystem();
var dt = 0.01;
function sampleDirection() {
var theta = Math.random() * 2 * Math.PI;
return new Vector2(Math.cos(theta), Math.sin(theta));
}
function step() {
ps.emit(new Particle(new Vector2(200, 200), sampleDirection().multiply(100), 1, Color.red, 5));
ps.simulate(dt);
clearCanvas();
ps.render(ctx);
}
start("basicParticleSystemCanvas", step);

<button onclick="eval(document.getElementById('basicParticleSystemCode').value)" type="button">Run</button>
<button onclick="stop();" type="button">Stop</button>
<table border="0" style="width: 100%;">
<tbody>
<tr>
<td><canvas id="basicParticleSystemCanvas" width="400" height="400"></canvas></td>
<td width="10"> </td>
<td width="100%" valign="top">
<h4>修改代碼試試看</h4>
<li>改變發(fā)射位置</li>
<li>向上發(fā)射,發(fā)射范圍在90度內(nèi)</li>
<li>改變生命</li>
<li>改變半徑</li>
<li>每幀發(fā)射5個(gè)粒子</li>

</td>
</tr>
</tbody>
</table>

簡單碰撞
為了說明用數(shù)值積分相對于分析解的優(yōu)點(diǎn),本文在粒子系統(tǒng)上加簡單的碰撞。我們想加入一個(gè)需求,當(dāng)粒子碰到長方形室(可設(shè)為整個(gè)Canvas大小)的內(nèi)壁,就會(huì)碰撞反彈,碰撞是完全彈性的(perfectly elastic collision)。
在程序設(shè)計(jì)上,我把這功能用回調(diào)方式進(jìn)行。 ParticleSystem類有一個(gè)effectors數(shù)組,在進(jìn)行運(yùn)動(dòng)學(xué)模擬之前,先執(zhí)行每個(gè)effectors對象的apply()函數(shù):
而長方形室就這樣實(shí)現(xiàn):
復(fù)制代碼 代碼如下:// 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;
};
}

這其實(shí)就是當(dāng)偵測到粒子超出內(nèi)壁的范圍,就反轉(zhuǎn)該方向的速度分量。
此外,這例子的主循環(huán)不再每次把整個(gè)Canvas清空,而是每幀畫一個(gè)半透明的黑色長方形,就可以模擬動(dòng)態(tài)模糊(motion blur)的效果。粒子的顏色也是隨機(jī)從兩個(gè)顏色中取樣。
復(fù)制代碼 代碼如下:
var ps = new ParticleSystem();
ps.effectors.push(new ChamberBox(0, 0, 400, 400)); // 最重要是多了這語句
var dt = 0.01;
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 step() {
ps.emit(new Particle(new Vector2(200, 200), sampleDirection(Math.PI * 1.75, Math.PI * 2).multiply(250), 3, sampleColor(Color.blue, Color.purple), 5));
ps.simulate(dt);
ctx.fillStyle="rgba(0, 0, 0, 0.1)";
ctx.fillRect(0,0,canvas.width,canvas.height);
ps.render(ctx);
}
start("collisionChamberCanvas", step);

<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>

互動(dòng)發(fā)射
最后一個(gè)例子加入互動(dòng)功能,在鼠標(biāo)位置發(fā)射粒子,粒子方向是按鼠標(biāo)移動(dòng)速度再加上一點(diǎn)噪音(noise)。粒子的大小和生命都加入了隨機(jī)性。
復(fù)制代碼 代碼如下: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>

總結(jié)
本文介紹了最簡單的運(yùn)動(dòng)學(xué)模擬,使用歐拉方法作數(shù)值積分,并以此法去實(shí)現(xiàn)一個(gè)有簡單碰撞的粒子系統(tǒng)。本文的精華其實(shí)只有兩條簡單公式(只有兩個(gè)加數(shù)和兩個(gè)乘數(shù)),希望讓讀者明白,其實(shí)物理模擬可以很簡單。雖然本文的例子是在二維空間,但這例子能擴(kuò)展至三維空間,只須把Vector2換成Vector3。本文完整源代碼可下載。
續(xù)篇會(huì)談及在此基礎(chǔ)上加入其他物理現(xiàn)象,有機(jī)會(huì)再加入其他物理模擬課題。希望各位支持,并給本人更多意見。

JavaScript技術(shù)用JavaScript玩轉(zhuǎn)游戲物理(一)運(yùn)動(dòng)學(xué)模擬與粒子系統(tǒng),轉(zhuǎn)載需保留來源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 国产精品视频一区二区三区 | 亚洲欧美成人影院 | 久久久久精 | 青青久视频 | 日韩成人性视频 | 91久久综合亚洲鲁鲁五月天 | 羞羞的视频免费看 | 色播99| 日韩在线欧美 | 久久久久国色av免费观看性色 | 久久综合一区 | 一本久久a久久精品亚洲 | 精品伦精品一区二区三区视频 | 精品国产久 | 欧美一级艳情片免费观看 | 国产精品日韩一区 | 粉嫩高清一区二区三区 | 欧美一区二区三 | 激情五月婷婷综合 | 免费国产网站 | 69xxx免费| 国产福利视频 | 九一精品| 国产精品亚洲一区二区三区在线 | 一区二区三区在线免费观看 | 精品一区二区三区视频在线观看 | 在线观看亚洲精品视频 | 日韩一区二区黄色片 | 成人欧美一区二区 | 色综合一区二区三区 | 国产夜恋视频在线观看 | 天天干,夜夜操 | 黄色网络在线观看 | 国产原创在线观看 | 亚洲三级在线 | 日韩在线不卡 | av一级久久| 午夜在线免费观看视频 | 国产91色在线 | 亚洲 | 婷婷色婷婷 | 久久av资源网 |