ITGギミック制作記5: Chase Me D23の落ちサビを作る (連続的なオプション変化)

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

今回は、前回作成したChase Meもどきをもう少し発展させて、連続的にオプションを変化させてChase Meの落ちサビもどきを作ります。

 

今回はこの動画の落ちサビ  (1:43~) にある、譜面逆流+フェイントを作成します。今思えば、このギミックを作るためだけにluaの勉強を始めたはずなのですが思えば変なところにたどり着いてしまった気がします。

 

さてPIUではこの手の譜面逆流は頻出です。これは、一瞬だけハイスピを0かそれに近い値にして、その直後からハイスピをゆっくり元の値にまで戻すことで作られています。こういう挙動の作り方としては、例えば

f:id:paraphrohn:20190704231554p:plain

こういう方法があります。ハイスピを0にする命令を出して0.02秒後に元に戻す命令を出すパターンです。Update形式を使っている場合にも、下のように書くよりは、上のようにまとめてしまった方が良いです。

(↓あまり良くない例: 動かないことが多い)

f:id:paraphrohn:20190704231802p:plain

これはUpdate形式における分解能の問題です。どうせ0.02秒ごとにしか更新しないのなら、上のように書いてしまった方が楽です。

 

ところで、「オプション変更命令を出した次のフレームに命令を出す」というあまりにも使い道の限定されたコマンドがあります。それがDelayedGameCommandです。

f:id:paraphrohn:20190704231949p:plain

DelayedGameCommandを使った例です。この例では、ノートを100%ステルスにした次フレームから、少しだけ時間をかけて徐々にステルスを解除します。これを実行すると、矢印が一瞬白く光るような演出になり、例えば譜面ワープ後の矢印出現を少し自然にできたりします。

これを使えばPIU的な譜面逆流も簡単に書けそうです。上の例をまとめるとこうなります。

f:id:paraphrohn:20190704232239p:plain

これはDelayedGameCommandの致命的な弱点なのですが、プレイヤーごとに命令を出すことができません。なのでこの書き方をすると、バーサスプレイで大変なことになります。どちらかといえばダブル向けの命令です。

ただこれを使えばconflictのビームが簡単に書けたりします。こんな感じです。

f:id:paraphrohn:20190704232419p:plain

Update形式での分解能に引っかからないので、ホールドをかなり短くしても反映されると思います。

 

話が逸れました。ともあれ今回再現する譜面を改めて見てみますと、

次の譜面の1小節前にワープ→28拍停止→BPM200で再開

という挙動になっています。これはなかなか厄介です。なぜ厄介なのか説明します。

 

次の譜面の1小節前にワープ

これは第1回に説明した方法で簡単に作れます。なので大した問題ではないのですが……

 

28拍停止

ここが大問題です。なぜなら、この間ハイスピを変えまくらないといけないのに拍数がずっと一緒なのでUpdateがまるで働かないからです。

 

・BPM200で再開

ここもかなりの問題です。ハイスピをゆっくり上げている間に再開すると矢印が滑らかに動かずカクカクとした動きになるためです。

 

とりあえず譜面を書きましょう。前から使ってる奴のBPMを200に上げて、ついでに末尾に譜面を追加します。

f:id:paraphrohn:20190705211935p:plain

かなり縦長になってしまいましたが……ワープ(赤色)の直後、6小節目頭に8.4秒の停止が入っています。これでChase Me感満載の譜面になりました。

 

次にxmlを弄ります。

f:id:paraphrohn:20190705212028p:plain

6小節目頭にChaseMeを追加しまして、

f:id:paraphrohn:20190705212230p:plain

とりあえず2Pの人のことは考えないこととしてDelayedGameCommandでそれっぽく書いてみます。

 

 うーん、何とも問題が多いですね。フェイントとかがないのは書いてないので当然ですが、やっぱり加速する瞬間に矢印が急に動き出すことと、ハイスピが完全に戻り切っておらず譜面が始まってからも微妙にスクロールが遅いことが現状の課題です。あとやっぱり2P側がどうしようもないというのも気になります。一つずつ解決していきましょう。

 

・フェイントを書く

Chase Meの逆流地帯8.4秒の内訳は(おそらく)以下の通りです。

譜面逆流(遅) 4.05秒

譜面逆流(速) 0.3秒

フェイント 0.15秒

譜面逆流(速) 0.45秒

譜面逆流(遅) 3.45秒

というわけで、これをイノセントに書き下すならば

f:id:paraphrohn:20190705212816p:plain

こんな感じになりそうです。残念ながらこの記法では全く思い通りに綺麗に動きません。

少し面倒な話になりますが、self:sleep()はself:関係のコマンドを休止する命令であって、それ以外の命令、例えばGAMESTATE:や各種演算、条件分岐などの実行には何ら影響を及ぼしません。これがどういうことかと言えば、上の命令が実行されると

1. ハイスピを0xに変更

2. ハイスピを元の1/2に変更

3. ハイスピを元のハイスピに変更

の4つの命令を1フレーム内に行い、結果としてハイスピは元のハイスピに変更=変化しないということになります。

その後、1フレーム置いてDelayedGameCommandが発動してハイスピがゆっくり1/4になります。

さらにその後にself:sleepが発動して、虚無を待ち続けることになります。

というわけでまずはここを直す必要があります。簡単に言えばself:系でハイスピを変更させてやればいいので……queuecommandを使って、別のコマンド内でハイスピを変えてやることが考えられます。

f:id:paraphrohn:20190705213429p:plain

