著者:岩本 和真
SLPでは最近、Webブラウザで動作するリズムゲームをチームで開発しました。さまざまな曲でプレーできるように、リズムゲーム本体と並行して、ゲームで使用する譜面を作成するツールも開発しました。このツールもWebブラ
ウザで動作します。今回は、この譜面作成ツールの実装について紹介します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。![]()
![]()
図3 最初に実行されるコード
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()関数のコード
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()関数のコード
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()関数のコード
function numberQLine(bpm, musicL) {
return new Promise(function(resolve) {
qLineQty = Math.floor(musicL / (60 / bpm) + 1);
resolve();
})
}
図7 setCanvas()関数のコード
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()関数のコード
function setQLine() {
return new Promise(function(resolve) {
for (let i = quarterLine.length; i < qLineQty; i++) {
quarterLine[i] = new QuarterLine(i);
}
resolve();
})
}
図9 QuarterLineクラスのコード
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()関数のコード
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()関数のコード
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()関数のコード
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()関数のコード
function scrollBottom() {
return new Promise(function(resolve) {
let target = document.getElementById('scroll');
target.scrollTop = target.scrollHeight;
resolve();
}
)
図14 レーン上のノーツをマウスクリックで制御するためのコード
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 スライダ機能のコード
let canScale = document.getElementById('canScale');
canScale.onchange = async function() {
qLineMargin = this.value;
await setCanvas();
await update();
await draw();
await scrollBottom();
}
図16 apply()関数のコード
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()関数のコード
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);
}
