[ITG] Lua講座(第2回) 難易度に応じてジャケットを変える – S.O.M.E.D.A.Y

f:id:kai_yuki:20200322231923p:plain

やりたいこと

難易度別に用意したジャケットを曲開始時に表示させたい

動画

(シングル)



(バーサス)

(ダブル)

ダウンロード


Lua

説明

EZ2ACでは難易度によりジャケットが変わるのですが、これをITGでも実現したいと思いました。
(参考:https://twitter.com/kai_yuki/status/1220938945397051392 )
今回の解説に使用する譜面はDJMAXのS.O.M.E.D.A.Yです。
軍に召集され連絡も取れなくなり、もう居なくなってしまったはずの彼氏と突然に駅で再会するというストーリーです。
(namu wiki:https://namu.wiki/w/Someday )
S.O.M.E.D.A.Yのシングルは3譜面用意していて、下からそれぞれNORMAL END、BOY’S SIDE、TRUE END(GIRL’S SIDE)をイメージしています。
軍に行ったはずの彼が駅員の恰好で目の前に居る。
赤紙まで用意して軍に行くと言っていたのが別れるための口実で実は嘘だったのが突然わかってしまった。
バッタリ目が合ってしまった後の数秒の緊張感、扉が開くと共に隔てるものが無くなり衝動として開放される瞬間、そういったものを表現したいと思いました。


Luaですが、1つ目のレイヤで1P/2Pの難易度を取得、2~4つ目のレイヤでジャケットを曲開始時に表示、5つ目でMVを表示しています。


GAMESTATE:GetCurrentSteps(PLAYER_1):GetDifficulty()で1P側の難易度が取得できます(2Pも同様)。
Beginner(Novice):0、Easy:1、Medium:2、Hard:3、Challenge(Expert):4、Edit:5です。
1P側でプレイしている際に2P側の難易度を取得するとエラーになるので、取得しない(-1を入れる)ようにしています。
また、バーサスプレイの場合は難易度が高い方を優先します。
ダブルの場合の難易度取得についてはシングルの場合と変わりません。
難易度取得の実装はいろいろ考えられるのですが、シングル/ダブル/バーサスを考慮し、かつバーサスプレイの場合は難易度が高い方を優先するのであればこのような実装が良いのではないかと思います。


ジャケットのレイヤは曲開始時に表示したあと、self:diffusealpha(0)で透明にしています。
逆にMVのレイヤは開始時に透明&停止にしていますが、一定時間経過後にジャケットと入れ替わりに表示&再生しています。


ITGのLua全般で言えることですが、あるレイヤで宣言した変数はLua全体で参照できます(レイヤに閉じない)。
そのため、気づかずに複数のレイヤで同じ変数を参照してしまい、変な挙動をする場合があります。エラーも出ないので気づきにくいミスの1つです。
これを避けるために変数にlocalを付けるという方法もありますが、localを付けた変数のスコープは非常に狭い(レイヤーではなくCommandに閉じる)ため、これはこれで気づきにくいミスに繋がることがあります。


また、今回は計算が単純なため問題ありませんが、Conditionの判定が行われるのはかなり早いタイミングのため、①あるレイヤで計算結果を変数に代入する ②別のレイヤのCondition文でその変数を参照する の処理が場合によっては②が先に実行され、意図しない動作をする場合があります。
これもLuaの記述としては問題なく見えるためデバッグの難しいミスです。


今回は曲の最初にジャケットを表示していますが、曲のあるタイミングで条件を満たした場合のみに何らかの動作をさせる(例えば、曲終盤のスコアレートに応じて演出が分岐するなど)という場合にはConditionを使わずif文で分岐させた方がよいでしょう。曲の特定のタイミングで判定を行う、の実装(updatecommandを使ったループ)については参考URLのparaphさんのブログに記載(https://paraphrohn.hateblo.jp/entry/2019/06/23/214120)があります。

詳細

self:stretchto(SCREEN_LEFT,SCREEN_TOP,SCREEN_RIGHT,SCREEN_BOTTOM)
self:diffusealpha(1)
self:animate(1)
 前回の記事を参照下さい。


PLAYER_1、PLAYER_2:OpenITG内で定義されているグローバル変数です。
 PLAYER_1=0、PLAYER_2=1です。そのためGAMESTATE:GetCurrentSteps(0):GetDifficulty()としても動きます。
Stretchto命令に使うSCREEN_LEFTなども同様です。


GAMESTATE:IsPlayerEnabled(PLAYER_1):
 カッコ内のプレイヤーがプレイしていれば1、そうでなければ0を返します。
今回のようにGAMESTATE:GetCurrentSteps(PLAYER_1):GetDifficulty()などと組み合わせるのに便利な関数です。


self:sleep(2.206):
 カッコ内の秒数だけLuaの動作を停止します。今回のように一定時間レイヤを表示させるのに使う用途や、sleep(9999)としてLuaの実行を停止しないようにする(fg側で使うテクニックの1つ)ほか、別の記事で解説するUpdateCommandのループでタイミングを調整するのにも使います。

参考

駅で突然彼に会ったと思ったがそれは彼によく似た駅員だったという説
(駅員のAさん:https://twitter.com/kai_yuki/status/1068704188236890112 )
もあるのですが、作者のコメントや設定集から考えるとやはりMV前半の彼氏と後半の駅員は同一人物であるようです。
また、駅員服に見えるのは軍服であり、任期を終えて帰ってきて再会したと見ることもできますが、それだと何もせず見送るのがつじつまが合わないかなという気がします。

参考URL

Lua API for OpenITG / NotITG(英語)
https://sm.heysora.net/doc/
ITGのLuaを書くのにまず参照するページです。
OpenITGで使用できる各種命令が記載されています。

paraphrohn’s diary
https://paraphrohn.hateblo.jp/
日本語でLuaの解説が書かれた貴重なページです。
Luaの基本から譜面のワープ、ハイスピ変更ギミックなど様々な実装についての解説があります。