ITGギミック作成記10: "self"とは

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

 

今回から数記事にわたって、今までとは大幅に趣向を変えて、これまで「レイヤー」と呼んでいたモノが具体的にどういうものなのか、そしてxmlの頭にある<ActorFrame><children>とはいったい何なのか、について紹介します。

ただ紹介するだけですと間違いなく意味不明な何かが羅列されるだけになってしまいますので、具体例を挙げながらなんとかわかりやすく説明していきたいと思います。

今回の内容は簡単に言えばxml書くときに使ってるself:って何なんだ? というお話です。

さて、今まではオプションの取得・変更を中心に、具体的なxmlおよびluaの書き方を紹介してきました。これを使えばPump It Upの通常譜面に登場するようなギミックは粗方書けるようになったはずですが……これを使って例えば青空のラプソディ(Celestial Harbor)のサビを作れるかというと、まあ無理だろうなというのは当然だと思います。何せITGのオプションには「判定レーンを大量発生させてy軸を軸に回転させる」みたいなものはありませんので。

もちろん、拙作Dual-Egoのようにすべてを画像でごまかすとかいうごり押しofごり押しをやれば作れないわけでもないです(マジで作れます)が、これだけのオブジェクト1個1個に命令を書くことを考えるとできれば避けたいです。

 

ここまでのことを考えなくても、U SAiD後半で判定レーンが上下に分かれるシーン、そもそも大半のMODS譜面でレーンが中央に表示される、などはオプションを弄ったりFGで画像を表示させるだけでは実現できないものです。これらを実装するには、まあ天下り式でも全然問題ないんですが……せっかくならばその原理から理解したほうがいろいろ使い勝手が良いのです。

その「原理」のカギになるのがActorFramechildrenLayerの関係です

とはいっても、これをトップダウンで理解するのもなんだか変な感じがあったので……ボトムアップで紹介していきます。まずはself:からです。

 

 

さて、某ドキドキ☆フレン・シーフォの時にこんなことを書きました。

f:id:paraphrohn:20200320221517p:plain

えっあんな3秒足らずの演出のために500行ってアホか?ってなると思います。筆者はなりました。

あるいはフレン・シーフォの顔が動きまくる火花散らしてremixでもこんなことを書いてます。

f:id:paraphrohn:20200320221623p:plain

それUpdateCommandのなかで完結できないんか????

 

はいこれを何とかします。

 

まずxmlを書くときに毎回使っているこの記述について考えます。

OnCommand="%function(self) self:... 

self:とはまあ自分自身を指すわけですが、じゃあ自分って何なんだということです。

例えばこの命令が入っているレイヤーが画像であればその画像ですし、Quadであれば長方形(表示されていないことがほとんど)というわけですが……わざわざ「自分」と指定する意味とは。

素直に解釈すればこれは「自分に対して命令を出している」ということで、例えばself:addx(100)は「自分をx軸方向に100pixel移動」になります。じゃあ自分以外にも命令を出せるのではないか? と考えるのは自然なことです。

 

では、適当なレイヤーから、他のレイヤーに命令を出すときに、どうやってそのレイヤーを指定すればよいのか、その例が下の書き方になります。

f:id:paraphrohn:20200320222911p:plain

Nameの通り、上のLayerが命令を受け取る側、下のLayerが命令を出す側です。順に読んでいきましょう。

 

まずは命令を受けとる側。

InitCommand="%function(self)

      Target_Layer = self;

 Target_Layerは一つの変数で、以前まで使っていたbeatとかlast_beatとかと一緒です。そして、その変数は自分である! という宣言をここでしています。

ここでいう自分とはself:addx(100)とかのself:と全く一緒で、つまりこのように宣言すれば、

self:sleep(1); 

 と

Target_Layer:sleep(1); 

は全く同じ意味を持ちます*1

そしてこのTarget_Layerは別のLayerからも使えます。 

つまり、命令を出す側のLayerにある

Target_Layer:addx(100); 

