0.前言
在MDN上面有一個彈球的例子,我們的小球會在螢幕上彈跳,當它們碰到彼此時會變色。
1.物件導向程式設計的實踐
官網講得太長,而且有一些漏洞,我改進一下
let canvas = document.querySelector('canvas');
let ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let balls = [];
let ran = (min,max) =>parseInt((max-min)*Math.random())+min;//生成隨機數
let Ball = function(vx,vy,x,y,r,color){//Ball的類
this.vx = vx;
this.vy = vy;
this.x = x||1;//防止速度為0
this.y = y||1;
this.r = r;
this.color = color;
}
Ball.prototype.draw = function(){//繪製的方法
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x,this.y,this.r,0,2*Math.PI);
ctx.fill();
}
Ball.prototype.update = function(){//更新的方法
if((this.x + this.r) >= width) {
this.x = width - this.r - 5;//防止半身進入邊緣,無限迴圈,黏住邊緣
this.vx = -(this.vx);//反彈
}
if((this.x - this.r) <= 0) {
this.x = this.r + 5;
this.vx = -(this.vx);
}
if((this.y + this.r) >= height) {
this.y = height - this.r - 5;
this.vy = -(this.vy);
}
if((this.y - this.r) <= 0) {
this.y = this.r + 5;
this.vy = -(this.vy);
}
this.x += this.vx;//小球前進
this.y += this.vy;
}
Ball.prototype.isCollision = function() {//是否碰撞
for(var j = 0; j < balls.length; j++) {
if(!(this === balls[j])) {//保證不自己和自己碰撞,因為自己也在陣列裡面,現在是遍歷陣列
var dx = this.x - balls[j].x;
var dy = this.y - balls[j].y;
var dvx = this.vx - balls[j].vx;
var dvy = this.vy - balls[j].vy;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= this.r + balls[j].r) {
balls[j].x -= 7*balls[j].vx;//防止相互糾纏
balls[j].y -= 7*balls[j].vy;
this.x -= 7*this.vx;
this.y -= 7* this.vy;
this.vx = -this.vx;
this.vy = -this.vy;
this.color = "#"+(~~(Math.random()*(1<<24))).toString(16);
}
}
}
};
let loop = function(){
ctx.fillStyle = 'rgba(0,0,0,.1)';//等於黑板擦,擦除前面動畫留下的痕跡
ctx.fillRect(0,0,width,height);
while(balls.length<40){//生成40個球
let ball = new Ball(ran(-7,7),ran(-7,7),ran(0,width),ran(0,height),
ran(10,20),"#"+(~~(Math.random()*(1<<24))).toString(16));
balls.push(ball);
}
for(let i = 0;i<balls.length;i++){//每一個球呼叫函式,保證動畫進行
balls[i].draw();
balls[i].update();
balls[i].isCollision();
}
requestAnimationFrame(loop);
}
loop();
複製程式碼
2.相互糾纏的現象
在面對碰撞檢測後還有後續動作的情況,必須考慮一下相互糾纏的問題: 如果兩個小球被檢測到碰撞的時候,而且加上他們的速度下一步還是處於碰撞範圍內,就像引力一樣無法脫離,無限原地碰撞。這時候,需要其他小球碰撞來解散這種糾纏。有時候,可能3個小球都會一起進入無限糾纏的狀態。(判斷碰撞-是-速度反方向-遠離-判斷碰撞-速度反方向-靠近-判斷碰撞-是-速度反方向-遠離……無限迴圈)
3.解決方案
對於邊界,防止黏住邊界,我們可以重置它的位置,讓他剛剛好離開邊界,比如右邊界
this.x = width - this.r - 5//-5保證它絕對離開,-1有時候也會黏住,但1和5距離差別還是不大的
其他邊界同理
對於兩個小球,我們也是重置位置,這個重置的演算法那個常數就看實際情況了。
this.x -= 7*this.vx; //我這裡,實踐證明大於6才比較低概率發生糾纏
//而且6幀也剛剛好是遊戲中的爆炸,那個瞬間有6幀,這樣我們才感覺到存在這個瞬間
//我直接讓他回退6幀,當然球的大小更大的,這個數字也更大
this.y -= 7* this.vy;
複製程式碼
解決方案2: 可以給Ball建構函式再初始化一個值:this.isleave = true; 對於Ball.prototype.isCollision函式,我們改動一下,等到碰撞的時候,this.isleave變成false
if(!this.isleave){
if(distance> this.r + balls[j].r){
this.isleave =true;//遠離後
}else{
//do something
return;
}
}else if(distance <= this.r + balls[j].r){
this.isleave = false;
//前面的程式碼
}
複製程式碼
4.模擬核裂變
碰撞的時候,旁邊生成一個新的小球。 因為鏈式反應,可能會一瞬間就把瀏覽器炸了,所以我們限制小球數量
//Ball.prototype.isCollision一部分更改
if (distance <= this.r + balls[j].r) {
balls[j].x -= 7*balls[j].vx;
balls[j].y -= 7*balls[j].vy;
this.x -= 7*this.vx;
this.y -= 7* this.vy;
this.vx = -this.vx;
this.vy = -this.vy;
if(balls.length<30){//裂變到30個就停止
let ball = new Ball(ran(-7,7),ran(-7,7),this.x-17*this.vx,this.y-17* this.vy,
this.r,
"#"+(~~(Math.random()*(1<<24))).toString(16));
balls.push(ball);
}
}
//loop一部分更改
let loop = function(){
ctx.fillStyle = 'rgba(0,0,0,.2)';
ctx.fillRect(0,0,width,height);
while(balls.length<2){//初始兩個球
let ball = new Ball(ran(-7,7),ran(-7,7),ran(0,width),ran(0,height),
ran(10,20),"#"+(~~(Math.random()*(1<<24))).toString(16));
balls.push(ball);
}
for(let i = 0;i<balls.length;i++){
balls[i].draw();
balls[i].update();
balls[i].isCollision();
}
requestAnimationFrame(loop);
}
複製程式碼
5.大魚吃小魚
MDN上面說再生成一個eval(這裡指的是這個會吃掉小球的敵人),是吃掉小球的。我這裡把這個eval也設定成和小球是同一個類的,但是他的isCollision方法就有點不同,會把小球吃掉。為了保證無限迴圈,當小球被吃剩5個,eval就會爆炸,又生成原本那麼多小球,繼續迴圈。
//對這個eval進行定義
Eval.prototype.draw = function(){
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x,this.y,this.r,0,2*Math.PI);
ctx.fill();
}
Eval.prototype.update = Ball.prototype.update;
Eval.prototype.isCollision = function(){
for(var j = 0; j < balls.length; j++) {
var dx = this.x - balls[j].x;
var dy = this.y - balls[j].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= this.r + balls[j].r) {
balls.splice(j,1)
this.vx = -this.vx;
this.vy = -this.vy;
this.r += 1;
}
}
}
let e = new Eval(10,10,ran(0,width),ran(0,height),20,'#fff');
//初始30個球
while(balls.length<30){
let ball = new Ball(ran(-7,7),ran(-7,7),ran(0,width),ran(0,height),
ran(10,20),"#"+(~~(Math.random()*(1<<24))).toString(16));
balls.push(ball);
}
//loop的改動
let loop = function(){
ctx.fillStyle = 'rgba(0,0,0,.2)';
ctx.fillRect(0,0,width,height);
if(balls.length<5){//少於5個,eval又是一個新的eval
e = new Eval(10,10,e.x,e.y,20,'#fff');
while(balls.length<30){//迴圈生成30個球
let ball = new Ball(ran(-7,7),ran(-7,7),ran(e.x,e.x),ran(e.x,e.x),
ran(10,20),"#"+(~~(Math.random()*(1<<24))).toString(16));
balls.push(ball);
}
}else{
e.draw();
e.update();
e.isCollision();
}
for(let i = 0;i<balls.length;i++){
balls[i].draw();
balls[i].update();
balls[i].isCollision();
}
requestAnimationFrame(loop);
}
複製程式碼
更加壯觀,是不是?