ITGギミック作成記4: Chase Me D23の開幕を作る (オプション読み込み、"Update形式"でのオプション変化)

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

前回はTwist of Fate D20の低速を作ったものの、その後元のハイスピードに戻せないという問題がありました。今回はここを解決していきます。

 Chase Me D23

今回はこの譜面の最初の、ハイスピが半分からゆっくり速くなるのを再現します。これはプレイヤーの設定したハイスピードを読み込まないと作れない演出です。

また曲開始直後にハイスピを半分にし、その後速くするという複数の操作を行いますので、これを"Update形式"で指定していこうと思います。

 

ハイスピの読み込み

今回は、前回作ったxmlをもとに改造すると原型が残らないレベルの改造になりますので、新しくxmlを作ります。前回のxmlはdefault_bk.xmlと改名しました。

f:id:paraphrohn:20190623185140p:plain

今回からはかなりプログラミングの要素が入ってきます。できる限りそういった知識が無くてもコピペか丸写しして使える形にしますが、何が起こってるか理解できるといくらでも応用が利かせられるようになります。なので、今回はおおざっぱに流れだけ説明し、次回にどういった動作をしているかの解説を挟もうと思います。

ともあれプログラミングにはおまじないが必須です。

f:id:paraphrohn:20190623185426p:plain

これがおまじないです。簡単に言えば虚無のレイヤーを作って9999秒待機させるだけの命令です。これがあると、曲の途中でxmlが止まったりすることがなくなります。詳しくは次回。

f:id:paraphrohn:20190623185550p:plain

おまじないを壊さないように別のレイヤーでハイスピの読み込みをします。前回と同じく、Quadのレイヤーを生成して隠します。

 

そして、完全に天下り式にハイスピードを読み込む記述を下に示します。

f:id:paraphrohn:20190623210706p:plain

hispeedP1 = 0;

hispeedP2 = 0;

for i = 1, 32 do if (GAMESTATE:PlayerIsUsingModifier(0, tostring(i*0.25)..'x')) then

hispeedP1 = i*0.25;

end

if (GAMESTATE:PlayerIsUsingModifier(1, tostring(i*0.25)..'x')) then

hispeedP2 = i*0.25;

end

end

これでhispeedP1に1P側のハイスピが、hispeedP2に2P側のハイスピが入ります。ただしCmodを使っている場合はハイスピが強制的に0になって実質プレイ不可能になります。それが嫌な場合は12,13行目のhispeedPxに適当に3とか4とか入れてやればそのハイスピでプレイすることになります。

何故これでハイスピが読めるのかは今回は説明しません。無心でコピペしてください。使えればいいのです。

 

追記: この手法ですが、難易度がBeginnerの場合動かなくなることがあります (merciful beginnerがONだと間違いなく動かないです) 。できればEasy以上の難易度に設定してください。

 

"Update形式"の実装

さて前回から何度も言っている"Update形式"を実装します。Update形式を簡単に説明すると、

・フレームごとに現在の拍数を確認し

・特定の拍数に達したら命令を下す

形式です。まずはUpdateコマンドを実行させましょう。

f:id:paraphrohn:20190623210922p:plain

UpdateCommandの書き方は前回のTwistofFateCommandと同じです。ちなみにself:sleep(1)を入れておかないと同時に色々読み込みすぎて落ちたりします。

これにフレームごとに現在の拍数を確認する機能を付けます。

f:id:paraphrohn:20190623200822p:plain

正確に言えばフレームごとではないんですが、0.02秒ごとに"beat"として現在の拍数を読み込むようにしました。まあ無限ループ構文なので逮捕されるかもしれませんが、実際には曲が終われば実行が止まるので多分大丈夫です。

 

さらにここに、特定の拍数を過ぎたら命令を下すように追加します。

f:id:paraphrohn:20190623210505p:plain

試しに、0.01拍目を過ぎたら (=曲開始時) ハイスピが半分になるようにしました*1。if文での分岐に加えてlast_beatとかいう概念が増えてます。last_beatが何なのかなぜ必要なのかは次回説明しますのでとりあえずそういうものだと思ってください。

