JavaScript ワイはあの娘とつながっているんや!をつくる(1)の続きです。今回はゲームとして動作するようにします。
Contents
ページが読み込まれたときの処理
ページが読み込まれたときの処理を示します。
Playerオブジェクトを生成し、canvasのサイズを設定します。また描画用の画像ファイルを読み込みます。そのあとイベントリスナを追加し、ボリュームを設定できるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
window.onload = () => { player = new Player(); $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; imagePlayerMain.src = './images/main.png'; imagePlayerSub.src = './images/sub.png'; imageEnemy.src = './images/enemy.png'; imageItem1.src = './images/item1.png'; imageItem2.src = './images/item2.png'; addEventListeners(); // 後述 initVolumeController(0.5); } |
ボリューム調整可能にする
ボリュームを調整するための処理は以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function initVolumeController(initValue){ const $elemVolume = document.getElementById("volume"); const $elemRange = document.getElementById("vol_range"); $elemVolume.addEventListener("change", function(){ const value = $elemVolume.value; setVolume(value); }, false); setVolume(initValue); function setVolume(value){ $elemRange.textContent = value; soundGameover.volume = value; soundByebye.volume = value; soundHihihi.volume = value; soundMiss.volume = value; } } function playSound(){ soundGameover.currentTime = 0; soundGameover.play(); } |
イベントリスナの追加
スタートボタンをクリックすることでゲームを開始したり、ボタンをクリックまたはPCでキーを操作することでプレイヤーを操作できるようにするためにイベントリスナを追加します。
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 40 41 42 43 44 45 46 47 48 49 50 |
function addEventListeners(){ // ゲームスタート $start.addEventListener('click', () => gameStart()) // ボタンのクリック const arr1 = ['mousedown', 'mouseup', 'touchstart', 'touchend']; const arr2 = [true, false, true, false]; for(let i = 0; i < 4; i++){ $up.addEventListener(arr1[i], (ev) => onPressButton(ev, 'up', arr2[i])); $down.addEventListener(arr1[i], (ev) => onPressButton(ev, 'down', arr2[i])); $left.addEventListener(arr1[i], (ev) => onPressButton(ev, 'left', arr2[i])); $right.addEventListener(arr1[i], (ev) => onPressButton(ev, 'right', arr2[i])); } window.addEventListener('mouseup', () => { player.MoveUp = false; player.MoveDown = false; player.MoveLeft = false; player.MoveRight = false; }); // PCのキー操作 const arr3 = ['keydown', 'keyup']; const arr4 = [true, false]; for(let i = 0; i < 2; i++){ document.addEventListener(arr3[i], (ev) => { if(ev.code == 'ArrowLeft') onPressButton(ev, 'left', arr4[i]); if(ev.code == 'ArrowUp') onPressButton(ev, 'up', arr4[i]); if(ev.code == 'ArrowRight') onPressButton(ev, 'right', arr4[i]); if(ev.code == 'ArrowDown') onPressButton(ev, 'down', arr4[i]); }); } } function onPressButton(ev, button, isPress){ ev.preventDefault(); if(button == 'up') player.MoveUp = isPress; if(button == 'down') player.MoveDown = isPress; if(button == 'left') player.MoveLeft = isPress; if(button == 'right') player.MoveRight = isPress; } |
ゲームをスタートする処理
スタートボタンを非表示にしてプレイヤーを操作するためのボタンを表示させます。Playerオブジェクトを初期化して、スコアをリセットし、敵とアイテムが格納されている配列をクリアします。そして最初の敵を作り、更新処理を開始させます。
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 |
function gameStart(){ // スタートボタンを非表示 // プレイヤーを操作するためのボタンを表示 $up.style.display = 'block'; $down.style.display = 'block'; $left.style.display = 'block'; $right.style.display = 'block'; $start.style.display = 'none'; // 各キャラクタを移動させる stopUpdate = false; score = 0; player.Init(); enemies = []; items1 = []; items2 = []; // 最初の敵をつくる enemies.push(new Enemy(Math.random() * CANVAS_WIDTH, 0, Math.random() * 4 - 2, Math.random() * 2 + 3)); // setInterval関数が実行されていないのであれば実行する // すでに実行されているのであれば何もしない if(setIntervalId == null){ setIntervalId = setInterval(() => { update(); }, 1000 / 30); } } |
描画更新の処理
描画処理はつねにおこないますが、移動の処理はstopUpdate == false のときだけにします。こうすることでゲームオーバー時は動きが止まって見えます。またゲームオーバー時はゲームオーバーの文字列も描画します。
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 |
function update(){ clearCanvas(); // stopUpdate == true のときだけ移動処理をおこなう if(!stopUpdate){ player.Update(); enemies.forEach(enemy => enemy.Update()); items1.forEach(item => item.Update()); items2.forEach(item => item.Update()); createCharcter(); // 後述 checkCrush(); // 後述 } // 描画処理はつねにおこなう player.Draw(); enemies.forEach(enemy => enemy.Draw()); items1.forEach(item => item.Draw()); items2.forEach(item => item.Draw()); // スコアの描画 drawScore(); // ゲームオーバー時はゲームオーバーの表示 if(stopUpdate) drawGameOver(); } |
clearCanvas関数はcanvas全体を黒で塗りつぶします。
1 2 3 4 |
function clearCanvas(){ ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); } |
敵、アイテムの生成
createCharcter関数は敵やアイテムをランダムに生成します。単に乱数で処理をすると敵がいない状態が長く場合があるので、敵がひとつもいないときは常に生成します。アイテムはフィールドにひとつも存在しない場合のみ生成します。
1 2 3 4 5 6 7 8 9 10 |
function createCharcter(){ if(enemies.length == 0 || (enemies.length < 12 && Math.random() < 0.02)) enemies.push(new Enemy(Math.random() * CANVAS_WIDTH, 0, Math.random() * 4 - 2, Math.random() * 2 + 3)); if(items1.length == 0 && Math.random() < 0.01) items1.push(new Item1(Math.random() * CANVAS_WIDTH, 0, Math.random() * 4 - 2, Math.random() * 2 + 3)); if(items2.length == 0 && Math.random() < 0.01) items2.push(new Item2(Math.random() * CANVAS_WIDTH, 0, Math.random() * 4 - 2, Math.random() * 2 + 3)); } |
当たり判定
敵と衝突した場合は糸の耐久度を1下げます。アイテム1をゲットした場合は糸の耐久度を1上げます。ただし最大値を超えて上がることはありません。アイテム2をゲットした場合は糸を短くします。アイテムをゲットした場合は10点を加点します。いずれの場合も効果音を鳴らします。
そのあと死亡フラグが立っているオブジェクトやフィールドの外に出たオブジェクトを配列から除去します。
当たり判定の処理がおわって糸の耐久度が0以下になっている場合はゲームオーバーの処理をおこないます。
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
function checkCrush(){ if(player.Life <= 0) return; // 敵との当たり判定 const positions = player.GetHitJugePositions(); for(let i = 0; i < positions.length; i++){ const hited = enemies.filter(enemy => !enemy.IsDead && Math.pow(enemy.X - positions[i].x, 2) + Math.pow(enemy.Y - positions[i].y, 2) < Math.pow(CHARCTER_SIZE, 2)); if(hited.length > 0){ hited.forEach(enemy => enemy.IsDead = true); player.Life--; if(player.Life > 0){ // 効果音(player.Life == 0になったときは別の効果音を鳴らす) soundMiss.currentTime = 0; soundMiss.play(); } } } // アイテム1との当たり判定 for(let i = 0; i < positions.length; i++){ const hited = items1.filter(item => !item.IsDead && Math.pow(item.X - positions[i].x, 2) + Math.pow(item.Y - positions[i].y, 2) < Math.pow(CHARCTER_SIZE, 2)); if(hited.length > 0){ hited.forEach(item => item.IsDead = true); if(player.Life + 1 <= LIFE_MAX) player.Life++; score += 10 * hited.length; // 効果音 soundHihihi.currentTime = 0; soundHihihi.play(); } } // アイテム2との当たり判定 for(let i = 0; i < positions.length; i++){ const hited = items2.filter(item => !item.IsDead && Math.pow(item.X - positions[i].x, 2) + Math.pow(item.Y - positions[i].y, 2) < Math.pow(CHARCTER_SIZE, 2)); if(hited.length > 0){ hited.forEach(item => item.IsDead = true); if(player.Distance - 10 >= DISTANCE_MIN) player.Distance -= 10; else player.Distance = DISTANCE_MIN; score += 10 * hited.length; // 効果音 soundHihihi.currentTime = 0; soundHihihi.play(); } } // 死亡フラグが立っているオブジェクトやフィールドの外に出たオブジェクトを配列から除去 enemies = enemies.filter(enemy => !enemy.IsDead && !(enemy.Y >= CANVAS_HEIGHT || enemy.X <= 0 || enemy.X >= CANVAS_WIDTH)); items1 = items1.filter(item => !item.IsDead && !(item.Y >= CANVAS_HEIGHT || item.X <= 0 || item.X >= CANVAS_WIDTH)); items2 = items2.filter(item => !item.IsDead && !(item.Y >= CANVAS_HEIGHT || item.X <= 0 || item.X >= CANVAS_WIDTH)); if(player.Life <= 0){ player.Life = 0; gameOver(); } } |
drawScore関数はスコアを、drawGameOver関数はゲームオーバーのときにゲームオーバーの文字列を描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function drawScore(){ ctx.font = '24px Arial'; ctx.textBaseline = 'top'; ctx.fillStyle = '#fff'; ctx.fillText('SCORE ' + score, 20, 10); } function drawGameOver(){ ctx.font = '24px Arial'; ctx.textBaseline = 'top'; ctx.fillStyle = '#fff'; ctx.fillText('今日もめでたくふられました', 25, 170); ctx.fillText('GAME OVER', 110, 230); } |
ゲームオーバーの処理
ゲームオーバーになったときは、プレイヤー操作用のボタンを非表示にします。そして糸が切れて「あの娘」がどこかへ飛んでいく演出をします。そのあと移動処理を止めるフラグをセットします。しばらく待機したあとゲームオーバーの効果音と再挑戦用のボタンを表示する処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function gameOver(){ // プレイヤー操作用のボタンを非表示にする $up.style.display = 'none'; $down.style.display = 'none'; $left.style.display = 'none'; $right.style.display = 'none'; // 女の子「バイバーイ」 soundByebye.currentTime = 0; soundByebye.play(); setTimeout(() => { // キャラクタの移動を停止する(「あの娘」が飛んでいく演出があるのでこのタイミングで止める) stopUpdate = true; soundGameover.currentTime = 0; soundGameover.play(); // 再挑戦用のボタンを表示する $start.style.display = 'block'; }, 1500); } |