で、上側のLayerをx方向に100pixel動かすことができるのです。

 

この時、名前はTarget_Layerである必要はなく、例えば gomi = self; と gomi:addx(100) の組み合わせであったり、 Flynn_Scifo = self; と Flynn_Scifo: addx(-960) の組み合わせでも使えます。

 

ではこれを使って実際にフレン・シーフォの顔を動かしてみましょう。

f:id:paraphrohn:20200320225828p:plain

UpdateCommand内で拍数を取得して、フレン・シーフォの画像のxy座標をその値に更新する書き方です。beatは単調増加しますので、時間経過によって顔が右下に動きます。

こうすることで、UpdateCommandごとにMESSAGEMAN:Broadcastして各画像レイヤーでmessageを受け取って更新、とする必要がなくなって、上で赤文字で書いた問題が解決しました。

 

ではこの顔を増やしてみましょう。

f:id:paraphrohn:20200320230318p:plain

安直に増やしました。左上から右下に移動するFlynn_Face1と右上から左下に移動するFlynn_Face2が画面中央でクロスします。

 

では、これを使って怒首領Papasito、あるいはDDFSのハートのような挙動を書くと……?

f:id:paraphrohn:20200320231300p:plain

うわなげえ

 

確かにUpdateCommandの中にまとまりはするのですが、肝心のUpdateCommandがとんでもない長さになってしまいます。せっかくなのでこれも何とかしてみましょう。

 

大量の「似たようなもの」を扱うときに便利なのが配列とfor文です。過去にもmodsなどの形で利用してきていますので、これを今回の レイヤー = self;に適用してみましょう。

 

例えばこういう書き方ができます。

f:id:paraphrohn:20200320231639p:plain

上のレイヤーでflynn_facesという配列を宣言して(これをしないと虚無に値を代入しようとしてバグります)から、画像内で self を flynn_faces の要素として追加しています*2

そうすると、flynn_face.pngを格納するLayerは配列flynn_facesの1番目の要素ですから

下のような形で命令を下せます。

f:id:paraphrohn:20200320232021p:plain

特に変わりありませんね。ここで顔の数を10個くらいに増やしてみましょう。

f:id:paraphrohn:20200320232123p:plain

違う速度で動く顔10個を並べてみました。画像の数が10倍になっているのにUpdateCommandがあまり長くなっていないこと、また下の塊のように画像の追加が1行でも済ませられること(改行していないだけ)なところに注目してみてください。

更にこれを使って、それぞれの顔のx,y座標も配列化してやって、例えば

f:id:paraphrohn:20200320232325p:plain

こういった形で命令してやることで、それぞれの顔に全く違った動きをさせることが可能となります。

 

この形式を使っているのがDual-Egoの矢印表示部分で、

f:id:paraphrohn:20200320232509p:plain

こんな感じで配列tpx及びtpyに保持しておいた値を代入してx,y座標を更新する形になっています。

 

どうでもいいですがPapasito状態だとこんな感じ。

f:id:paraphrohn:20200320232650p:plain

Papasito関数に突っ込んであるだけです。Papasito関数って何?

 

 

話があっちこっちしてしまってなんだかアレな感じになってしまいましたが、

・(変数名) = self;としてやれば他のLayerから命令を出せるようになる

・同じようなLayerが多い場合は、配列を用意してtable.insert(配列, self)とすると扱いやすい

今回はこの2点になります。

次回はこのような「別のLayerに命令を出す」の進化系として、矢印が流れるレーンや画面上の表示にも命令を出す方法を知るためにActorFrameとchildrenの関係について説明する予定です。

*1:これはInitCommandで宣言するのが良いです。なぜなら宣言する前に別レイヤーから命令を出そうとすると虚無に命令が出されてバグるからです。

*2:table.insert(配列名, 要素)は、配列名 の配列の末尾に 要素 を追加する関数です。例えば、alphabets = {a, b, c}だった時に、table.insert(alphabets, d)とすればalphabetsは{a, b, c, d}になります。