著者:岩本 和真
SLPでは最近、Webブラウザで動作するリズムゲームをチームで開発しました。さまざまな曲でプレーできるように、リズムゲーム本体と並行して、ゲームで使用する譜面を作成するツールも開発しました。このツールもWebブラ
ウザで動作します。今回は、この譜面作成ツールの実装について紹介します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
図3 最初に実行されるコード
1 2 3 4 5 6 7 8 9 10 11 |
window.onload = async function() { const {bpm, musicL} = getMusicInfoViaElement() await getWidth(); await numberQLine(bpm, musicL); await setCanvas(); await setQLine(); await setXLine(); await update(); await draw(); await scrollBottom(); } |
図4 getMusicInfoViaElement()関数のコード
1 2 3 4 5 6 |
function getMusicInfoViaElement() { const bpm = document.getElementById('bpm').value; const musicL = document.getElementById('musicL').value; const musicBody = document.getElementById('musicBody').value; return {bpm, musicL, musicBody} } |
図5 getWidth()関数のコード
1 2 3 4 5 6 7 8 9 10 11 |
const can = document.getElementById("can2"); const ctx = can.getContext("2d"); can.setAttribute('style', 'background-color: #f6f7d7'); function getWidth() { return new Promise(function(resolve) { cst = window.getComputedStyle(can); canvasW = parseInt(cst.width); can.width = canvasW; resolve(); }) } |
図6 numberQLine()関数のコード
1 2 3 4 5 6 |
function numberQLine(bpm, musicL) { return new Promise(function(resolve) { qLineQty = Math.floor(musicL / (60 / bpm) + 1); resolve(); }) } |
図7 setCanvas()関数のコード
1 2 3 4 5 6 7 8 9 10 11 |
let qLineMargin = 80; function setCanvas() { return new Promise(function(resolve) { canvasH = qLineQty * qLineMargin; can.height = canvasH; ctx.translate(0, can.height); ctx.scale(1, -1); can.style.height = canvasH * 2.5 +'px'; resolve(); }) } |
図8 setQLine()関数のコード
1 2 3 4 5 6 7 8 |
function setQLine() { return new Promise(function(resolve) { for (let i = quarterLine.length; i < qLineQty; i++) { quarterLine[i] = new QuarterLine(i); } resolve(); }) } |
図9 QuarterLineクラスのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class QuarterLine { constructor(no) { this.no = no; this.y = qLineMargin * this.no + qLineMargin / 4; } update() { this.y = qLineMargin * this.no + qLineMargin / 4; } draw() { ctx.beginPath(); ctx.strokeStyle = "#3ec1d3"; ctx.lineWidth = Q_LINE_T; ctx.moveTo(0, Math.round(this.y)); ctx.lineTo(canvasW, Math.round(this.y)); ctx.stroke(); } } |
図10 setXLine()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function setXLine() { return new Promise(function(resolve) { for (let i = 0; i < 4; i++) { for (let j = xLine[i].length; j < qLineQty; j++) { xLine[i][j] = []; for (let k = 0; k < 14; k++) { xLine[i][j][k] = new XthLine(i, j, k); } } } resolve(); }) } |
図11 update()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function update() { return new Promise(function (resolve) { // ノーツの縦の長さを更新 noteH = (qLineMargin / 9 >= Q_LINE_T || divValue == 8) ? qLineMargin / 9 : Q_LINE_T // ノーツの位置と各拍、各連符の横線の位置を更新 const laneMargin = canvasW / 5; laneSet = [laneMargin / 2, laneMargin * 1.5, laneMargin * 2.5, laneMargin * 3.5, laneMargin * 4.5]; noteW = laneMargin / 3; quarterLine.forEach((val) => val.update() ) editLane.forEach((val) => val.update() ) for (let i = 0; i < 4; i++) { for (let j = 0; j < qLineQty; j++) { for (let k = 0; k < 14; k++) { xLine[i][j][k].update(); } } } resolve(); }) } |
図12 draw()関数のコード
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 draw() { return new Promise(function(resolve) { ctx.clearRect(0, 0, can.width, can.height); for (let i = 0; i < 4; i++) { for (let j = 0; j < qLineQty; j++) { for (let k = 0; k < 14; k++) { xLine[i][j][k].draw(); } } } for (let i = 0; i < qLineQty; i++) { quarterLine[i].draw(); } editLane.forEach((val) => val.draw()) for (let i = 0; i < 4; i++) { for (let j = 0; j < qLineQty; j++) { for (let k = 0; k < 14; k++) { xLine[i][j][k].drawNote(); } } } resolve(); }) } |
図13 draw()関数のコード
1 2 3 4 5 6 7 |
function scrollBottom() { return new Promise(function(resolve) { let target = document.getElementById('scroll'); target.scrollTop = target.scrollHeight; resolve(); } ) |
図14 レーン上のノーツをマウスクリックで制御するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
let mouseDown; can.addEventListener('mousedown', async function(e) { mouseDown = true; await pos(e); await update(); await draw(); }); can.addEventListener('mouseup', function(e) { mouseDown = false; }); function pos(e) { return new Promise(function(resolve) { mouseDownX = (e.clientX - can.getBoundingClientRect().left); mouseDownY = -(e.clientY - can.getBoundingClientRect().bottom) / 2.5; resolve(); }) } |
図15 スライダ機能のコード
1 2 3 4 5 6 7 8 |
let canScale = document.getElementById('canScale'); canScale.onchange = async function() { qLineMargin = this.value; await setCanvas(); await update(); await draw(); await scrollBottom(); } |
図16 apply()関数のコード
1 2 3 4 5 6 7 8 9 10 |
async function apply() { const {bpm, musicL} = getMusicInfoViaElement() await numberQLine(bpm, musicL); await setCanvas(); await setQLine(); await setXLine(); await update(); await draw(); return Promise.resolve(); } |
図17 convert()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function calcNote(jnoteValue, calced, musicBody) { return Math.round((jnoteValue+calced+musicBody*1000)*100)/100; } async function convert() { await apply(); const fileName = document.getElementById('fileName').value; const speed = document.getElementById("speed").value; const {bpm, musicL, musicBody} = getMusicInfoViaElement() const {noteValue, note32Value, note6Value} = calcNoteValue(bpm) const outInfo = Array() xLine.forEach((val1, i) => { val1.forEach((val2, j) => { val2.forEach((val3, k) => { if (val3.note) { const tmp = k < 8 ? calcNote(j*noteValue, k*note32Value, musicBody) : calcNote(j*noteValue, k%8*note6Value, musicBody) outInfo.push(Array(1, i+1, speed, tmp)) } }) }) }) createAndDownloadCsv(fileName, outInfo, bpm, musicL, musicBody); } |