JavaScriptでつくる動く暇つぶしの続きです。今回はcanvas要素を使います。前回のものにはなかった効果音やゲームらしさも追加します。
Contents
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>JavaScriptでつくる動く暇つぶし</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <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"> <canvas id = "can"></canvas> <div id="buttons"> <p class = "white">どのくらい暇ですか?</p> <select id="select"> <option value="1">少しだけ暇</option> <option value="2">普通に暇</option> <option value="3">死ぬほど暇</option> </select> <button id="start" onclick="Start()">スタート</button> </div> </div> <script type='text/javascript' src='./app.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 |
#container { width: 320px; height: 480px; position: relative; } #buttons { position: absolute; top: 320px; left: 85px; } #select { width: 150px; } #start { width: 150px; height: 50px; margin-top: 10px; display: block; } .white { color: white; } |
JavaScript部分
前回同様にタイマーで更新処理をおこない、更新時に「暇」という文字を追加して移動させます。
グローバル変数
最初にグローバル変数を示します。
app.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 |
let $can = document.getElementById('can'); // canvas要素 let $buttons = document.getElementById('buttons'); // スタートボタンとゲームのコース選択 let $select = document.getElementById('select'); // コース選択用のセレクトボックス const WIDTH = 320; // canvasの幅と高さ const HEIGHT = 480; let maxX = WIDTH; // 「暇」が描画されるX座標が取り得る最大値 let maxY = HEIGHT; // 「暇」が描画されるY座標が取り得る最大値 let colors = ['#f00', '#0f0', '#00f','#ff0', '#f0f', '#0ff']; // 「暇」の色 let himaCountMax = 10; // 「暇」の総数 let hit = 0; // つぶした「暇」の数 let miss = 0; // クリックしたけど「暇」がつぶせなかった回数 let himas = []; // Himaオブジェクトを格納する配列 let himaCount = 0; // これまでに出現した「暇」の数 // 効果音 let hitSound = new Audio('./hit.mp3'); let missSound = new Audio('./miss.mp3'); let finishSound1 = new Audio('./finish1.mp3'); let finishSound2 = new Audio('./finish2.mp3'); |
初期化
canvasのサイズを設定します。そのあとコンテキストを取得し、全体を黒で塗りつぶします。
app.js
1 2 3 4 5 6 7 |
$can.width = WIDTH; $can.height = HEIGHT; let ctx = $can.getContext('2d'); ctx.fillStyle = '#000'; ctx.fillRect(0, 0, WIDTH, HEIGHT); // canvas全体を黒('#000')で塗りつぶす |
Himaクラスの定義
「暇」という文字を移動・描画するHimaクラスを定義します。
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 |
class Hima { constructor(startX, startY, endX, endY){ let colorIndex = Math.floor( Math.random() * colors.length); // 描画するときの色 let speed = 2 + Math.random() * 3; // 移動速度 this.X = startX; // どこに描画するか? this.Y = startY; this.Color = colors[colorIndex]; // 描画するときの色を設定する this.IsDead = false; // XYそれぞれの方向の移動する量 let angle = Math.atan2(endY - startY, endX - startX); this.VX = Math.cos(angle) * speed; this.VY = Math.sin(angle) * speed; // 描画する文字(いうまでもなく「暇」) this.text = '暇'; } Move(){ // 描画すべきXY座標を求める this.X += this.VX; this.Y += this.VY; // 画面の外に移動したら消滅させる if(this.X < 0 || this.X > maxX || this.Y < 0 || this.Y > maxY) this.IsDead = true; } Draw(){ // IsDeadフラグが立っている場合は描画しない if(this.IsDead) return; ctx.fillStyle = this.Color; // 設定した色で描画する ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = '32px MS ゴシック'; ctx.fillText(this.text, this.X, this.Y); } // 引数の座標は文字が描画されているエリアか? // エリア内であればIsDeadフラグをセットしてtrueを返す // そうでないならfalseを返す IsHit(x, y){ let metrics = ctx.measureText(this.text); let textWidth = metrics.width; let textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; if(Math.abs(this.X - x) < textWidth / 2 && Math.abs(this.Y - y) < textHeight / 2){ this.IsDead = true; return true; } return false; } } |
ゲームスタートの処理
スタートボタンがクリックされたらStart関数を呼び出して、「暇」をゲーム中に何回表示させるかを決定すると同時にゲームの初期設定をしたあと、更新処理を一定時間おきに繰り返します。
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 Start(){ // 「暇」をゲーム中に何回表示させるか? if($select.value == '1') himaCountMax = 10; if($select.value == '2') himaCountMax = 20; if($select.value == '3') himaCountMax = 50; // スタートボタンなどを非表示に $buttons.style.display = 'none'; // ゲームの初期設定(変数の初期化など) hit = 0; miss = 0; himas = []; updateCount = 0; himaCount = 0; // 更新処理(ゲームが終了するまで繰り返す) let timer = setInterval(() => { Update(); // ゲームが終了したら終了処理をする if(himaCount == himaCountMax && himas.length == 0) Finish(timer); }, 33); } |
更新処理
更新処理では新しく「暇」を生成するともに移動・描画の処理をおこないます。
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 |
let updateCount = 0; function Update(){ updateCount++; ctx.fillStyle = '#000'; ctx.fillRect(0, 0, WIDTH, HEIGHT); // 新しく「暇」を生成 if( himaCount < himaCountMax && (updateCount % 100 == 0 || updateCount % 100 == 25 || updateCount % 100 == 50 || updateCount % 100 == 75) ){ himaCount++; if(updateCount % 100 == 0) himas.push(new Hima(0, Math.random() * maxY,maxX, Math.random() * maxY)); if(updateCount % 100 == 25) himas.push(new Hima(maxX, Math.random() * maxY, 0, Math.random() * maxY)); if(updateCount % 100 == 50) himas.push(new Hima(Math.random() * maxX, 0, Math.random() * maxX, maxY)); if(updateCount % 100 == 75) himas.push(new Hima(Math.random() * maxX, maxY, Math.random() * maxX, 0)); } // 「暇」の移動処理 for(let i=0; i<himas.length; i++) himas[i].Move(); // 「暇」の描画処理 for(let i=0; i<himas.length; i++) himas[i].Draw(); // 配列のなかに消滅していない「暇」のみを格納する himas = himas.filter(_=> !_.IsDead); } |
終了処理
終了時の処理を示します。
タイマーを停止してドラムロールを鳴らします。そのあと結果を表示するとともにスタートボタンなどを再表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function Finish(timer){ // タイマー停止 clearInterval(timer); // ドラムロールを鳴らす(2秒間) finishSound1.currentTime = 0; finishSound1.play(); setTimeout(() => { // ドラムロールが鳴り終わったら結果を表示し、スタートボタンなどを再表示する finishSound1.pause(); finishSound2.currentTime = 0; finishSound2.play(); ShowResult(); $buttons.style.display = 'block'; }, 2000); } |
結果を表示する処理を示します。暇つぶしの成功率を表示します。
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 |
function ShowResult(){ ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#fff'; ctx.font = '24px MS ゴシック'; let himaText = 'ひま ' + himaCountMax + ' 個'; let metrics = ctx.measureText(himaText); let himaTextWidth = metrics.width; let hitText = 'HIT ' + hit; metrics = ctx.measureText(hitText); let hitTextWidth = metrics.width; let missText = 'MISS ' + miss; metrics = ctx.measureText(missText); let missTextWidth = metrics.width; ctx.fillStyle = '#ff0'; ctx.fillText(himaText, (WIDTH - himaTextWidth) / 2, 100); ctx.fillStyle = '#0f0'; ctx.fillText(hitText, (WIDTH - hitTextWidth) / 2, 140); ctx.fillStyle = '#f00'; ctx.fillText(missText, (WIDTH - missTextWidth) / 2, 180); ctx.font = '20px MS ゴシック'; let rate1 = Math.round(100 * hit / himaCountMax); let rateText1 = 'あなたの暇つぶし率は ' + rate1 + ' %'; metrics = ctx.measureText(rateText1); let rateTextWidth1 = metrics.width; let clickCount = hit + miss; if(clickCount == 0) clickCount= 1; let rate2 = Math.round(100 * hit / clickCount); let rateText2 = '命中率は ' + rate2 + ' % です'; metrics = ctx.measureText(rateText2); let rateTextWidth2 = metrics.width; ctx.fillStyle = '#0ff'; ctx.fillText(rateText1, (WIDTH - rateTextWidth1) / 2, 230); ctx.fillText(rateText2, (WIDTH - rateTextWidth2) / 2, 270); } |
クリック時の処理
canvas要素がクリックされたらどの部分がクリックされたかを調べて、暇つぶしが成功したかを調べます。そのあと成功数と失敗数をグローバル変数に格納します。
効果音は以下の処理で再生できます。
1 2 3 |
let hitSound = new Audio('./hit.mp3'); // 音声ファイルのurl(絶対url、相対url、どちらでも可) hitSound.currentTime = 0; // 最初から再生するなら0を設定 hitSound.play(); // 再生開始 |
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 |
$can.onclick = (ev) => { // canvas要素のどの部分がクリックされたか? let canvasRect = $can.getBoundingClientRect() ; let canvasX = canvasRect.left + window.pageXOffset ; let canvasY = canvasRect.top + window.pageYOffset ; let x = ev.pageX - canvasX; let y = ev.pageY - canvasY; // クリックによってつぶせた暇の数を数える let count = 0; for(let i=0; i<himas.length; i++){ if(himas[i].IsHit(x, y)) count++ } hit += count; // クリックによってつぶせた暇の数が~なら失敗 // 成功時と失敗時の効果音を鳴らす if(count == 0){ miss++; missSound.currentTime = 0; missSound.play(); } else { hitSound.currentTime = 0; hitSound.play(); } } |
初めましてこんにちは。記事のテーマと違うコメントですみません。
「画像ファイルの色を置換したい」 を使わせて頂いて、自分の目的と合いそうだなと、ありがたく思っております。
こちらのソフトですが、入力対象のフォルダを指定またはドラッグ&ドロップして、出力フォルダも指定し、入力のサブフォルダまで一括で置換、というようなバージョンアップは今後ありませんでしょうか・・・。もしあったら嬉しく存じます・・・。
勝手なお願い申し上げて失礼しました。
わかりました。対応します。
しばらくお待ちください。