ITGギミック作成記4.5: Update形式、解説

一通りStepManiaもしくはITGの譜面が作れるようになって、ちょっとギミックを使って演出を強化したりプレイヤーの邪魔をしたりしたいな、という方向けにいくつか記事を書いていこうと思います。第4.5回です。

今回は、前回天下り式に示した謎の文字列の数々について実際にどういう動作をさせているかを簡単に説明していきます。

・おまじない

f:id:paraphrohn:20190625213130p:plain

ITGの仕様として、「何も (self系) 動作を行っていないxmlの動作を停止する」というものがあります。たとえそれが動画再生中であろうと移動中であろうとです。なので、このおまじないを忘れるとこういうことが起こったりします。

 self:sleep(9999)をself:sleep(16)にした例です。fgを設定した-16拍目から数えて16秒目でxmlの実行が止まり、それ以降のフレン・シーフォ(fg)が表示されなくなっています。

たとえコード自体が動いていても、selfが全部終わったら終わりなのでご注意を。

 

・ハイスピ取得

f:id:paraphrohn:20190625212033p:plain

for i = 1, 32 do

iを1から32まで1ずつ増加させながら、endまでの動作を繰り返します。

例えば、

sum = 0;

for i = 1, 10 do

sum = sum + i;

end

ならばsumは1から10までの数字を全部足した数字、55になります。

で、今回何をやっているかと言えば……

 

GAMESTATE:PlayerIsUsingModifier(playernumber, modifier)

playernumber(0なら1P、1なら2P)のプレイヤーが、modifierのオプションを使っているならばtrue、使っていないならばfalseを返します。こういったtrueもしくはfalseとなるものはbooleanと呼びます。

今回は上の段では1P側がi*0.25倍のxmodを使っている場合にtrue、そうでない場合にfalseを返すようになっています。下の段では2Pで同じ操作を行います。

 

if (boolean) then

booleanがtrueの場合にのみ、thenからendの間までの処理を行います。すなわち、i*0.25倍のxmodを使っている場合に、1Pもしくは2Pのハイスピ(hispeedPx)に、i*0.25を代入することになります。

 

で、これらGAMESTATEifの動作をiを1から32まで増加させながら繰り返しますので……結果としては各プレイヤーが使っているハイスピを0.25刻みで0.25から8.0まで調べて、一致する場合にhispeedPxに代入してくれるわけです。

なので、プレイヤーがxmodを使っていない場合(cmod、mmod)やハイスピが0.25刻みじゃない場合(特にSimplyLove)にはbooleanが常にfalseとなり、hispeedPxへの代入は行われません。その場合、for文の手前に書いたhispeedPx = 0がそのまま残るので強制ハイスピ0状態になります。

 

・UpdateCommand

f:id:paraphrohn:20190625214521p:plain

for m, mod in ipairs(mods) do

これはクソめんどくさい筆者もよくわかってない部分です。いわゆるジェネリックfor文と呼ばれるものでして、配列({a,b,c,d,e}みたいなもの)に対して使用します。

解釈としては

for m = 1, (配列modsの要素数) do

mod = mods[m]

end

のようなものです。

ここで、mods[m]はmodsという配列のm番目の要素を表します。すなわち

hairetsu = {a,b,c,d,e} のとき hairetsu[1] = a

hairetsu2 = {{a,A},{b,B},{c,C}} のとき hairetsu2[2] = {b,B}

となります。

なので、for m, mod in ipairs(mods) doは

・mを1からmodsの要素数まで1ずつ増やしながら

・modを、modsのm番目の要素と定義して

・forからendを繰り返す

という形になります。

 

if (beat >= mod[1] and last_beat < mod[1]) then

modは上のジェネリックfor文で定義したので、mod = mods[m]です。

ここで、modsは「{拍数, 文字列}という配列」を配列化したものですので、modは{拍数, 文字列}という配列になります。

mods = {

{4.00, 'a'},

{8.00, 'b'},

}

ならば、m=1のとき mod = {4.00, 'a'}、m=2のとき mod = {8.00, 'b'} となります。つまりmod[1]は指定した拍数を表します。

で、beatは現在の拍数、last_beatは前フレームでの拍数なので、前フレームから今フレームの間に指定した拍数が存在した場合に命令を下すことになります。

 

last_beatの必要性

f:id:paraphrohn:20190623202031p:plain

例えば {0.00, 'HSChange1'} を実行するとき、0.000拍目前後での動きを考えます。拍数取得(1)の時はbeat = -0.02くらい、拍数取得(2)の時はbeat = 0.02くらいになっている図です。

もし、last_beatを使わずに

if (beat == mod[1]) then

という条件分岐をしたとすると、この場合はbeat = 0.00になる瞬間がないので分岐することができません。運よく、0.000拍目に拍数を取得できた場合にのみ分岐するという確実性もクソもないxmlになってしまいます。

一方で、last_beatを使えば拍数取得(2)の時にlast_beat = -0.02くらいになります。この場合、last_beatとbeatの間に0.00にあるので分岐できます。

拍数取得(3)の時にはlast_beatが拍数取得(2)の位置に移動しているので、理論上は漏らしが起こらないコードになります。もっとも、動作がめちゃくちゃ重くなると何故か漏らしが起こりますが……

ともあれ、UpdateCommand形式でも常に拍数を読み続けているわけではないというのは頭の片隅に入れておくとよいです。self:sleep()の秒数だけ、命令が下されるタイミングが前後する可能性があるわけです。

上のフレン・シーフォ消失事件では0.25拍ずつにfgに命令を出していますが、微妙に等間隔になっていません。これはおそらくこのタイミングの前後に由来するものでしょう……

ちなみに、self:sleep(0.0625/GAMESTATE:GetCurBPS());とすると、64分ごとにUpdateを下すようになります。この場合、同一プレー内におけるタイミングの前後をそれなりに抑制することができますので、もし細かいズレが気になるようだったら試してみてください。

 

break

for文から抜け出します。それだけ。

 

 

というわけで今回は特に新しいことをせず前回書いたxmlの中身を説明する回になりました。これだけ把握できていれば、もうどんなxmlも書き放題なので好き放題できるようになります。もし興味がある方は頑張って理解してみてください。

次回こそ、新しいxmlを書こうと思います。具体的にはChase Me D23の落ちサビを作る予定です。self:とGAMESTATE:の違いについてと、PIUによくある逆流を簡単に書く方法です。