Processingで音ゲーを作成

投稿者: | 7月 22, 2025

きっかけ

バイトでやってるプログラミング教室で教材として作り、せっかくなので公開します。

上からノーツが落ちてくる感じのやつです。

// ノーツが落ちてくる感じの音ゲー

// 画面設定
int daten = 900; // 打つタイミングの位置(Y座標)
final int windowX = 300; // ウィンドウサイズ
final int windowY = 1000;

int [] commentX = {80,180}; // 各列のコメントの位置(X座標)
int commentY = 950; // コメントの位置(Y座標)

// ノーツの設定
// ノーツのタイミングの設定 notuN[列][行]/60秒 のタイミングにdatenで打つようにノーツが現れる
int[][] notuN = {
  {100, 200, 300, 450, 550, 1000}, // 1列目
  {400, 500, 600, 700 ,800, 900} // 2列目
};
int notuMaxLength = 0; // 長い配列の番号 1列目なら0、2列目なら1
int[] notuX = {100, 200}; // それぞれの列の位置

class Notu { // ノーツのクラス
  int t; // ノーツのタイミング(フレーム数)
  int x; // ノーツの位置
  int v = 5; // ノーツの速さ 1フレームあたりのyの変化量
  boolean exist = true; // 存在するときはtrue
  int size = 50; // ノーツの大きさ

  int move(int nowT) { // ノーツを描画する
    int notuToDaten = (t - nowT) * v; // 打つタイミングとノーツの距離
    if (notuToDaten < daten + size/2 && exist) { //画面の範囲内か?
      ellipse(x, daten - notuToDaten, size, size); // ノーツの円を書く
    }
    if (windowY + size/2 - daten < -notuToDaten && exist) { // 画面の範囲外になったとき
      exist = false; // 消える
      return -1; // miss
    }
    return 0;
  }

  int click(int nowT) { // ノーツと打点の距離のチェック
    int notuToDaten = (t - nowT) * v; // 打つタイミングとノーツの距離
    if (abs(notuToDaten) < size && exist) { // 距離がノーツの直径よりも小さい時(半径分の猶予がある)
      exist = false; // 消える
      return notuToDaten; // 距離を返す
    }
    return windowY;
  }
}

Notu[][] notu = new Notu[notuN.length][notuN[notuMaxLength].length]; // ノーツの配列を作成

void setup() {
  size(10,10); // ウィンドウの仮作成
  windowResize(windowX, windowY); // ウィンドウのサイズ変更
  PFont myfont = createFont("MS Gothic", 20); // 文字サイズの変更
  textFont(myfont); // 文字サイズの変更
  // 各ノーツの設定
  for (int i = 0; i < notuN.length; i++) { // 列の選択
    for (int j = 0; j < notuN[i].length; j++) { // 行の選択
      notu[i][j] = new Notu(); // ノーツのインスタンスを作成
      notu[i][j].x = notuX[i]; // ノーツの列を設定
      notu[i][j].t = notuN[i][j]; // ノーツのタイミングを設定
    }
  }
}

int t = 0; // 現在のフレーム
int score = 0; // 得点
int[] comment = {0, 0}; // コメント識別番号&点数、3:Great,2:Good,1:Bad,-1:miss
int[] commentCount = {0, 0}; // コメントを表示する時間のカウント、0の間はコメント非表示、1フレームで1つ減る

