著者:増田 嶺
前回に引き続き、SLPで開発したリズムゲームを紹介します。前回はゲームで使用する譜面作成ツールについて解説しました。今回は、ゲーム本体について解説します。ゲーム本体は「Visual Studio Code」(VSCode)で開発します。
図5 「js/game/singleNote.js」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { constructor(speed, reachTime) { this.height = note.height; this.width = note.width; this.frameColor = 'rgb(' + note.frameColor + ')'; // 枠の色 this.bodyColor = 'rgba(' + note.bodyColor + ')'; // 中の色 this.this.centerY = -this.height; // ノーツ中心のY座標 this.speed = speed * note.speedRatio; // px/ms // ゲーム開始から判定ラインに到達するまでの時間 this.reachTime = reachTime + note.delay; // canvasに入る時間 this.appearTime = this.reachTime - (JUDGE_LINE.centerY + this.height / 2) / this.speed; // canvasから出る時間 this.hideTime = this.reachTime + (CANVAS_H - JUDGE_LINE.centerY + this.height / 2) / this.speed; this.act = true; // 自身がヒット処理対象かどうか this.show = false; // 自身がcanvasに描画されるかどうか } } |
図6 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 |
(略) generateNote(data) { this.note = data.map((val) => new SingleNote(val[2], val[3])); } (略) |
図7 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { constructor(speed, reachTime) { (略) } draw(x) { if (this.show) { this.centerY = JUDGE_LINE.centerY - (this.reachTime - time.elapsed) * this.speed; CTX.fillStyle = this.bodyColor; CTX.fillRect(x, this.centerY - this.height / 2, this.width, this.height); CTX.strokeStyle = this.frameColor; CTX.strokeRect( x + LINE_WIDTH / 2, this.centerY - this.height / 2 + LINE_WIDTH / 2, this.width - LINE_WIDTH, this.height - LINE_WIDTH ); } } } |
図8 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 |
(略) drawNote() { this.note.forEach(val => val.draw(this.x)); } (略) |
図9 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { constructor(speed, reachTime) { (略) } draw(x) { (略) } update() { this.updateShow(); } updateShow() { if (this.act || this.show) { if (this.appearTime <= time.elapsed && time.elapsed <= this.hideTime) { this.show = true; } else { this.show = false; } } } } |
図11 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 |
(略) update() { this.note.forEach(val => val.update()); } (略) |
図13 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(略) judge() { calcElapsedTime(); // 経過時間を更新 // ヒットしているノーツを抽出 const TARGET = this.note.filter(val => val.checkHit(note.hitRange[3])); // TARGETの先頭から処理、先頭ノーツのグレードがBadだった場合のみ // 二つ目以降のノーツを処理し、それらのノーツがBadだった場合は中断 const GRADE = [3] for (let i = 0; TARGET[i] && GRADE[0] === 3; i++) { GRADE[i] = TARGET[i].getGrade(); // 二つ目以降のノーツがBadの場合はそこで中断 if (i > 0 && GRADE[i] === 3) { break; } JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画 TARGET[i].close(); // 判定済みのノーツ処理を停止 } } (略) |
図15 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class SingleNote { (略) updateShow() { (略) } checkHit(range) { if (this.act && Math.abs(time.elapsed - this.reachTime) <= range) { return true; } else { return false; } } getGrade() { let grade = 3; for (let i = 2; i >= 0; i--) { if (this.checkHit(note.hitRange[i])) { grade = i; } } return grade; } } |
図16 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { (略) update() { this.updateShow(); if (!this.act) { return; } // 判定ラインから自身の判定ゾーンが // 過ぎた時点で処理 if (this.reachTime < time.elapsed && !this.checkHit(note.hitRange[3])) { JUDGE_LINE.setGrade(3); this.act = false; } } (略) close() { this.act = false; this.show = false; } } |
図18 「js/game/gameScoreManager.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 |
(略) calcScore(grade) { switch (grade) { case 0: this.point += 100 + this.combo; this.perfect++; this.combo++; break; case 1: this.point += 80 + this.combo * 0.8; this.great++; this.combo++; break; case 2: this.point += 50 + this.combo * 0.5; this.good++; this.combo++; break; case 3: this.bad++; this.combo = 0; break; } if (this.maxCombo < this.combo) { this.maxCombo = this.combo; } } (略) |
図19 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(略) judge() { (略) if (GRADE[i] < 3) { // Bad以外の判定ならヒットSEを鳴らす sound.playSE(sound.seList[0]); } else { sound.playSE(sound.seList[1]); // Bad判定ならバッドSEを鳴らす } gameScore.calcScore(GRADE[i]); // スコアを更新 JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画 TARGET[i].close(); // 判定済みのノーツ処理を停止 } } (略) |
図20 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class SingleNote { (略) update() { (略) if (this.reachTime < time.elapsed && !this.checkHit(note.hitRange[3])) { gameScore.calcScore(3); JUDGE_LINE.setGrade(3); this.act = false; } } (略) } |
図22 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(略) draw() { (略) if (inputKey.status[this.key]) { const GRAD = CTX.createLinearGradient(this.x, JUDGE_LINE.centerY, this.x, CANVAS_H / 3); GRAD.addColorStop(0.0, 'rgba(' + this.actColor + ', 0.6)'); GRAD.addColorStop(1.0, 'rgba(' + this.actColor + ', 0)'); CTX.fillStyle = GRAD; CTX.fillRect(this.x, 0, this.width, JUDGE_LINE.centerY); } } (略) |