「Alcubierre Drive」の譜面ギミック生成プログラムの解説

こんにちは、raiiです。

BOFU2017では「Alcubierre Drive」というBMSを出しました。 その譜面ではプログラムによって生成されたギミックが使われています。 この記事では、ギミックの仕組みや、譜面ギミック生成プログラムでどのようなことをやっているかについて解説します。

Alcubierre Driveのギミックについて

実際にAlcubierre Driveの譜面を再生したものが以下の動画です。

www.youtube.com

使用したギミックとその箇所は以下の通りです。

  • 任意のBPMを表示 (0:06-0:12, 0:35-1:21, 1:44-2:34, 2:37)
    • ランダムなBPMを表示 (0:11, 0:39-1:01, 1:48-2:32)
    • 滑らかにBPM変化させる (0:35, 1:02, 1:19, 1:44, 2:33)
  • 小節線を突然表示させる (0:11, 2:37)
  • 小節線アニメ (0:35, 1:44)
  • 譜面が加速度的に落下する (0:39, 1:48)

これらのギミックは、全て普通に作成されたギミックなしの譜面に プログラムによってギミックを追加しています。

プログラムを作成する際の方針

BMSの譜面ファイルはテキストデータとなっており、 BMSEなどの視覚的な譜面エディタでなくても、 テキストエディタ等でも編集が可能です。 このことを利用し、プログラムで譜面データのテキストを置き換えたり、 テキストを追加したりすることでギミックを作成できます。

BMSファイルの仕様についてはこのページが参考になります。
BMS仕様書(もどき)
このページではWAV定義は36進数で01-FZ、 拡張BPM定義とSTOP定義は16進数で01-FFとありますが、 Lunatic Rave 2ではどれも36進数で01-ZZを使うことができます。

Alcubierre Driveではプログラミング言語としてRubyを使用しましたが、 一般的なプログラミング言語ならどのような言語でも作ることができるでしょう。

ギミックの作成方法

ギミックを作成する際の基本的な事項はこれらの記事が 参考になると思います。

プログラムでギミックを追加する場合は、 基本的に譜面ファイルの最後に追記するといった形で追加しています。

以下ではそれぞれのギミックの作り方を説明します。 実際の譜面データの例では省略部は...で示しています。

任意のBPMを表示

Lunatic Rave 2ではBPMが1000を超えている場合、 BPM表示ではその下3桁を表示します。 このギミックはそれを利用し、 BPMを実際の10000倍である1740000付近にして、 その下3桁を任意の数にすることで実現しています。 そのままでは譜面が10000倍の速さで流れるので、 その間に細かくストップシーケンスを入れることで 通常と同じ速さで譜面が流れるようにしています。

表示するBPMは拡張BPM定義を利用しています。 拡張BPM定義では、

#BPM1A 1740159
#BPM1B 1739647
#BPM1C 1740410
#BPM1D 1739923
#BPM1E 1739788
#BPM1F 1740461
#BPM1G 1739765
#BPM1H 1740197
#BPM1I 1740139
#BPM1J 1740044
...

という風に使用するBPM#BPM(定義番号) (BPM値) という形式で定義しておき、 そのBPMを使用する箇所では、

#42908:1A1B1C1D1E1F1G1H1I1J1K1L1M1N1O1P...

という風に対応するBPM定義番号を指定します。 ランダムBPMと滑らかなBPM変化では、 BPM値を拡張BPMに定義し、 それを使用する箇所ではその定義を順番に指定することで 表示するBPMを変えています。 ランダムBPMでは、ランダムなBPM値をプログラムで生成しています。 滑らかなBPM変化は、 指数関数や二次関数を利用してBPM値を生成しています。

その際に必要なストップシーケンスですが、

#STOP01 5000

とSTOP値を定義し、

#42909:01010101010101010101010101010101...

という風にストップシーケンスを置いています。 ここで、STOP値が10000だと停止位置の間隔が広いため 譜面を再生した際にガクガクして見えるので、 それを軽減するために5000にして、 配置数を10000の場合の2倍にしています。

また、小節長によって必要になるBPM変化と ストップシーケンスの配置数が変わります。 そこで、プログラムによってBMSファイルの譜面の各小節長を読み込み、 配置するBPM変化とストップシーケンスの数を求めて、 その数だけ配置しています。

小節線を突然表示させる

ギミック追加前は以下のような譜面です。

f:id:raii_x:20180428021945p:plain