こういう形で、それぞれのChaseMe1Command、ChaseMe2Command・・・でGAMESTATE:ApplyGameCommandを使ってやります。これで順番にオプションが動くようになりました。

各コマンド内の細かな数値、設定については最後にまとめて書きます。

 

・加速する瞬間に矢印が急に動き出す

これの対処法は実は割と簡単です。加速する瞬間にはハイスピを元の半分くらいにしておいて、加速した瞬間からハイスピの変更速度を大きくしながら元のハイスピに戻してやれば比較的スムーズにできます。

BPM変化によるスクロール速度変化をハイスピ変更速度の差で打ち消してやるイメージです。

 

・譜面開始時にハイスピが戻り切っていない

上の手法を使えば解決です。

 

・2P側がどうしようもない

せっかく紹介したところですが、DelayedGameCommandを諦めるしかありません。「フェイントを書く」を参考にすれば、例えばこういう書き方が考えられます。

f:id:paraphrohn:20190705214129p:plain

ChaseMe1Commandで、ゆっくりハイスピを上げていく形にします。0.01秒しか差がないので、まあ1フレームみたいなものです。

 

というわけで、ここまでの対策をまとめて、細かく調整したものを下に載せます。

f:id:paraphrohn:20190705214932p:plain

f:id:paraphrohn:20190705214917p:plain

かなり気持ち悪いですが、これでそれっぽく動きました。

 

  ところで上の動画はハイスピを3.0xにして撮った動画です。これを試しに2.0xにしてみます。

 うーんダサい。あまりにもダサいです。これがどうして起こるのかChaseMe1Commandを例にとって説明してみますと

 

・ハイスピが3.0の場合

3.0 * 0.075 = 0.225から、3.0 * 0.25 = 0.75まで、ハイスピ差0.525を一定の速度(*0.1)で変化

 

・ハイスピが2.0の場合

2.0 * 0.075 = 0.150から、2.0 * 0.25 = 0.5まで、ハイスピ差0.35を一定の速度(*0.1)で変化

 

となり、かかる時間に差があることが分かります*1。なので、0.525変化するには4.04秒以上かかっていたハイスピ変更が、0.35だけ変化するときには4.04秒かからなかったためにダサい速度変化が生まれてしまったというわけです。

これを解決するには、ハイスピの変更速度もハイスピ依存にしてしまうのが一番手っ取り早いです。つまりこういうことです。

f:id:paraphrohn:20190705215656p:plain

かなり書くのがめんどくさくなってきましたが、効果はてきめんで、下のようにバーサスプレイでそれぞれ違うハイスピを選んだとしてもきちんと動きます。

  

というわけで今回の教訓です。

・DelayedGameCommandは1P2Pを指定できない

・self:sleep()とGAMESTATE:は組み合わせられない

・ハイスピ変更時の変更速度はハイスピ依存にするとズレない

これだけ覚えておけば、少なくともオプション変更関係のギミックで困ることは無いと思います。あとは発想次第でどんなものでも作れるかと。

 

ここまで5回+1回にわたって、ワープから始まってFG、ハイスピ固定、変更とある程度のギミック(具体的にはDoppelgangerを除くフメンタイン2019程度のギミック)が作れるくらいのxmlの書き方について説明してきました。

ここから先は作りたいギミックに応じて、以下のサイトで必要な情報だけ抜き取ってくるのが向いているかと思います。

 

例えば、現在の残りゲージに応じた演出を行うことを考えます。

上ページを「Life」で検索すると

f:id:paraphrohn:20190705221207p:plain

こういったものが見つかります*2。このブロックの頭を見るとYou can get an instance of PlayerStageStats by using ITGStageStats.GetPlayerStageStats().と書いてあります。そこでこのGetPlayerStageStatsをクリックしてみると……

f:id:paraphrohn:20190705221411p:plain

ここに飛ばされます。見るとYou can get an instance of StageStats with the StatsManager class.とありまして……すぐ下にStatsManagerの欄があります。

f:id:paraphrohn:20190705221522p:plain

今回は「現在プレイ中の曲におけるステータス」を参照するのでGetCurStageStatsが使えますかね……上側を見ればSTATSMAN singletonを使えって書いてあります。そうすると、ライフの取得は次のようになります。

 

STATSMAN:GetCurStageStats():GetPlayerStageStats(0):GetCurrentLife()

 

長い……ですがこれでちゃんと取得できます。()の中はvoidと書いてあるところは空白で、そうでないところは指示に従って書いていきます。

 

というわけでライフが減れば減るほどハイスピが下がるxmlを書いてみましょう。

f:id:paraphrohn:20190705222204p:plain

今回は他の動作をしないことを前提に、Updateの中身にハイスピ変更を書き込みました。ちなみに致命的な欠点として1ノートも踏んでいない場合にはlifeは0として扱われるため最初の1歩までハイスピ0になるのですが、まあその辺はfgを置くタイミングを適当に変えれば何とかなると思います。

とにかく、ここまで覚えておけば調べものと発想、あとはifとかforとかを適切に使えるだけでまあいろいろ作れるということです。

 

本シリーズは今回で終了になりますが、もし万が一需要があれば、今までに動画を上げてきたギミック譜面についてxmlの中身と解説を書いていこうと思います。見てみたい奴があったら@paraphrohnまでよろしくお願いいたします。それではここまで長らくおつきあいくださりありがとうございました。

*1:一定の速度*0.1については未検証なのですが、おそらく*1だと1秒当たりハイスピ1変わるんじゃないでしょうか? 多分

*2:左側の矢印が赤いものはopenITG対応、緑色のものはnotITG専用です