canvasで少しずつ作るブロック崩し(4/5)
前回は右クリックでボールを出現、左クリックでバーを上下させボールを打ち返す機能を加えました。今回はいよいよブロックを作ってボールが当たると消える機能をつけてみます。
完成品
左クリック: バーを下げる
右クリック: ボール出現
ソースコード
<html> <head> <meta charset="UTF-8"> <script type="text/javascript"> (function() { var canvas; var ctx; var mouseX; var mouseY; // バーの情報 var BAR = { 'HEIGHT' : 10, 'WIDTH' : 50, 'UNDER' : 25, 'X' : 0, 'Y' : 0, 'PUSH' : 10, 'Vx' : 0, 'Vx0' : 0, 'Vy' : 0, 'Vy0' : 150, 'M' : 5, 'E' : 0.7, 'E0' : 10, }; // フィールドの情報 var FIELD = { 'HEIGHT' : 0, 'WIDTH' : 0, 'GRAVITY' : 588, // 重力加速度(0.9 * FPS) 'FPS' : 60, // frame per second 'E' : 0.7, 'E0' : 10, }; // ボールの情報 var BALL = []; var Ball = function() { this.Alive = 0; this.X = 0; this.Y = 0; this.Vy = 0; this.Vx = 100; this.Vx0 = 0; this.Vy0 = 200; this.RADIUS = 5; this.M = 1; this.HUE = 0.5; }; // ブロックの情報 var BLOCK = []; var Block = function() { this.Alive = 0; this.WIDTH = 30; this.HEIGHT = 10; this.X = 0; this.Y = 0; }; // 初期化処理 function initialize() { canvas = document.getElementById('canvas'); if(!canvas && !canvas.getContext) { return false; } // キャンバス作成 ctx = canvas.getContext('2d'); FIELD.WIDTH = ctx.canvas.width ; FIELD.HEIGHT = ctx.canvas.height; // バーの設定 mouseX = FIELD.WIDTH/2; // バーの初期位置は中心 BAR.X = mouseX; BAR.Y = FIELD.HEIGHT-BAR.UNDER; // ブロックの生成 createBlocks(); // 各種イベント設定 canvas.addEventListener('mousemove', getMouseCoordinate, false); canvas.addEventListener('mousedown', pushBar, false); canvas.addEventListener('mouseup', popBar, false); canvas.addEventListener('contextmenu', putBall, false); setInterval(drawField, 1000/FIELD.FPS); }; // マウス座標の更新 function getMouseCoordinate(e) { var rect = e.target.getBoundingClientRect(); mouseX = Math.floor(e.clientX - rect.left); mouseY = Math.floor(e.clientY - rect.top); }; // ボールの生成 function putBall(e) { e.preventDefault(); // ボールの初期位置は中心 var tail = BALL.length; BALL[tail] = new Ball(); BALL[tail].Alive = 1; BALL[tail].X = BAR.X; BALL[tail].Y = FIELD.HEIGHT-BAR.UNDER; BALL[tail].Vy = BALL[tail].Vy0 * (-1); }; // バーの収縮 function pushBar(e) { if (!e.pageX) { e = event.touches[0]; } if (e.button == 0) { setTimeout(pushBarEvent, 1000/FIELD.FPS); } }; // バーの反発 function popBar(e) { if (!e.pageX) { e = event.touches[0]; } if (e.button == 0) { setTimeout(popBarEvent, 1000/FIELD.FPS); } }; // バーの収縮処理 function pushBarEvent() { if (BAR.Y < FIELD.HEIGHT - BAR.UNDER + BAR.PUSH) { BAR.Vy = BAR.Vy0; BAR.Y += BAR.Vy * (FIELD.FPS/1000); setTimeout(pushBarEvent, 1000/FIELD.FPS); } else { BAR.Vy = 0; BAR.Y = FIELD.HEIGHT - BAR.UNDER + BAR.PUSH; } }; var BarTimer; // バーの反発処理 function popBarEvent() { if (BAR.Y > FIELD.HEIGHT-BAR.UNDER) { BAR.Vy = BAR.Vy0 * (-1); BAR.Y += BAR.Vy * (FIELD.FPS/1000); setTimeout(popBarEvent, 1000/FIELD.FPS); } else { BAR.Y = FIELD.HEIGHT-BAR.UNDER; setTimeout(resetBarSpeed, 100); } }; function resetBarSpeed() { BAR.Vy = 0; }; // ブロックの生成 function createBlocks() { var bxmax = 13; var bymax = 6; var btop = 20; var bleft = 20; var bidx = 0; var bint = 5; for (var x = 0; x < bxmax; x++) { for (var y = 0; y < bymax; y++) { BLOCK[bidx] = new Block(); BLOCK[bidx].X = x * BLOCK[bidx].WIDTH + btop + (x * bint); BLOCK[bidx].Y = y * BLOCK[bidx].HEIGHT + bleft + (y * bint); BLOCK[bidx].Alive = 1; bidx++; } } }; // 画面の描画 function drawField() { calcBallP(); drawBack(); drawBall(); drawBar(); drawBlock(); }; // ボール位置計算 function calcBallP() { for (var i = 0; i < BALL.length; i++) { if (BALL[i] == null) { continue; } // 生存しているボールのみ計算 if (BALL[i].Alive == 1) { // 床・天井接触 if (BALL[i].Y <= 0 || FIELD.HEIGHT <= BALL[i].Y) { if (BALL[i].Y <= 0) { // 天井 BALL[i].Y = FIELD.E0; BALL[i].Vy = BALL[i].Vy * FIELD.E * (-1) } else { // 床 // 床に接触したボールは死亡 BALL[i].Alive = 0; BALL[i].Vx = 0; BALL[i].Vy = 0; } } // 壁接触 if (BALL[i].X <= 0 || FIELD.WIDTH <= BALL[i].X) { if (BALL[i].X <= 0) { BALL[i].X = FIELD.E0; } else { BALL[i].X = FIELD.WIDTH - FIELD.E0; } BALL[i].Vx = BALL[i].Vx * (-1); } // バー接触 if (BAR.X <= BALL[i].X && BALL[i].X <= BAR.X + BAR.WIDTH) { if (Math.abs(BALL[i].Y - BAR.Y) <= BAR.HEIGHT) { // バーとボールの境界でバタつきを防ぐための処置 BALL[i].Y = BAR.Y - BAR.E0; // バー接触後の速度計算 BALL[i].Vy = (BALL[i].M * BALL[i].Vy - BAR.M * BAR.Vy) / BALL[i].M; // Vyが初速度より減速した場合は、初速度に戻す BALL[i].Vy = Math.abs(BALL[i].Vy) > BALL[i].Vy0 ? (BALL[i].Vy * BAR.E * (-1)) : (BALL[i].Vy0 * (-1)); } } // ブロック接触 for (var bi = 0; bi < BLOCK.length; bi++) { var xtouch = 0; var ytouch = 0; if (BLOCK[bi] == null) { continue; } if (BLOCK[bi].Alive == 0) { continue; } if ((Math.abs(BALL[i].X - BLOCK[bi].X) < BALL[i].RADIUS/2 + BLOCK[bi].WIDTH/2) && (Math.abs(BALL[i].Y - BLOCK[bi].Y) < BALL[i].RADIUS/2 + BLOCK[bi].HEIGHT/2)) { BLOCK[bi].Alive = 0; BALL[i].Vy *= (-1); } } // 縦計算 BALL[i].Vy += FIELD.GRAVITY * (1/FIELD.FPS); BALL[i].Y += BALL[i].Vy * (1/FIELD.FPS); // 横計算 BALL[i].X += BALL[i].Vx * (1/FIELD.FPS); } } deleteAllDeadBall(); }; // 死亡したボールを削除 function deleteAllDeadBall() { var isDeadBall = 1; while(isDeadBall != 0) { isDeadBall = 0; for (var i = 0; i < BALL.length; i++) { if (BALL[i] == null) { continue; } if(BALL[i].Alive == 0) { delete BALL[i]; BALL.splice(i,1); isDeadBall = 1; break; } } } } function drawBack() { ctx.fillStyle = 'rgb(0, 0, 0)'; ctx.fillRect(0, 0, FIELD.WIDTH, FIELD.HEIGHT); }; // ボールの描画 function drawBall() { ctx.save(); // 生存しているボールの数だけ描画 for(var i = 0; i < BALL.length; i++) { if (BALL[i] == null) { continue; } if (BALL[i].Alive) { // 円の描画設定 ctx.beginPath(); ctx.arc(BALL[i].X, BALL[i].Y, BALL[i].RADIUS, 0, 2*Math.PI, true); ctx.closePath(); // 色設定 BALL[i].HUE += 0.5; ctx.strokeStyle = 'hsl(' + BALL[i].HUE + ', 50%, 50%)'; ctx.fillStyle = 'hsl(' + BALL[i].HUE + ', 50%, 50%)'; ctx.shadowColor = 'hsl(' + BALL[i].HUE + ', 50%, 50%)'; } // 描画実行 ctx.stroke(); ctx.fill(); } ctx.restore(); }; // バーの描画 function drawBar() { var delay = 1; BAR.X = (mouseX + delay * BAR.X) / (delay+1); // 色設定 ctx.fillStyle = 'rgb(255,255,255)'; // バーの描画設定 ctx.fillRect(BAR.X, BAR.Y, BAR.WIDTH, BAR.HEIGHT); }; // ブロックの描画 function drawBlock() { // 色設定 ctx.fillStyle = 'rgb(0,255,0)'; for (var i = 0; i < BLOCK.length; i++) { if (BLOCK[i] == null) { continue; } if (BLOCK[i].Alive) { ctx.fillRect(BLOCK[i].X, BLOCK[i].Y, BLOCK[i].WIDTH, BLOCK[i].HEIGHT); } } } // 初期化イベント window.addEventListener('load', initialize, false); } ) (); </script> </head> <body> <canvas id='canvas' width=500 height=300></canvas> <p> 左クリック: バーを下げる <br /> 右クリック: ボール出現 </p> </body> </html>
ボールとブロックの当たり判定がイマイチなので変な動きが多いです。
他にも色々ツッコミどころはありますが、改善点含めて次回でまとめて完成としたいです。
関連ページ
canvasで少しずつ作るブロック崩し(1/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(2/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(3/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(5/5) - Segmentation Fault