GoogleAdsence

DXライブラリとC#での音楽ゲームの作り方その1 BMS形式の特徴

2020年2月24日

弾幕シューティングゲームも完成したので、次は音楽ゲームの作り方を解説します。

音楽ゲームに必要な要素

  • 譜面データ
  • 曲データ
  • 各種入力デバイス
  • エフェクト
  • カウンタによるオブジェクトの計算
  • 入力したタイミングによる判定

さっと思いつくだけでも以上の要素が必要になります。
ここで一番重要なのは、譜面データを決めて、そのデータを読み込みどの様に音楽と同期させるかです。

BMS形式

今回は譜面データはBMS形式を採用することにします。これはやねうらおさんが開発したBM98で使われた形式で、今でもたくさんの音楽ゲームで使用されている形式です。

BMS形式の特徴

通常の楽器などでは、楽譜が楽器に対して1つ割り当てられてるため、 楽譜上の音符は全て同じ楽器の音であり音階が違うだけですが、 BMSにはそれがなく1つの音符ごとに音を自由に割り当てられ、分母を自由に変えられるのが最大の特徴です。
さらにテキスト形式のファイルなので、メモ帳などのエディタでも簡単に編集することができます。
そして、仕様に関していうと、システムコマンド(ヘッダ)部分と音楽データ部分に分かれます。

システムコマンド(ヘッダ)

上が実際にBMSで使われているヘッダです。

#PLAYER nプレイヤー番号 何人プレイかを指定。
1:Single Play(1P)
2:Couple(1p+2p)
3:Double Play(1p&2pを1人で)
4:Battle(1Pと同じシーケンスを2人で)(Delightでは未実装)
#GEANLE xxxxジャンルジャンル名です。
#TITLE xxxxタイトルタイトル名です。
#ARTIST xxxxアーティストアーティスト名です。
#PLAYERLEVEL nゲームの難易度この譜面データの楽曲の難易度を表します。
#RANK nBMSの判定の厳しさを指定。0:Very Hard
1:Hard
2:Normal
3:Easy
#VOLWAL nマスターボリューム 音量を現段階のn%として出力するか指定
#BPM n Beats Per Minute 曲のBPM(1分あたりの四分音符の数)を指定(省略すると130と指定される)
#TOTAL n ゲージの増減率 ゲージの増量を指定。
オールGREATでn%ととする。
#STAGEFILE nステージファイルデータ読み込み時に表示する画像ファイルを指定。
#BMP ファイル名画像アニメーション画像ファイルを任意のタイミングで表示させための定義。

音声データ

#WAVnn xxxxxx.wav(or xxxxxx.mp3) (or xxxxxx.ogg)
読み込むwavかmp3かoggファイルを指定。01からFZの36進数で指定。
xにはファイル名が入る。

音楽データ

データの書き方

#aaabb:xxxxxxx

aaa10進数で3桁の小節ナンバー。000から始まって最大値が999となる。
bbチャンネルナンバー。10進2桁で指定。
xx各小節内でのオブジェクト配列。2文字を1単位として指定。 実際のデータとなり文字数は可変となりますが必ず偶数桁になります。桁数を増やして分母は自由に決められる。

チャンネルナンバーについて

上記のbbの部分の詳細です。

01 その位置に来たら自動的に再生されるWAVを指定(BGM再生)
このチャンネルは同一の位置に複数のサウンドを配置することが出来ます。
これによりバスドラやスネア、ハイハットのWAVだけを使ってリズムを作ることも出来ます。
02 指定の小節の長さを定義倍する(小数点可)
#00102:0.5   … 1小節目を2/4拍子に
#00202:0.75  … 2小節目を3/4拍子に

この命令は1小節に1つの定義しか出来ません。
もし複数定義されていた場合は後方を優先します。
例)
 #00102:0.25
 #00102:0.5
 ※1小節目に2つの定義があった場合は後方の0.5が使用される
03 再生テンポの途中変更(1~255を16進数で定義可能)
04 バックグラウンドのアニメーション画像の定義
#BMPnnで指定した番号を使用します。
05落下してくるオブジェを、別のキャラクターナンバーのものにすり替える機能
06 POORを出したときに表示される画像の変更
07
04で指定したBGAの上にかぶせるBMPを指定(スプライト表示)
8bitや24bit画像の場合は、抜き色(カラーキー)として黒で塗られた部分が
透過されるようになっています。なお、グラフィックボードにより24bitカラーを
16bitカラーに変換する場合など、濃い灰色も黒に変換されることがあり、
意図しない箇所が透過されてしまうという問題があります。
このため黒を表現するには、16bitに変換しても黒にならないような
濃い灰色を選択する必要があります。
08 インデックス型テンポ変更(拡張版)
合わせて#BPM??コマンドが追加(#BPMとは別物で続けてインデックスが付くかで判断する必要がある)されており、先に256個分のテンポバッファに指定のテンポ値を入れておき、データ側にてそのインデックスを指定することで、指定のタイミングでテンポを変更することが出来ます。これは03チャンネルでは16進数の上限としてFF(255bpm)までしか定義出来なかったのと、小数値のテンポチェンジが一切扱えなかったという問題を解決するために追加されました。※このサイトではこの機能も実装しています
09 ストップシーケンス(拡張版)
一定時間譜面のスクロールを完全に停止させます。
10~19 1プレイヤー用の音符データ
音符データは同一の位置に存在してはなりません。
※厳密には存在可能ですが、ゲーム中は1回のキー押しで1つしか判定されないため、
 もう1つの方はミスになってしまいます
