著者:岩本 和真
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); }  |