void draw() {
  background(255); // 背景を描画
  line(0, daten, windowX, daten); // 打つタイミング用の線を描画
  for (int i = 0; i < notuN.length; i++) { // ノーツの行
    for (int j = 0; j < notuN[i].length; j++) { // ノーツの列
      if (notu[i][j].move(t) == -1) { // ノーツを描画する
        comment[i] = -1; // miss
        commentCount[i] = 60; // 1秒(60コマ)の間表示
      }
    }
  }
  fill(0);
  text(score, 10, 20); // スコアの表示
  // 上の行のノーツの点数の表示
  for (int i = 0; i < comment.length; i++) {
    if (0 < commentCount[i]) { // 表示させる時間内か?
      fill(0);
      if (comment[i] == -1){ // Missのとき
        text("Miss", commentX[i], commentY); // Missを表示
      } else if (comment[i] == 3){
        text("Great", commentX[i], commentY);
      } else if (comment[i] == 2){
        text("Good", commentX[i], commentY);
      } else if (comment[i] == 1){
        text("Bad", commentX[i], commentY);
      }
      commentCount[i] -= 1; // 表示時間を一つ減らす
    }
  }
  t++;
}

// スコアの計算
void calscore(int retu) {
  int point;
  for (int i = 0; i < notuN[retu].length; i++) {
    point = notu[retu][i].click(t); // pointは打点とノーツの距離(=小さいほど近い)
    if (point < windowY) { // 有効なpointか?
      if (point < notu[retu][i].size / 4) {
        comment[retu] = 3; // Great
      } else if (point < notu[0][i].size / 2) {
        comment[retu] = 2; // Good
      } else {
        comment[retu] = 1; // Bad
      }
      score += comment[retu]; // スコアに点数を足す
      commentCount[retu] = 60; // 1秒(60コマ)の間表示
      break;
    }
  }
}

// マウスがクリックされたとき
void mousePressed() {
  if (mouseButton == LEFT) { // 左
    calscore(0); // 1列目のスコアを計算
  }
  if (mouseButton == RIGHT) { // 右
    calscore(1); // 2列目のスコアを計算
  }
}

座標の関係はこんな感じです。

太鼓の達人てきな感じで右から左にノーツが流れてくるやつです。

// 太鼓の達人的な

// 画面設定
int daten = 100; // 打つタイミング(Y座標)
final int windowX = 1000; // ウィンドウサイズ
final int windowY = 300;

int commentX = 10; // コメントの位置(X座標)
int [] commentY = {80,180}; // 各行のコメントの位置(Y座標)

// ノーツの設定
// ノーツのタイミングの設定 notuN[行][列]/60秒 のタイミングにdatenで打つようにノーツが現れる
int[][] notuN = {
  {100, 200, 300, 450, 550, 1000}, // 1行目
  {400, 500, 600, 700 ,800, 900} // 2行目
};
int notuMaxLength = 0; // 長い配列の番号 1行目なら0、2行目なら1
int[] notuY = {100, 200}; // それぞれの行の位置

class Notu { // ノーツのクラス
  int t; // ノーツのタイミング(フレーム数)
  int y; // ノーツの位置
  int v = 5; // ノーツの速さ 1フレームあたりのxの変化量
  boolean exist = true; // 存在するときはtrue
  int size = 50; // ノーツの大きさ

  int move(int nowT) { // ノーツを描画する
    int notuToDaten = (t - nowT) * v; // 打つタイミングとノーツの距離
    if (notuToDaten < windowX - daten + size/2 && exist) { //画面の範囲内か?
      ellipse(notuToDaten + daten, y, size, size); // ノーツの円を書く
    }
    if (daten + size/2 < -notuToDaten && exist) { // 画面の範囲外になったとき
      exist = false; // 消える
      return -1; // miss
    }
    return 0;
  }

  int click(int nowT) { // ノーツと打点の距離のチェック
    int notuToDaten = (t - nowT) * v; // 打つタイミングとノーツの距離
    if (abs(notuToDaten) < size && exist) { // 距離がノーツの直径よりも小さい時(半径分の猶予がある)
      exist = false; // 消える
      return notuToDaten; // 距離を返す
    }
    return windowX;
  }
}

Notu[][] notu = new Notu[notuN.length][notuN[notuMaxLength].length]; // ノーツの配列を作成

