JavaScriptで黒ひげ危機一発をつくります。
Contents
HTML部分
HTML部分を示します。
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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>黒ひげ危機一発</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel = "stylesheet" href = "./style.css" type = "text/css" media = "all"> </head> <body> <div id = "container"> <button id = "retry">スタート!</button> <div> <canvas id = "canvas"></canvas> </div> <div id = "boxes"></div> </div> <p>音量: <input type = "range" id = "volume" min = "0" max = "1" step = "0.01"> <span id = "vol_range"></span> <button onclick = "playSound()">音量テスト</button> </p> <script src= "./index.js"></script> </body> </html> |
style.css
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 |
body { background-color: black; color: #fff; } #container { width: 360px; background-color: black; padding-top: 0px; padding-bottom: 20px; color: #fff; position: relative; } .box { display: inline-block; margin-left: 20px; margin-top: 20px; } #nokori { font-size: 20px; font-weight: bold; text-align: center; margin-top: 20px; } #retry { width: 200px; height: 50px; position: absolute; top: 100px; left: 75px; } |
JavaScript部分
主な定数とグローバル変数を示します。
index.js
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 64 65 66 67 68 69 70 |
// canvasのサイズ const CANVAS_WIDTH = 360; const CANVAS_HEIGHT = 400; // 樽のイメージのサイズ const TARU_WIDTH = 140; const TARU_HEIGHT = 160;; // 黒ひげのイメージのサイズ const KUROHIGE_WIDTH = 100; const KUROHIGE_HEIGHT = 100; // 樽のイメージ const imageTaru = new Image(); imageTaru.src = './images/taru.png'; // 黒ひげのイメージ(飛び出す) const imageKurohige1 = new Image(); imageKurohige1.src = './images/kurohige1.png'; // 黒ひげのイメージ(落ちてくる) const imageKurohige2 = new Image(); imageKurohige2.src = './images/kurohige2.png'; // 刀を刺す部分 const BOX_WIDTH = 45; const BOX_HEIGHT = 40; // 刀を刺す部分は縦3列横5列 const BOX_ROW_MAX = 3; const BOX_COL_MAX = 5; // canvas要素 const $canvas = document.getElementById('canvas'); const ctx = $canvas.getContext('2d'); // この要素のなかに刀を刺すボックスを並べる const $boxes = document.getElementById('boxes'); // 再挑戦のボタン const $retry = document.getElementById('retry'); // 効果音 const soundGameover1 = new Audio('./sounds/gameover1.mp3'); const soundGameover2 = new Audio('./sounds/gameover2.mp3'); const soundOk = new Audio('./sounds/ok.mp3'); const soundClear = new Audio('./sounds/clear.mp3'); // これを選んだらアウト! let deadID = Math.floor(Math.random() * BOX_ROW_MAX * BOX_COL_MAX); // 刀を刺す部分の要素と未選択の個数 let boxes = []; let unselectedCount = BOX_ROW_MAX * BOX_COL_MAX; // 黒ひげを描画するX座標と初期Y座標 const KUROHIGE_X = (CANVAS_WIDTH - KUROHIGE_WIDTH) / 2; const INIT_KUROHIGE_Y = (CANVAS_HEIGHT - TARU_HEIGHT) - 50; // 黒ひげを描画する必要があるか? 移動中か? 飛び出してからどれだけ移動したか? let showKurohige = false; let isMovingKurohige = false; let kurohigeMoveLength = 0; // 黒ひげはどこまで上昇(min)し、どこまで下降(max)するか? const KUROHIGE_MIN_Y = -100; const KUROHIGE_MAX_Y = 280; // 現在プレイ中か? let playing = false; |
ページが読み込まれたときの処理
ページが読み込まれたときにおこなわれる処理を示します。
canvasを黒で塗りつぶし刀を刺す部分を要素として追加(後述)します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
window.onload = () => { initCanvas(); clearCanvas(); initBoxex(); // 後述 $retry.addEventListener('click', () => retry()); setInterval(() => { update(); // 後述 }, 30); initVolumeController(0.5); // 後述 } function initCanvas(){ $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; } function clearCanvas(){ ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); } |
イベントリスナの追加
刀を刺す部分を要素として追加する処理を示します。div要素を生成してその内部に画像を表示させています。そしてクリック時に対応するためにイベントリスナを追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function initBoxex(){ let boxID = 0; for(let row = 0; row < BOX_ROW_MAX; row++){ const $boxes1 = document.createElement('div'); for(let col = 0; col < BOX_COL_MAX; col++){ const image = new Image(); image.src = './images/unselected.png'; image.width = BOX_WIDTH; image.height = BOX_HEIGHT; const $box = document.createElement('div'); $box.className = 'box'; $box.id = 'box-' + boxID++; $box.addEventListener('click', (ev) => onclicked(ev, $box)) $box.appendChild(image); $boxes1.appendChild($box); boxes.push($box); } $boxes.appendChild($boxes1); } } |
追加するイベントリスナを示します。
プレイ中でない場合はなにもしません。またすでに選択されたボックスがクリックされた場合もなにもしません。それ以外の場合は要素内に表示される画像をチェック済みのものに入れ替えます。
そのあと選んではいけないもの(乱数で0~14がdeadIDに格納されている)を選ばなかった場合は効果音を鳴らして残りの個数を1減らします。それによって残り1になった場合はゲームクリアです。選んではいけないものを選んでしまった場合はonGameOvered関数(後述)が呼びだされてゲームオーバーの処理がおこなわれます。
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 onclicked(ev, elm){ // elm.children[0].srcのなかに'/images/selected.png'があれば選択済みである if(!playing || elm.children[0].src.indexOf('/images/selected.png') != -1) return; elm.children[0].remove(); const image = new Image(); image.src = './images/selected.png'; image.width = BOX_WIDTH; image.height = BOX_HEIGHT; elm.appendChild(image); if(elm.id != 'box-' + deadID){ soundOk.currentTime = 0; soundOk.play(); unselectedCount--; if(unselectedCount == 1) onGameCleared(); } else onGameOvered(); } |
ゲーム開始時の処理
ゲーム開始ボタンがクリックされたときの処理を示します。
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 |
function retry(){ playing = true; // 黒ひげは非表示に。黒ひげの移動量も0にリセットする showKurohige = false; kurohigeMoveLength = 0; // 選んだらアウトになる部分を乱数で決める // 未選択の個数を最大値にする deadID = Math.floor(Math.random() * BOX_ROW_MAX * BOX_COL_MAX); unselectedCount = BOX_ROW_MAX * BOX_COL_MAX; // 再挑戦用のボタンを非表示にする $retry.style.display = 'none'; // ボックスをすべて未選択の状態にする boxes.forEach(box => { box.children[0].remove(); const image = new Image(); image.src = './images/unselected.png'; image.width = BOX_WIDTH; image.height = BOX_HEIGHT; box.appendChild(image); }); } |
ゲームオーバー時とゲームクリア時の処理
ゲームオーバー時とゲームクリア時におこなわれる処理を示します。
黒ひげが樽から飛び出してくる演出をするため、黒ひげの描画と移動をするフラグをセットします。実際の描画は更新処理をするupdate関数(後述)内でおこないます。そのあとしばらく時間をおいてから再挑戦用のボタンを表示します。
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 |
function onGameOvered(){ playing = false; isMovingKurohige = true; // 黒ひげの移動処理を開始する showKurohige = true; // 黒ひげの描画処理を開始する // 効果音(爆発音を鳴らしたあといつも使っている定番の効果音)を鳴らす soundGameover1.currentTime = 0; soundGameover1.play(); setTimeout(() => { soundGameover2.currentTime = 0; soundGameover2.play(); }, 2000); setTimeout(() => { // 再挑戦用のボタンを表示する $retry.style.display = 'block'; }, 4000); } function onGameCleared(){ playing = false; soundClear.currentTime = 0; soundClear.play(); setTimeout(() => { // 再挑戦用のボタンを表示する $retry.style.display = 'block'; }, 2000); } |
描画の更新
描画を更新する処理を示します。ここではcanvas全体を黒で塗りつぶし、樽と残り個数、ゲームオーバー時には黒ひげの描画処理もおこなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function update(){ clearCanvas(); drawTaru(); drawKurohige(); // 後述 drawNokori(); } function drawTaru(){ const x = (CANVAS_WIDTH - TARU_WIDTH) / 2; const y = (CANVAS_HEIGHT - TARU_HEIGHT) - 50; ctx.drawImage(imageTaru, x, y, TARU_WIDTH, TARU_HEIGHT); } function drawNokori(){ let text = '残り:' + unselectedCount; if(unselectedCount == 1) text = 'ゲームクリア!!'; ctx.fillStyle = '#fff'; ctx.font = '24px Arial'; ctx.textBaseline = 'top'; ctx.fillText(text, 50, 10, TARU_WIDTH, TARU_HEIGHT); } |
黒ひげの移動と描画処理をしている部分を示します。
ゲームオーバーではないときは黒ひげの描画をする必要はないのでなにもしていません。描画するときはisMovingKurohigeフラグを参照して黒ひげを移動させる必要があるかを調べます。移動させる場合は最初は上へ移動させ、途中から下方向へ折り返す移動をさせます。黒ひげのY座標はその最小値とこれまでの移動量から求めています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function drawKurohige(){ if(!showKurohige) return; if(isMovingKurohige) kurohigeMoveLength += 16; let image = imageKurohige1; let kurohigeY = INIT_KUROHIGE_Y - kurohigeMoveLength; // 上へ飛びすぎた場合は上下逆にしたイメージに変更して折り返し移動をさせる if(kurohigeY < KUROHIGE_MIN_Y){ kurohigeY = KUROHIGE_MIN_Y + Math.abs(kurohigeY - KUROHIGE_MIN_Y); image = imageKurohige2; } // 下限(KUROHIGE_MAX_Y)まで落ちてきたら移動を止める if(kurohigeY > KUROHIGE_MAX_Y) isMovingKurohige = false; ctx.drawImage(image, KUROHIGE_X, kurohigeY, KUROHIGE_WIDTH, KUROHIGE_HEIGHT); } |
ボリューム調整関連
ボリューム調整関連の処理を示します。効果音の定数名が違うだけで他はいつもと完全に同じです。
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(){ setVolume($elemVolume.value); }, false); setVolume(initValue); function setVolume(value){ $elemVolume.value = value; $elemRange.textContent = value; soundOk.volume = value; soundGameover1.volume = value; soundGameover1.volume = value; } } function playSound(){ soundOk.currentTime = 0; soundOk.play(); } |