ビット演算、高校レベルの数学、バイナリーデータの読み書きを理解しておくこと
・Processingでリズムゲーム 授業で作った作品の仕組みを非常に軽く解説
・「メディアプログラミング2A」という科目名に心当たりある人は、
このプログラムを書き換えたものであっても提出しないでください。(参考程度にする)
・評価順位1/105位をとった記念に公開しています。
・UIは下手くそなので、自分で変えてください。
※※※このプログラムを実行/何かしらの課題で提出した際に発生した不利益に関しては、責任は負いかねます。※※※
ビット演算、高校レベルの数学、バイナリーデータの読み書きを理解しておくこと
基本的には、太鼓さん次郎と同じような構造
曲名など (ゲーム開始時に取り込まれる)
作者名などの詳細 (ゲーム開始時に取り込まれる)
曲選択時に流れる曲の開始時間[秒](基本的にはサビの時間を入力) (ゲーム開始時に取り込まれる)
譜面を何秒ずらすか(曲の1小節目と譜面の1小説目のタイミングをこれで合わせる)
歌詞トラックの開始地点 カンマ(+改行)区切りで記入 1小節に1文
歌詞トラックの終わり
各難易度の譜面の開始地点 難易度は1から10で指定
譜面終了
各レーンの素因数の値 数値は素数を指定 例)#NUM2 17
譜面速度倍率 追い越し譜面も作れる
臨時で譜面を遅らせる 何らかの理由で譜面と曲がずれる場合に使用
16進数で、1小節ごとにカンマ(+改行)区切りで記入 (左のレーンが1---右のレーンが8の4bit)
例:1小節に4分音符
●●●●
●●●-
●●--
●---
と配置する場合は、
F731,
と記入(小文字不可)
得点獲得量はコンボ数により変わり、以下の式の通りになる (\(c\)...今叩いたノーツを含むコンボ数 \(k\)...判定による倍率)
\(k\)の値: \(PERFECT...1\) \(GOOD...0.5\) \(OK...0.3\) \(BAD,MISS...0\) (Playing Pause内のdouble[] judge_mult参照)
太鼓の達人でいう、初項200点、公比20点(ただし、1000コンボまで得点増加し、Max 1コンボ2200点)
game_data\musicに入っているフォルダをすべて読み込む
.txt,.wavの両方が最低限揃っている曲をすべて取り込み、InfoReader側のInfoクラスのリストに追加する
画像ファイルがなければnoimage.jpgをジャケット画像として表示する
最後に、ゲーム全体で使う画像(PImageクラス)、効果音(Clipクラス)を読み込む
state変数でページ推移を分かりやすく表し、その文字列を使ってswitch文で各場面のdraw関数を実行
game_time配列([演奏時の経過時間,前のフレームからの経過時間,前のフレーム時のgame_time[0]の値]の計算はここで行う
変数se_playingを使って鳴りすぎないようにする
効果音をならす。 変数se_playingを使って鳴りすぎないようにする
最初に、変数kを使って代替キーの入力値を通常キーと合わせる
kの値に応じて、swithc文で各場面のキー入力関数に移動 演奏中は、同時押しに対応させるため、keyTyped関数から移動
キーを離したら、効果音を鳴らせるようにする
同時押しを受け付けるため、演奏中(state=="Playing")のときの処理のみこっちにある
変数を上から 難易度、各難易度のハイスコア、各難易度のクリア状況、デモの再生時間、曲名、詳細、txtのファイルパス、wavのファイルパス、datのファイルパス、ジャケット画像 を、ゲーム起動時に取得
ReadHiScore関数(少し下にある)を使ってハイスコア+クリア状況を取得
ReadCommandD,ReadCommandS関数(少し下にある)を使ってその他情報を取得 ただし、文字列は、文字コードをISO-8859-1→SJISにする(ここ初見だと激ムズ Processingで実行とexeファイルのエクスポートで挙動が変わるので
コンストラクタは、datファイルのありなしで2つある
命令にくっついている文字列を取得
命令にくっついている数値を取得
ファイルをバイナリー形式で読み込み、サンプリングレートを抽出する。
サンプリングレートにgame_speedを掛けて整数にし、抽出したバイト配列を書き換える
サンプリングレートを\(x\)倍すると、再生速度は\(x\)倍 ピッチは半音\(12\log_{2}(x)\)個上がる (\(y=12\log_{2}(x)\)の逆関数は、\(x=2^\frac{y}{12}\))
最後にそのバイト配列で新しいwavファイルを作る。
サンプリングレートの計算式
バイト配列\(d\)において、\(l\)から\(m\)バイト目のリトルエンディアンでの値は
wavファイルは、\(l=24,m=27\)として計算するとサンプリング周波数が求まる(\(44100Hz,48000Hz\)など 収録しているファイルは\(20000Hz\))
この場合は、\(00 00 4E 20_{(16)}\)と読み、10進数に直すと値は\(20000_{(10)}\)[Hz]である。
こちらは1.5倍速でプレイする際に出力される再生用ファイルで、\(00 00 75 30_{(16)}=30000_{(10)}\)[Hz]である。
2つのファイルを比較すると、赤枠の4Byteしか変わっていないことがわかる。(つまり、ここをいじれば再生速度を変えられる)
Clipクラスの変数を作る用 コピペ
各曲のdatファイルは10byteで、以下のような形になっている(最後だけ1bit区切り)
H H H N N N E E E xxhhnnee
H...むずかしいのスコア(リトルエンディアン)
N...ふつうのスコア(リトルエンディアン)
E...かんたんのスコア(リトルエンディアン)
x...未使用
h...むずかしいのクリア状況(0...未クリア 1...クリア 2...フルコンボ 3...パーフェクト)
n...ふつうのクリア状況(0...未クリア 1...クリア 2...フルコンボ 3...パーフェクト)
e...かんたんのクリア状況(0...未クリア 1...クリア 2...フルコンボ 3...パーフェクト)
これをビットシフトなどを活用し読み込む。クリア状況はInfoクラス内で読み取る(変数をまとめて返したいため)
戻り値は、長さ4のint配列 [むずかしいのスコア,ふつうのスコア,かんたんのスコア,クリア状況をそのままの値で]
ReadHiScoreと逆の手順で、クリア状況を記録
音符情報を保持
変数を上から、因数の値、流れるレーンの組み合わせの値(1~15)、押されるべきタイミング、スクロール倍率、判定済みか(初期値false)を格納
素因数変化、曲の終了などを保持
変数を上から、イベント名、開始時間、値、実行されたか(初期値false)で格納
ファイルを読み込み、for文で1文字1文字をint型でチェックする。(char/byte型ではない)
#から改行までの文字列は別で格納して、改行時にReadCommandS,ReadCommandD関数で何の命令、どんな値か確認
歌詞も別で格納して、改行時に文字コードをISO-8859-1→SJISにしてからリストに追加する。
ノーツ情報、一部命令は、選択された難易度の命令文を読み込んだことを確認してから判定する。
,が入力されたら、小節の時間の記録し、,までの一時的なノーツリストの長さが0であれば、休符を1つ追加する。
\(k\)分音符、BPMが\(BPM\)[BPM]のとき、ノーツの間隔\(\Delta t\)[s]は次の計算式をみたすことを利用する。
今までの\(\Delta t\)の合計を蓄積した値を押されるべきタイミングとして記録
最大コンボ数(combo[1])に1であるビットの個数を足す (0xB=0b1011なら+3)
文字がノート番号だった(default:)場合、選択された難易度内のものであれば、文字を16進数に変換する (int char_vの部分)
ランダムを有効にしている場合は、ランダムなビット2つを3回入れ替える
最後に一時的なノーツリストに格納する
描画とオート時の判定など
//同期の部分で定期的に譜面と曲の再生時間を合わせる(頻度が多いと重い 少ないと同期したときに音符がカクつく)
auto_modeがtrueなら、押されるべき時間(notes.get(i).time)と現在時刻(game_time[0])の差がある程度少なければ(2未満)押したことにする
基本的に音を鳴らすが、押されるべき時間からある程度(100以上)経過していれば押さない(多重で鳴るのを防止)
現在コンボ数にノーツの1であるビット数だけ、for文でコンボ数、スコアの計算(PERFECT判定で)
最後に対象のノーツのメンバ変数judgedをtrueにする
auto_modeがfalseかつ、押されるべき時間からBAD判定(150)以上経過しているノーツがあれば、コンボ数を0、そのノーツのjudgedをtrueにする
未判定かつ画面から50px上より下で表示できるものは、各ビットに対応するレーンにノーツを表示
各小節の時間のリストと歌詞のリストを比較して、時間にあった歌詞を表示 なければ表示しない(歌詞リストの長さが0など)
イベントもここで処理する。現在時刻がイベントの時間を超えたイベントの処理をすべて実行
イベントはswitch文でそれぞれ処理し、最後にjudgedをtrueにする。
もし、ここでENDが来たら曲を終了させ、リザルト画面へ推移(state="Result")
最後に、D/G/J/Lキー入力(press_key)の各ビットを確認し、1であればどのノーツを押したか判定する(JudgeNote関数)
対象のビットを0にして終了(桁を多くとってint型にするともっと綺麗にかける press_key = press_key &~(1<<i))
ポーズ画面の描画
演奏中のキー入力
press_key |= (1 << n)のような形でかける
case 'e':の時だけポーズ画面への推移の処理をする
音も鳴らす
演奏中のキー入力
wsで上下移動(sel_pauseを増減)
ポーズから一定時間以上経過かつ、sel_pauseが0(再開)のとき演奏中へ、1なら曲Loading(一瞬 実質未使用)から選択画面へ
num_light[ind] = 3;でノーツを光るように設定
\(\Delta t=|\)ノーツの押される時間\(-\)現在時刻\(|\)が最小になるノーツを探す。
\(\Delta t\)がjudge_range[3](BAD判定)以下(未満)かつ、押したキーに対応するビットが1であれば、そのノーツを判定する。
ここでfor文を使い、ノーツを判定、判定に応じた倍率の得点を与える。\(i\lt2\)(GOOD判定まで)なら、コンボ数を増加。でなければ0にする。
判定したら、キーに対応するビットを0にし、すべてのビットが0ならそのノーツを判定済みにする。(notes.get(near[0]).judged = true;)
一部変数のリセット、譜面の再読み込みなどしてから演奏中へ
結果を描画 スコア、各判定のノーツ数、結果(クリア/フルコンボ/パーフェクト)、オート/低速プレイ(記録不可)かどうかを表示
ロード画面(実質未使用)
リザルト画面のキー入力
ハイスコアかクリア状況が良くなった時にSetHiScore関数を用いてdatファイルを書き換える
オートと低速プレイ(再生速度1未満 誤差を考慮して0.99未満に設定)の場合は記録しない
曲選択画面の描画
曲選択画面関連すべてのベース(背景など)の描画
難易度の描画
オプションの描画
曲選択画面のキー入力
sel_music = (sel_music - 1 \(\pm\) info.size()) % info.size();のように、選択肢の個数の倍数を足すことで、正負を気にせず選択番号の0と最終番号をループさせることができる。
再生させる曲と時間を、譜面ファイル内で指定した時間(#DEMO)に応じて変える
難易度選択と開始
オプション選択
bool値は、random_note = !random_note;のようにすると簡単に切り替えられる
タイトルを表示
タイトル画面操作用
説明表示(swich文でページ切り替え)
説明時のページ移動用