ウィンドウの下に画像を表示する方法【ツクールMV】

はいどーもこんばんは無職です。
本日はTwitter上で見かけた「ウィンドウの下に画像を表示する方法」について説明してみたいと思います。
かなり詳しめに書いていますが、プログラマ向けの内容です。

ウィンドウの下に画像を表示するとは

「ウィンドウの下に画像を表示する方法ってなんやねん。普通に表示したらええやろが」と思うかもしれませんが、ツクールMVの仕様上これがちょっと面倒なんですね。

このやり方を解説しようと、はい、そういうわけなんですね。
ちなみにTwitter上では「Scene_Menuに追加したい」という感じだったので、そこに追加していこうと思います。

画像を表示するには

とりあえずエネミーのスライムを表示することにします。
こんな感じのコードで、メニュー画面を開くとスライムのスプライトが表示されるようになります。

しかしこの場合、ウィンドウよりも上にスライムが表示されます。

これをウィンドウよりも下に表示したい場合、どうすればよいでしょうか?
こんな感じでいけそうでしょうか?

残念ながら、このコードはうまくいきません。
スライムが完全に消えてしまいます。

Scene_Menu.prototype.create

先ほどのコードではなぜスライムが消えたのかを追っていきます。
重要なのはもちろん

こいつが何をやっているかです。
ちゅーわけでScene_Menu.prototype.createを見てみましょう。
以下のようなコードになっています。

見た感じ、ここでWindowが作成されているように思います。
ということは、Window作成より前にスライムをAddすればウィンドウの下にスライムを表示できるんじゃないでしょうか?

ところがどっこい、そうは問屋がおろさず、このコードもうまくいきません。
スライムはウィンドウの上に表示されてしまいます。
さらに言うなら、このような書き方(関数の完全な上書き)はお行儀がよろしくありません。
よほどのことがない限りはしない方がよいでしょう。

Scene_MenuBase.prototype.create

さて問題は
Scene_MenuBase.prototype.create
にまで遡らないとわからないようです。
次はこれを見ていきましょう。
こんな感じのコードになっています。

おや!

なるものがありますね。
こいつが怪しいです。
これはScene_Baseに定義されていて、こんな感じのコードです。

こいつを理解すれば、スライムをウィンドウの下に表示できそうな気がします。

答え

というわけでまず答えから書きます。
以下のコードをプラグインとして導入すると、ウィンドウの下にスライムを表示することができます。

はいこれだけです。

createSlimeSpriteを除けば、createWindowLayerの処理の前にaddChildしているだけです。
なぜこれでスライムがウィンドウ画像の下に表示されるのかを見ていきます。

Sceneという入れ物

実はSceneもSpriteもWindowも同じ親を持ちます。
すなわちPIXI.DisplayObjectです。
WindowはPIXI.Containerを継承していますし(PIXI.ContainerはPIXI.DisplayObjectを継承している)、SpriteはPIXI.Spriteを継承しています(PIXI.Containerを継承している)。
またScene(Stage)もPIXI.Containerを継承しています。

先ほどのサンプルコードで

としましたが、これはPIXI.ContainerのaddChildを呼んでいます。
このaddChildはPIXI.DisplayObjectを継承しているものなら何でも受け付けます。

これはフォルダとファイルの関係に似ています。
this.addChild(slimeSprite);というコードは、Sceneフォルダの中にスライムファイルを直接追加していることに相当します。
ではWindowもそうなのか? と言えば少し違います。
Window専用のフォルダが作られています。
それがWindowLayerです(これもPIXI.Containerを継承しています)。

Sceneフォルダの中にWindowLayerというフォルダを作成し、各種ウィンドウはWindowLayerフォルダに追加されていくのですね。
ツクールのコアスクリプトでは、WindowをWindowLayerに追加しやすいようにaddWindowという関数が用意されています。

スライムを追加したフォルダ

こちらのコードを再度見てください。

このコードではスライムは完全に消えてしまいました。
その理由は、以下のようなフォルダの構造になっていたからです。

・Scene_Menuフォルダ
 ・スライム画像
 ・背景画像
 ・WindowLayerフォルダ
  ・各種ウィンドウ画像

この場合、スライム画像は背景画像に完全に塗りつぶされてしまっているのです!
そこでやらなければならなかったのは「背景画像よりはあとに表示するが、WindowLayerフォルダよりは手前へ表示」することです。
フォルダ関係で言えばこんな感じ。

・Scene_Menuフォルダ
 ・背景画像
 ・スライム画像
 ・WindowLayerフォルダ
  ・(あるいはここで他のウィンドウよりも早くスライム画像を入れても表示可能)
  ・各種ウィンドウ画像

