一通りStepManiaもしくはITGの譜面が作れるようになって、ちょっとギミックを使って演出を強化したりプレイヤーの邪魔をしたりしたいな、という方向けにいくつか記事を書いていこうと思います。第11回です。
前回はself:とはなんぞやということについて紹介し、(変数名) = self; とすることで他のレイヤーからも命令を出せるというお話をしました。今回はさらに発展させて、今度こそ「レイヤー」とはなんなのかについて紹介します。
さて、xmlを書く時に、大体の場合ははじめに<ActorFrame><children>、最後に</children></ActorFrame>というものを書きます。これは何なのか、文字面通りに受け取れば、Actorのフレーム、その子供たち、という意味になります。
はい簡単に言い換えます。ActorFrameは「おもちゃ箱」、childrenはその中に入っている「おもちゃ」、そしておもちゃとはすなわちActor = 「レイヤー」と呼んでいたものです。
default.xmlの一番上にActorFrameを書いて、その後レイヤーを3つ用意した場合のdefault.xmlの中身の図解です。中に入っているActorは、これまでレイヤーと呼んでいたものと同じです。このように、複数のActorを一つにまとめる役割を担っているのが<ActorFrame>であるわけです。そしてActorFrame自体もまたActorの種類の一つである*1が故に、以前紹介したようにここにInitCommandを書き込むことができるのです*2。
長くなりました。
・これまで「レイヤー」と読んでいたものは、ITG的にはActorと呼ぶ
・ActorFrameは複数のActorを1つにまとめる役割を持つActorである
・ActorFrameの中にあるActorは、そのActorFrameのchildrenである
ということです。
さて、ActorFrameもActorであるのならば、ActorFrameの中にActorFrameを入れることも可能です。これは、前々回くらいに紹介した「外部xmlを読み込む」行為にあたります。すなわち
ActorFrameの中にActorFrameを入れる、おもちゃ箱の中に小さい箱を入れて整理整頓するような入れ子構造を作っていたわけです。この時、青矢印で示したActorは、paraph_HS.xmlのchildrenであり、default.xml側のActorFrameのchildrenのchildrenとなっています。
ちょっとActorが多すぎて意味分からなくなってきたので名前をつけてみましょう。各Actorには
<Layer Name="namae" Type="Quad"
というように、Name=""の形で名前をつけられます。
筆者は大体こんな感じの汚い名前をつけてしまいます。なんてこった
この時、
・default の children はgomi, kasu, face, paraph_HS
・paraph_HS の children は kuso
という関係です。
ここで、Actor "default"内で、 Target_Actor = self;としたとしましょう。そうすると、Target_Actorはdefaultを指すことになります。
ところでITG luaにはchildrenを取得する関数GetChild()があります。これは、
(Actor名):GetChild('children名')
という形で、Actorに対して命令を出すと、childrtenのActorを指すものとして取り扱えます。いや意味不明ですね。
具体例を出しますと
test = Target_Actor:GetChild('face');
としましょう。 Target_ActorはActorFrameを指しますので、その中にあるchildrenであるfaceを取得すれば、つまりRobot Battle D23マンの画像を指しています。
なので、この状態で
test:addx(120);
とすればRobot Battle D23マンの顔を右に120pixelズラすことができます。
また、別の書き方として
Target_Actor:GetChild('face'):addx(120);
ということもできます。Target_Actor:GetChild('face')はtestに等しいので、この書き方でも動作としてはtest:addx(120)とおなじ、つまりRobot Battle D23マンの顔が右に120pixelずれる結果になります。
というわけで、前回紹介していた (変数名) = self; というのは、変数にActorを代入していたということです*3。
ここから話が少し変わります。
ところでITGのスキン自体もActorFrameの群れで構成されています。ハイスピ取得の時に少しだけ述べましたが、実際にFGとしてのluaが動く場面はScreenGameplayというスクリーン中なのですが、このスクリーン自体は
こーんな感じになっています(本当はもっといろんなActorがいろんなところに入っています)。ちなみにスクリーンも(おそらく)Actorで、これ自体は
SCREENMAN:GetTopScreen()
で取得できます。
ということは、上で"test"として取得したRobot Battle D23マンの顔ですが、
SCREENMAN:GetTopScreen():GetChild('SongForeground'):GetChild('default'):GetChild('face')
で取得できるということです!!!!!
は?
まあ (変数名) = self; が使用できますので、わざわざスクリーンから取得する必要ないんですが、これ重要なテクニックでして、というのも例えば上の画像で示すように判定文字とかもActorの一つとしてスクリーンから追えるはずなんですよね。で実際、
SCREENMAN:GetTopScreen():GetChild('PlayerP1'):GetChild('Judgment')
とすれば判定文字を格納しているActorFrameを取得できまして、
judgeP1 = SCREENMAN:GetTopScreen():GetChild('PlayerP1'):GetChild('Judgment')
judgeP1:hidden(1)
で1P側の判定文字を消すことができます。
で、以前話したMODS譜面では矢印が画面中央に流れてくるというもの、同じ要領で
SCREENMAN:GetTopScreen():GetChild('PlayerP1'):x(SCREEN_CENTER_X);
と、PlayerP1という名前のActorFrameを画面中央に動かしてやることで実現できます。この時、PlayerP1自体を画面中央に動かすので、その中に入っているJudgmentやComboなども同時に画面中央にまで動きます。
一緒のようにPlayerP2も画面中央に動かしてやれば、2つの判定レーンが画面中央で重なって実質1個に見えて、しかし両プレイヤーが別々の判定をとったりすると判定文字が重なって気持ち悪い、というU SAiD的な挙動が完成します。
クソフマス2019お疲れさまでした。今回の譜面その1です(動画切れてるのでフル版はyoutubeから)
— paraphrohn (@paraphrohn) 2019年12月21日
U SAiD SH12https://t.co/STXg0AdsNk pic.twitter.com/0SXdlBxH4u
後半のレーンが分裂するところも、例えばPlayerP1を下向きに、PlayerP2を上向きに動かしてやれば作れますので、実は意外に簡単な処理だけしていたということがわかると思います。
また話がとっ散らかってしまいました。このActorFrame、children、Actorの関係を理解できると、luaでできることが一気に増えます。ITGのメインストリームとは離れますが、凝ったギミックを作る際には覚えておいて損はない要素なので、是非ものにしてみてください。
*1:これに関してはSM5 luaでの表記を見比べるとわかりやすいです。Def.ActorFrameとDef.Quad。他にもDef.SpriteやDef.ActorMultiVertexなどももちろんActorの一種ですし、LoadActor('')などは露骨にActorを読んでいます
*2:なぜこんなことが必要かというと、一つのxmlは一つのActorしか返すことができないためです。FGとして一つの要素が欲しいのに、複数の要素を返されるとどれを使えばいいかわからなくなってエラーを吐いてしまうわけです。
*3:変数には例えば数値(int, float)や文字列(string)を代入することができますが、そこにActorを代入したというそれだけのことです