やりたいこと
プレイヤーの現在のライフ残量に応じてリアルタイムにBGの画像を伸縮させたい
動画
ダウンロード
説明
ITGをプレイしていると、ゲージ残量に応じてキャラクターの首を伸ばしたくなることがあると思います。
今回はそういったアイデアを実現するluaの実装方法を解説したいと思います。
この書き出しを成立させるために上記の記事を書きました。前半を読んでいただければ、
ゲージ残量に応じてキャラクターの首を伸ばすというアイデアが
決して突飛なものではないということがご理解いただけると思います。
顔の周りで手が回るのは突飛だと思います。
今回の記事の対象は顔の周りで手が回るPapasitoではありませんが、
そちらを目的に記事を見に来られた方も居ると思いますので
ロストワンのPapasitoを貼っておきます。
解説
このluaは大きく分けて3つの要素によって成り立っています。
①プレイヤーのライフを取得するGetCurrentLife()
②レイヤ(画像)の位置を指定するx()、y()
③状態取得の命令①、およびレイヤを操作する命令②を短時間で実行し続けることで
リアルタイムな処理を実現するUpdateCommand及びqueuecommand('Update')
①プレイヤーのライフを取得する
STATSMAN:GetCurStageStats():GetPlayerStageStats(PLAYER):GetCurrentLife()で取得できます。
今回のluaでは1Pのみ、2Pのみ、バーサスのパターンを考慮してGAMESTATE:IsPlayerEnabled(PLAYER)で分岐させて取得しています。
バーサスの際に1Pと2Pのライフ状況によって動きにバリエーションを持たせたかったので、Life1PとLife2Pの変数にそれぞれ格納しています。
②レイヤ(画像)の位置を操作する
x(ピクセル)、y(ピクセル)で画像の表示位置を変更できます。
増分を指定するaddx、addyという命令もあります。
x,yで指定するのは画像の左上ではなく、中心点のx,y座標なので注意が必要です。
③状態取得の命令①、およびレイヤを操作する命令②を短時間で実行し続けることでリアルタイムな処理を実現する
今回解説のメイン部分となります。
第3回までの記事では1回だけ命令を実行するものでしたが、今回実装したいリアルタイムな処理では、短時間で何度もパラメータを変えて同じ命令を実行する必要があります。
これを実現するためによく使われる手法が、UpdateCommand及びqueuecommand('Update')による「無限ループ」です。
最も単純な構造としては以下です。
<Layer Type="Quad" OnCommand="%function(self) self:queuecommand('Update'); end" UpdateCommand="%function(self) <<命令の記述>> self:sleep(0.02); self:queuecommand('Update'); end" />
Luaでは最初に実行されるInitCommand、通常のタイミングで実行されるOnCommandのほかに、○○Commandとユーザ自身が名前を付け、そのコマンドをqueuecommand(○○)で呼び出すことができる機能があります。
上記ではUpdateCommandとしていますが、この名称である必要はなく、Command名とqueuecommandで呼び出す名称が合っていればなんでもよいです。
UpdateCommand内でqueuecommand('Update')を行うことで「無限ループ」を発生させ、<<命令の記述>>の部分を繰り返し実行することができます。
今回のLuaの全体構造としては下記のようになっています。
グローバル変数はLua全体で参照可能であることを利用して、ライフ取得及び変数を計算するレイヤを1つにまとめています。
◆変数用Quad <Layer Type="Quad" InitCommand="%function(self) <<変数の初期化>> end" OnCommand="%function(self) self:queuecommand('Update'); end" UpdateCommand="%function(self) <<①ライフの取得>> <<ライフに基づいた位置変数の計算>> self:sleep(0.02); self:queuecommand('Update'); end" /> ◆画像レイヤ(複数) <Layer File="画像名" OnCommand="%function(self) self:queuecommand('Update'); end" UpdateCommand="%function(self) <<②画像位置の変更>> self:sleep(0.02); self:queuecommand('Update'); end" />
今回のLuaだと、1Pのライフに応じて首を伸縮させ、2Pのライフに応じて手を伸縮させています。
また、両者のライフが9割以上の場合は振動するようにし、ライフ差が大きい場合はライフの高い方に体全体が移動するような効果を入れています。
応用
今回の記事で解説した内容は非常に応用範囲が広く、プレイヤーのプレイ内容に応じた
インタラクティブな処理を実現する際に特に役立ちます。
例えば、
・コンボを繋いでいくと譜面が見えにくくなっていく
・地雷ノーツを踏むと画像レイヤが七色に光り回転しながら爆音が流れる
・パネルを踏むと自機が移動する(どのパネルを踏んでいるかはSM5限定の命令)
・ライフがデンジャーに突入するとドカベンが応援してくれる
などです。
端的に言えば〇〇(条件)すると△△(結果)する、で表せる表現は
単純化すればこの手法で実現できます。
「フレン・シーフォ or notフレン・シーフォクイズを間違えるとCROSS†OVERに分岐するShiny World」などです。
(フレンシーフォクイズは他の様々なテクニックの応用の結果なので、これだけ身に着ければ実装できるというものでもないですが)
https://twitter.com/paraphrohn/status/1140211559039324160
また、①のライフ取得をコンボ取得に変更し、位置変数の計算を弾幕の軌道となるようにし、画像レイヤをたくさん用意したものがフメンタイン2020で提出した怒首領Papasito大往生デスレーベルとなります。
余談ですが、怒首領Papasito大往生デスレーベルのように画像レイヤが非常に多い場合は、それぞれの画像レイヤでUpdateCommmadを回し続ける必要があるためLuaが長大になりがちですが、SM5限定の記法にはなりますがparaphさんのブログでより簡潔に記述する方法が紹介されています。
終わりに
Lua講座も第4回まで来ました。構想としては全5回を予定しており、次回(最終回)は右腕が2重振り子状に運動する火花散らしてについて解説したいと考えています。
参考URL
Lua API for OpenITG / NotITG(英語)
https://sm.heysora.net/doc/
ITGのLuaを書くのにまず参照するページです。OpenITGで使用できる各種命令が記載されています。
Lua for StepMania 5
https://quietly-turning.github.io/Lua-For-SM5/
StepMania5版のlua解説サイト(英語)です。使用できる関数や3.9系との違いなど、SM5系のluaを作成するのに必要な情報が詰まっています。
paraphrohn’s diary
https://paraphrohn.hateblo.jp/
日本語でLuaの解説が書かれた貴重なページです。Luaの基本から譜面のワープ、ハイスピ変更ギミックなど様々な実装についての解説があります。