重要なのは('mod, *10000 '..tostring(hispeedP1*0.5)..'x', 1);の方です。

今回のように、ハイスピの値として直接数字を入力するのではなく数式や変数を使いたい場合はこうやって書く必要があります。'が多くて一見わかりにくいですが

'mod, *10000 ' ←文字列

..tostring(hispeedP1*0.5).. ←hispeedP1*0.5の計算結果を、文字列として出力

'x' ←文字列

という組み合わせになっています。ここは詰まる人が多いと思います。何かあれば@paraphrohnまでメッセージを頂けると個別に対応します。そうして頂いた方がお互い楽だと思います。


ちなみにこのままだとエラー吐くので、OnCommandの方にこれを追加します*2

f:id:paraphrohn:20190623202246p:plain

last_beat = -16の1行です。こうすればエラー吐きません*3

 

せっかくなので、10拍目を過ぎたらハイスピが元に戻るようにする命令も追加します。

f:id:paraphrohn:20190623203422p:plain

簡単に追加できました。もしハイスピ変更のタイミングを8拍目にしたかったら

if (beat >= 8 and last_beat < 8) then

に変更すれば簡単に変更できます。これがUpdate形式のいいところです。

 

最後に、.smファイルの#FGCHANGESを弄って、少なくとも0.000にsleepの1秒を加えて-2.000よりも手前にはxmlが読み込まれるようにします。そうしないと、0拍目の命令に間に合わないですので。

f:id:paraphrohn:20190623203603p:plain

適当に-16にしました。これをnotITGで再生してみます。

ざっとこんなもんです。これでChase Meの開幕もバッチリです。

 

ところで今回は命令が2種類しかないので条件をUpdateCommand内に直接書き込みました。ですがこれが10種類20種類と増えてくると目的の条件を探すのも大変です。そういう場合に、条件をもう少し見やすく書く手段があります。それが下です。

f:id:paraphrohn:20190623212634p:plain

f:id:paraphrohn:20190623212649p:plain

見やすいとか言いながら余計縦に長くなってしまったので2枚に分けてます。ですがいじるのは赤枠の中だけで、他は丸コピしてテンプレとして持っておけばOKです。

せっかくなのでテンプレを用意しました。こちらからダウンロードしてください。

さて実際に弄るところです。

・mods = { の赤枠

{(拍数), '(わかりやすい名前)'},

の組で管理します。いくらでも追加してOKです。行末のカンマを忘れるとバグります。

 

・if (mod[2] == の赤枠

(mod[2] == '(分かりやすい名前)') then

(そのタイミングで実行したい命令)

で管理します。今回はハイスピの変更2種類をここに入れています。

 

他の部分が何の処理してるかについては、次回説明するつもりですが、ちょっと筆者自身分かってない部分も多いのでどうなるか微妙です。

この手法のいいところはxml開いたら上の方にババーンとmodかける拍数と何がかかるかが並んでくれるのでウム!ヨシ!ってなるところですね。可読性大事です。まあ動けばそれでいいんですけど、あとからちょっと気になって修正するときとかにちゃちゃっと修正できるのは嬉しいと思います。

ちなみにOne Way Streetの場合はこんな感じです。

f:id:paraphrohn:20190623213311p:plain

分かりやすい名前とは一体。

 

とりあえず今回の記事ではUpdate形式を使って命令を管理する手法について説明しました。実際にどういう考えでどういう処理を差せているのかは次回に記そうと思います。また、次々回にはPIUによくあるハイスピが一瞬0になってノートが逆走しながらハイスピが上がっていくみたいなギミックの作り方を紹介します。使えればいいんだよ!という方はそちらをお待ちください。

*1:beat >= 0で設定すると上手く行きませんでした。おそらく同じタイミングで本体側からApplyGameCommandが出ているためです

*2:last_beatが数字であると定義していないので、数字じゃないものを数字と比較しようとしたとしてエラーを吐きます

*3:last_beatは数字であるという認識が入るためです