void setup() {
  size(10,10); // ウィンドウの仮作成
  windowResize(windowX, windowY); // ウィンドウのサイズ変更
  PFont myfont = createFont("MS Gothic", 20); // 文字サイズの変更
  textFont(myfont); // 文字サイズの変更
  // 各ノーツの設定
  for (int i = 0; i < notuN.length; i++) { // 行の選択
    for (int j = 0; j < notuN[i].length; j++) { // 列の選択
      notu[i][j] = new Notu(); // ノーツのインスタンスを作成
      notu[i][j].y = notuY[i]; // ノーツの行を設定
      notu[i][j].t = notuN[i][j]; // ノーツのタイミングを設定
    }
  }
}

int t = 0; // 現在のフレーム
int score = 0; // 得点
int[] comment = {0, 0}; // コメント識別番号&点数、3:Great,2:Good,1:Bad,-1:miss
int[] commentCount = {0, 0}; // コメントを表示する時間のカウント、0の間はコメント非表示、1フレームで1つ減る

void draw() {
  background(255); // 背景を描画
  line(daten, 0, daten, windowY); // 打つタイミング用の線を描画
  for (int i = 0; i < notuN.length; i++) { // ノーツの行
    for (int j = 0; j < notuN[i].length; j++) { // ノーツの列
      if (notu[i][j].move(t) == -1) { // ノーツを描画する
        comment[i] = -1; // miss
        commentCount[i] = 60; // 1秒(60コマ)の間表示
      }
    }
  }
  fill(0);
  text(score, 10, 20); // スコアの表示
  // 上の行のノーツの点数の表示
  for (int i = 0; i < comment.length; i++) {
    if (0 < commentCount[i]) { // 表示させる時間内か?
      fill(0);
      if (comment[i] == -1){ // Missのとき
        text("Miss", commentX, commentY[i]); // Missを表示
      } else if (comment[i] == 3){
        text("Great", commentX, commentY[i]);
      } else if (comment[i] == 2){
        text("Good", commentX, commentY[i]);
      } else if (comment[i] == 1){
        text("Bad", commentX, commentY[i]);
      }
      commentCount[i] -= 1; // 表示時間を一つ減らす
    }
  }
  t++;
}

// スコアの計算
void calscore(int gyou) {
  int point;
  for (int i = 0; i < notuN[gyou].length; i++) {
    point = notu[gyou][i].click(t); // pointは打点とノーツの距離(=小さいほど近い)
    if (point < windowX) { // 有効なpointか?
      if (point < notu[gyou][i].size / 4) {
        comment[gyou] = 3; // Great
      } else if (point < notu[0][i].size / 2) {
        comment[gyou] = 2; // Good
      } else {
        comment[gyou] = 1; // Bad
      }
      score += comment[gyou]; // スコアに点数を足す
      commentCount[gyou] = 60; // 1秒(60コマ)の間表示
      break;
    }
  }
}

// マウスがクリックされたとき
void mousePressed() {
  if (mouseButton == LEFT) { // 左
    calscore(0); // 1行目のスコアを計算
  }
  if (mouseButton == RIGHT) { // 右
    calscore(1); // 2行目のスコアを計算
  }
}

座標の関係はこんな感じです。

改造とか

音楽の挿入

ProcessingのSoundライブラリを使い、setup()の最後にplay()を呼ぶのが良いと思います。

譜面の作成

notuN[][]の2次元配列が譜面を意味しています。中身はノーツが打たれるタイミングのフレーム数です。そのため、/60したものが、秒となります。

行・列を増やす

ノーツの列・行数を増やすのは割と簡単にできるようにできています。はじめの方の定義のところの各配列の要素数を増やして、入力方法の部分を書き加えれば増やせると思います。

入力方法の変更

ディフォルトでは、マウスの左右クリックになっています。キーボードとかにすると良いと思います。

音ズレ

処理が遅れると音ズレが起きます。30fpsに減らしたりすることで回避できると思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)