この譜面の*で示された小節部分を以下の譜面のように伸ばします。

f:id:raii_x:20180428021914p:plain

ここで、BPM値を1741000にして、伸ばした分だけを一瞬で通過させることで、 最初は小節線を表示させず、 そこを通過した瞬間に残りの小節線を表示するギミックを作ることができます。 小節線の長さを変えるために、 プログラムで譜面ファイルの小節長定義を書き換えています。 それに加え、BPM変化を置き、 伸ばした部分が一瞬で通過するようにしています。

ここで、BPM値が1741000である理由は、 Lunatic Rave 2の最大BPM表示も最大のBPM値の下3桁が表示されるので、 それを000とするためです。

小節線アニメ

この小節線アニメは、パラパラ漫画の要領で作っています。 つまり、表示する複数のコマを作り、それを順番に表示することで 動いているように見せています。 これは、10000倍のBPM値を指定し、各コマを表示する部分でストップシーケンスを入れることで実現しています。

ギミック追加前の譜面では、アニメを追加する部分は1小節になっています。 そこにアニメを追加するために新しく400小節を挿入する必要があります。 そこでプログラムでは、譜面ファイルの小節番号を見て、 それがアニメより後の小節番号なら その番号に400を足して書き換えるという処理をします。

小節線アニメを作るために、プログラムでは sin関数などを利用して各コマでの5本の小節線の判定ラインからの位置を決め、 それを各小節長とストップシーケンスの位置に変換します。 更に、あるコマを表示しているときにその次のコマが見えないようにするため、 各コマの間の小節長を4と指定して十分長い小節長にします。 ここで、10000倍のBPM値で通過する部分が長くなり、 そのための時間が無視できないほど大きくなるので、 STOP値を少し小さくして調整しています。 また、「任意のBPMを表示」で説明したものと同じように各時間にBPM変化を指定し、 BPMがだんだん上がっていくようにします。

これは、譜面ファイルでは以下のように表現されています。

#02708:70
#03202:4
#03208:71
#03702:4
#03708:72
#04202:4
...
#41208:95
#41702:4
#41708:96
#42202:4
#42208:97
#42702:4
#02709:00000000000000000000000000000000...00000002000000
#02802:0.006451
#02902:0.006451
#03002:0.006451
#03102:0.006451
#03209:00000000000000000000000000000000...02000000000000
#03302:0.012641
#03402:0.012641
#03502:0.012641
#03602:0.012641
...
#41709:00000000000000000000000000000000...02...0000000000
#41802:0.000821
#41902:0.026950
#42002:0.000277
#42102:0.018886
#42209:00000000000000000000000000000000...02...0000000000
#42302:0.002080
#42402:0.010822
#42502:0.003632
#42602:0.007133

最初に、#(小節番号)08:(BPM定義番号)BPM変化を、 #(小節番号)02:4でコマ間の小節長を指定しています。 その後、#(小節番号)09:でストップシーケンスの位置を指定し、 #(小節番号)02:で変換した小節長を指定しています。

譜面が加速度的に落下する

このBMSの小節線アニメは、1つの小節線が5つになり、 それが1本に収束するといったものです。 その後、収束した地点から譜面が始まります。 ここで、収束する演出を見せるためにその地点を プレイ画面内で見える位置にする必要がありますが、 その位置から譜面が始まるため、 プレイヤーが突然出現した譜面に瞬時に反応しなければならないという問題があります。 これを解決するために譜面を加速度的に落下させています。 落下部分は見た目では1拍分の間隔ですが、 時間では2拍分の長さがあります。 こうすることで、収束地点を見せつつ、 プレイヤーが反応するために十分な時間を確保しています。

これは、10000倍のBPM値を指定し、 そこでSTOP値を変化させることによって実現しています。 以下のようなSTOP値定義をプログラムによって生成しています。

#STOP10 97979
#STOP11 40585
#STOP12 31141
#STOP13 26254
#STOP14 23130
#STOP15 20911
...
#STOP3I 5149
#STOP3J 5122
#STOP3K 5093
#STOP3L 5067
#STOP3M 5039
#STOP3N 5014

この値は、各停止地点に到達する時間をSTOP値換算で平方根を使って求め、 その中の隣接する地点の時間の差を取って求めます。 これを以下のようにして配置しています。

#42809:10111213141516171819...3E3F3G3H3I3J3K3L3M3N

終わりに

余力があれば、実際に作成したプログラムのソースコードについても解説するかもしれません。