20~29 2プレイヤー用の音符データ(1プレイヤー側と仕様は同じ)
コロン コマンド部とデータ部の分割用です。
システムコマンドではスペースでしたが、こちらはコロンにて分割する形になっています。

.サンプル

ここでサンプルを見てみましょう。

#WAV01 BOMB.WAV
#00111:01

まず始めにシステムコマンドとしてWAVコマンドを使用しています。
ここではインデックス01番目のWAVデータとしてBOMB.WAVというファイルを使用するという意味になります。

そして曲定義部の00111では上で説明した書式から、1小節目のチャンネル11番にデータを記入しています。ちなみにチャンネル11番目というのはBM98でいうと左下の白鍵となります。

実データには01と定義されていますが、この番号はWAVのインデックス番号となるので、
ここでは1小節目の最初にBOMB.WAVを鳴らすという解釈となっています。

全音符からn分音符

上で説明したサンプルデータではオブジェは1つしか定義しませんでした。
ということはこれは、全音符という1小節まるまる鳴らすという定義になります。

ではn分音符の定義をするにはどうするでしょうか?
順番に考えていきましょう。 まず1小節内に1個とするにはデータを1個(01)を指定しました。
1小節内に2個とするにはデータを2個配置すればいいと言う事ですね。
これを半音符と言いました。

よく耳にする(と思われる)四分音符というのは1小節に4個あるわけなので、
これは4個書けばいい事になります。

そしてこれを踏まえて以下の表にまとめてみました。

名称データ文字数
全音符(一分音符)012
二分音符01014
四分音符010101018
八分音符010101010101010116
十六分音符0101010101010101010101010101010132
n分音符0101010101~~~~~~~~~~~~~~~~~~n*2

これだけ出来ればいいような気もしますが、実は音符にはまだまだ種類があります。
3連符(さんれんぷ)というものを聞いたことがあるでしょうか?
表記的には下のような感じです。

連符は元となる音符を下の数字で割ったものとして定義されるそうですが、
実はBMSではこの表記データも記述することが出来ます。

たとえば下のように四分音符を3つに分割した3連符が1小節の最初に1つあったとします。

これをデータ化するとこうなります。

データ文字数
01010100000000000000000024

3連符では1拍あたり3個のデータ(01が3個)になったので、
これを残りの部分に足すことで1小節分のデータを定義出来ることになります。

ちなみに3連符以上のものも存在し、5連符や7連符など1拍内に叩く数が多くなったときは、そのデータ文字数を4倍することで1小節分のデータにすることが出来ますが、7連符を定義したとすると、7連x4x2文字で56文字となるので、細かいデータを定義するほど文字数が増えるということになります。

その他の表記法

これだけかと思いきや、実はまだまだ音符の定義は存在します。

たとえば付点n分音符です。
付点が付くとその音符の長さの半分をその音符に追加した長さになります。
つまり付点が1つ付くとその音符は1.5倍の長さになります。

実は付点は複数付けることができ、付けるごとにその音符の半分の半分の半分の・・・長さをすべて足した長さになります。

ややこしいので詳しくは説明しませんが、付点の付いたデータがあった場合、
小節の細かさをその倍にすればいいということです。
たとえば四分音符の文字数を2倍の八分音符にすることで2倍の細かさで定義が出来るので、これで付点四分音符の長さが定義できます。

なお、BMSは再生開始位置だけを持った仕様であり、その音符の長さ情報は持っていません
このためここでの長さというのは、実際には割り当てた音自体の再生時間であり、
再生時間が次の音符にまたぐような場合でも、特に停止させる必要はありません。

メイン

データの裏技

連符と通常のn分音符が混ざったようなデータはどう定義するでしょうか?
例えば以下のような譜面です。

実はBMSの仕様では同一の小節とチャンネルのデータは複数定義することができ、これらを合成したデータをゲーム内で使用します。つまりn分音符だけのデータを1行、連符だけのデータを1行、それぞれ2行書いて1つのデータに合成します。
ただし、合成するのはゲーム本体側でやらなければならないので、このようなデータにも対応しなければなりません。
この場合は以下のように、同じチャンネル同じ小節に2つのデータを定義します。

#00111:010101000000010101000000
#00111:0000010100000101

ちなみに頑張ればこれを1行で済ますことも出来ます。
それは3連符と八分音符との最小公倍数を求める方法です。

まず3連符は1小節あたり12個分あることになるので12分音符と表現出来ます。
そしてこの12と8の最小公倍数を求めると結果は24となります。
つまり24分音符として配置していけば1行に収めることが出来ます。

以下は24部音符(48文字)で定義した場合の例です。

#00111:010001000100010000010000010001000100010000010000

※赤時は3連符部分、青字は八分音符部分となります

応用

BMSは以上のような構成により、ほぼすべての曲データに対応できるという優れた能力を持っています。

この原理が理解できれば他の音ゲーにも流用することが出来るし、 また解析後のデータを独自のバイナリ形式などで保存すれば、
BMSが使われた形跡を残さないようにすることも出来ます。