これをScene_Menu内で完結させるには、createWindowLayerを改造しないと難しいかなと思いました(createの段階では背景画像よりも前か、WindowLayerフォルダよりもあとにしかスライム画像を追加できないため)。

Compositeパターン

今まで「フォルダとファイルの関係に似ている」と言ってきましたが、これを一般的にCompositeパターンと言います(GoFのデザインパターンでググってみてください)。
SceneやWindowの関係を知るのに役立つと思います。
ただ僕はPixiJSに詳しくないので、完全なCompositeパターンになっているかどうかは保証できませんw
似ているとは思いましたw

addChildされたときの描画順

さて改めてaddChildされたときの描画順について見てみましょう。
多くのサイトでは「あとから追加されたものが前面に表示される」と説明しています。
これはこれで正しいのですが、階層の深さについて書かれているものが少ないので混乱している方がいるのかなと思いました。

例えば以下のような構造があったとします。

・Scene_Menuフォルダ
 ・背景画像
 ・スライム画像
 ・WindowLayerフォルダ
  ・各種ウィンドウ画像
 ・ゴブリン画像

このときの表示順は以下のようになります(若い番号が優先的に表示される)。

・Scene_Menuフォルダ……6
 ・背景画像……5
 ・スライム画像……4
 ・WindowLayerフォルダ……3
  ・各種ウィンドウ画像……2
 ・ゴブリン画像……1

Scene_Menu階層にあるフォルダとファイルは、まさに後からaddChildされたものが優先的に表示されています。
そしてWindowLayerフォルダという階層でも、この階層でaddChildされた(addWindowを使うのが一般的)各種ウィンドウ画像がaddChildされたのが後のものほど優先的に表示されます。
ただしこのとき、WindowLayerフォルダはゴブリン画像よりも優先度が低いため、各種ウィンドウ画像はゴブリン画像よりも前に表示されることはありません。
なのでWindowLayerがすでにScene_MenuにaddChildされたあとにいくら他のオブジェクトをScene_MenuにaddChildしたとしても、それはもうWindowよりも手前に表示されてしまうのですね。
逆に言うと、一度WindowLayerを作ってしまえば、あとはそのレイヤーにaddWindowすれば、どのタイミングでWindowを追加したとしても、必ずWindowはゴブリン画像よりも後ろに表示されます。

別解:背景画像フォルダにスライム画像を追加する

先ほどスライム画像をウィンドウの後ろに表示する際、こんな感じの階層を用いました。

・Scene_Menuフォルダ
 ・背景画像
 ・スライム画像
 ・WindowLayerフォルダ
  ・(あるいはここで他のウィンドウよりも早くスライム画像を入れても表示可能)
  ・各種ウィンドウ画像

ですが覚えていますでしょうか。
PIXI.SpriteはPIXI.Containerを継承しています。
これはとどのつまり背景画像にaddChildできることを意味します。
つまり階層としてはこんな感じ。

・Scene_Menuフォルダ
 ・背景画像
  ・スライム画像
 ・WindowLayerフォルダ
  ・(あるいはここで他のウィンドウよりも早くスライム画像を入れても表示可能)
  ・各種ウィンドウ画像

微妙な違いですが、こちらのバージョンのコードも書いてみます。

これでも今回の場合だとWindowの後ろにスライム画像を表示できます。
ただ背景画像に引っ張られるので、あんまりよくないかも。

別解2:描画順を入れ替える

実は描画順を入れ替えるメソッドも用意されています。
詳しい説明は省略しますが、こんな感じ。

これでもスライムはウィンドウの後ろに表示されます。
この場合はcreateメソッドの中に処理を書くことができるので、メソッド名と内容に乖離がなくなるのでスマートですね(初めて見たらなんでswapしてるんだとか思いそうなのがアレですが)。

別解3:Z座標を実装する

2018/08/07に追加

Z座標を実装することでも解決できます。
詳しくは以下の記事をご覧ください。
シーンにZ座標を実装する

addChildは何者か

addChildはPIXI.Containerが持つメソッドです。
このメソッドはPIXI.DisplayObject型を継承しているオブジェクトなら何でも受け取ります。
つまりツクールMVで言えばSpriteでもWindowでも、何ならSceneでも追加できます。
そしてまた、addChildはPIXI.Containerを継承しているオブジェクトならどのオブジェクトでも持っています。
だからこそSpriteでもWindowでもSceneでもaddChildが使えるのです。
つまりSpriteやWindowやSceneはSpriteやWindowやSceneを受け取る入れ物であると同時に、SpriteやWindowやSceneが持つ中身でもある、と言えます。

終わりに

いかがでしたでしょーか。
今回は結構詳しめに「ウィンドウの下に画像を表示する方法」を書いてみました。
あ、プログラミングの家庭教師のお仕事も募集してます。

ほなそんな感じでまた。

フォローする