著者:増田 嶺
前回に引き続き、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);     }   } (略) | 


