前回はJavaScriptでルーレットを作りましたが、今回は円形のルーレットをつくります。
扇形に内接する正方形を求める
円形のルーレットをつくる場合、扇形に内接する正方形の座標を考えなければなりません。半径(R)と中心角(θ)が与えられている場合、扇形に内接する正方形の一辺の長さ(L)と頂点の座標を知るにはどうすればいいでしょうか?
上下対称なので上半分だけ考えます。三角形OBDを考えます。OBの長さはR、BDはL/2、あとはODの長さが分かれば三平方の定理より方程式を作って求めることができそうです。
CDの長さはLです。ではOCの長さはどうなるでしょうか? tan(θ/2)= AC / OCであり、AC = L / 2 なので、OC = L / 2 / tan(θ/2)です(上半分だけ考えているので中心角はθの半分)。
OB^2 = BD^2 + OD^2
R^2 = (L / 2)^2 + (L + L / 2 / tan(θ / 2)) ^ 2
あとはLの方程式を解くだけです。Lの値がわかればOCの長さもわかります。
L = R × √(1/A) * Aは 1 / 4 + {1 + 1 / 2 / tan(θ / 2)}^2
OCの長さ = L / 2 / tan(θ / 2)
ではコーディングしていきましょう。
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 |
<!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"> <canvas id = "canvas"></canvas> <div> <button id = "start" onclick="start()">スタート</button> <button id = "stop" onclick="stop()">ストップ</button> </div> </div> <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 |
body { background-color: #000; } #container { width: 360px; height: 480px; background-color: #000; } #canvas { margin-left: 0px; margin-top: 0px; } #start, #stop { margin-left: 120px; margin-top: 30px; width: 120px; height: 50px; } |
グローバル変数と定数
グローバル変数と定数を示します。
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 |
// 半径 const radius = 150; // canvasの幅と高さ const CANVAS_WIDTH = 360; const CANVAS_HEIGHT = 360; // 中心座標 const centerX = 180; const centerY = 180; // ルーレットの目の数 const count = 7; // 各目に描画するイメージが格納されている配列 const images = []; // 各目の背景色 const colors = ['#f40','#0f0','#00f','#ff0','#f0f','#0ff','#808',]; // 回転速度(弧度法) const MAX_SPEED = 0.3; const MIN_SPEED = 0.01; let speed = 0; // 扇形の中心角 const rad = Math.PI / count * 2; const startRad = -rad / 2; const endRad = rad / 2; // 扇形に内接する正方形の一辺の長さと座標 const squareLength = radius * Math.sqrt(1/ (Math.pow(1 + 0.5 / Math.tan(rad / 2), 2) + 0.25)); const sx = 0.5 / Math.tan(rad / 2) * squareLength; // canvas要素とコンテキスト const $canvas = document.getElementById('canvas'); const ctx = $canvas.getContext('2d'); // スタートボタンと停止ボタン const $start = document.getElementById('start'); const $stop = document.getElementById('stop'); // 効果音 const startSound = new Audio('./sounds/start.mp3'); const stopedSound = new Audio('./sounds/stoped.mp3'); |
ページが読み込まれたときの処理
画像ファイルはimagesフォルダのなかに0.png~6.pngという名前をつけて保存されています。これらを読み出してイメージを配列に格納します。それから回転が始まっていないのに停止ボタンを選択できるのはおかしいので、これを非表示にします。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
window.onload = () => { $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; // 画像ファイルを読み出してイメージを配列に格納 for(let i = 0; i < 7; i++){ const image = new Image(); image.src = `./images/${i}.png`; images.push(image); } // 停止ボタンを非表示に $stop.style.display = 'none'; // 更新処理の開始 setInterval(() => { update(); }, 10); } |
更新処理
扇形を描画してその内部を塗りつぶし、そのなかに画像を描画します。扇形の中心角だけ回転させながらこれを繰り返せば円形のルーレットを描画することができます。そして右側にルーレットのどの部分を指しているかがわかるように三角形の図形を描画します。
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 |
let initRound = 0; function update(){ // canvas全体を黒で塗りつぶす ctx.fillStyle = '#000'; ctx.fillRect(0, 0, $canvas.width, $canvas.height); ctx.save(); // 初期状態からどれだけ回転しているか?(回転速度を追加すればよい) initRound += speed; // 中心になる座標が原点になるように平行移動 ctx.translate(centerX, centerY); // 回転 ctx.rotate(initRound); for(let i = 0; i < count; i++){ // 扇形を描画 ctx.beginPath(); ctx.arc(0, 0, radius, startRad, endRad); ctx.lineTo(0, 0); ctx.closePath(); // 扇形の内部を塗りつぶす ctx.fillStyle= colors[i]; ctx.fill(); // 扇形に内接するようにイメージを描画する ctx.drawImage(images[i % images.length], sx, - squareLength / 2, squareLength, squareLength); ctx.rotate(rad); // これをcount回繰り返せば扇形がつながって円形になる } ctx.restore(); // ルーレットを指す三角形を描画する // 三角形の内部を描画 ctx.beginPath(); ctx.moveTo($canvas.width, centerY - 10); ctx.lineTo($canvas.width - 45, centerY); ctx.lineTo($canvas.width, centerY + 10); ctx.closePath(); ctx.fillStyle= '#f00'; ctx.fill(); // 三角形の輪郭を描画 ctx.lineWidth = 2; ctx.strokeStyle = '#fff'; ctx.stroke(); ctx.lineWidth = 1; } |
ルーレットを回転させる
回転ボタンをクリックしたときの処理を示します。
speedを0からMAX_SPEEDに変更することで、更新処理時にルーレットが回転しているように見えるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
async function start(){ // 回転速度を最大に speed = MAX_SPEED; // 開始ボタンを非表示にして停止ボタンを表示させる $start.style.display = 'none'; $stop.style.display = 'block'; // 効果音を再生する startSound.currentTime = 0; startSound.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 |
function stop(){ // 停止ボタンを非表示に $stop.style.display = 'none'; const id = setInterval(() => { // 回転速度を低下させ、最低速度を下回ったら停止させる if(speed > MIN_SPEED) speed -= 0.004; else{ // 停止させるときは回転速度を0にしてインターバルを削除する speed = 0; clearInterval(id); // 回転時の効果音を停止 // 一呼吸おいて停止時の効果音を再生する startSound.pause(); setTimeout(() => { $start.style.display = 'block'; stopedSound.currentTime = 0; stopedSound.play(); }, 300); } }, 30); } |