ピクチャを調べてみた

お金はいらないぞ!

なんか画像関係いっぱいある

 画像関連はめちゃめちゃいっぱいクラスがあるので、今回はとにかく[ピクチャの表示]イベントコマンドなどで使うピクチャに関するものだけ追ってみる。
 あと、ピクチャはマップでもバトルでも表示されますが、マップで表示されるルートを見て行くことにします。

 次のクラスあたりが理解できれば良さそう。

 他にもGraphics といういかにもな感じのクラスがありますが、これは画面に表示される様々な要素を抱えているだけで、挙動的にはさほど重要ではないみたいです。

ImageManager

 これは各種画像ファイルを読み込むのに使う静的クラス。

Game_Screen

 これは画像関連イベントコマンドを実行するためのクラス。
 大域変数 $gameScreen に登録されてセーブデータとして保存される。
 [天候]、[フェード]、[フラッシュ]、[ピクチャ]、[シェイク]、[色調] あと、イベントコマンドにはないけどズーム機能がある。
 …なんでズーム機能はコマンドにないの?! めっちゃ便利じゃんよ!!
 それはそうと、ピクチャを管理してるのは主にここっぽい。

どこで new されているのか?

 画像を表示するには、オブジェクトが作られなきゃいけないわけで、それがどこで行われているか追ってみます。
 と言っても、new オブジェクト名 で検索するだけですけど。

new Game_Picture

 Game_Screen.showPicture() で生成されてますね。
 このメソッドがどういう経路で呼ばれてるかというと、console.log( new Error().stack );Game_Screen.showPicture() に書いてイベントコマンド[ピクチャの表示]を実行、スタックトレースしますと。
 Scene_Map.update()Scene_Map.updateMainMultiply()Scene_Map.updateMain()Game_Map.update()Game_Map.updateInterpreter()Game_Interpreter.update()Game_Interpreter.executeCommand()Game_Interpreter.command231()Game_Screen.showPicture() みたいな経路になってます。
 なげーよ!

 とはいえ、Game_Interpreter.command231() がイベントコマンドの実行なので、実際は即呼ばれてます。
 誰かが作ってる便利なRPGツクールMVコマンド表を見ると231は[ピクチャの表示]です。間違いないですね。

 で、Game_Screen.showPicture() の中を見ると、
 引数をGame_Picture に設定して、生成した Game_PictureGame_Screen._pictures プロパティに放り込まれてます。
 この辺を真似すれば、イベントコマンドを使わずに JavaScript から直接ピクチャを表示できそうです。

 でも、Sprite_Picture の生成とかしてないんですよね。
 じゃあどのタイミングで生成してんの? って話ですよ。

new Sprite_Picture

 同じように検索してみると Spriteset_Base.createPictures() で生成しているのが見つかります。
 そしてやっぱりスタックトレース。

 これはイベントの実行とかしなくても、即表示されました。
Scene_Map.onMapLoaded()Scene_Map.createDisplayObjects()Scene_Map.createSpriteset()new Spriteset_Map()Spriteset_Map.initialize()Spriteset_Base.initialize()Spriteset_Base.createUpperLayer()Spriteset_Base.createPictures()
 …やっぱ長い。

 そして Spriteset_Base.createPictures() が何やってるかというと、Spriteset_Map に最大個数(100) Sprite_Picture を追加してる。
 もしかしたら、作っては消しってやってたらメモリリークが発生する、ということを懸念してなのかな。
 なお、Spriteset_Battle でも100個で計200個作る。
 ま、なんでそんなことしてるのか分からないけど、とにかく生成箇所は分かった。

update() を追っていこう

 画像オブジェクトの生成が分かったところで、表示や値の変更の動きを見てみましょう。

Game_Picture の update()

 以前の記事をお読みの方なら、もうお分かりかと思いますが、SceneManager からアップデートを追っていきます。
 途中端折りまして、Scene_Map.updateMain()Game_Screen.update()Game_Screen.updatePictures()Game_Picture.update() と呼び出されていきます。
 そして Game_Picture.update() では、Game_Picture.updateMove()Game_Picture.updateTone()Game_Picture.updateRotation() が呼ばれて、それぞれの数値のアップデートが行われます。
 Game_Picture.update() で書き換えやってるな、とアタリをつけてスタックトレースでもいいですよ。

Sprite_Picture の update()

 Sprite_Picture の方で、Game_Picture の値を参照して画像の描画が行われるので、こちらのupdateも追ってみます。
 Scene_Map.uadate() から親クラスの Scene_Base.update()Scene_Base.updateChildren() で、自身が包含している childlen に登録されているオブジェクトをアップデートしてます。
 Scene_Map の場合その中に、Spriteset_Map も含まれています。
 Spriteset_Map.update() は親クラスへと辿っていき Sprite.update() でやはり自身が包含している childlen に登録されているオブジェクトをアップデートしてます。
 で、やっと Sprite_Picture.update() が呼ばれます。

 Sprite_Picture.update() の中では大量のアップデート系メソッドが呼ばれていて、アップデート祭りです。
 次のメソッド群は表示している時だけ呼ばれます。
 Sprite_Picture.updateOrigin()Sprite_Picture.updatePosition()Sprite_Picture.updateScale()Sprite_Picture.updateTone()Sprite_Picture.updateOther()
 だいたいメソッド名で内容は想像つきますね。

 そして常に Sprite_Picture.updateBitmap() が呼ばれます。
 Sprite_Picture.picture() で取得する Game_Picture が持っているGame_Picture.name()で取ってファイル名をチェックして、今までのと変わっていたらSprite_Picture.loadBitmap() して画像を設定。
  Game_Picture がそもそもなかったら、非表示にする。

 チュー感じで、Game_Picture の値がどっかで変更され、update() でそれを見て(ポーリング計100) Sprite_Picture を書き換えるという感じの動作になってます。
 こういう仕組みのおかげで、1フレーム中に複数回の値の書き換えがあっても、画像の描き替えが発生するのは1回で済むようになってます。
 ただし、ピクチャの設定があろうとなかろうと、100個分のループするのすごい無駄な感じする。

画像読み込み

 Sprite_Picture.loadBitmap() の中を見てみると、this.bitmap = ImageManager.loadPicture( this._pictureName ); とだけあって、メソッドにするほどの事か? と思ったりしますが、オブジェクト間で同じような機能を持ったメソッド名を揃えておくと、何かと見通しがよくなります。
 それは前に書いた update() で証明されてると思います。

 さて ImageManager.loadPicture() の中を見て行きますと return this.loadBitmap( 'img/pictures/', filename, hue, true ); とだけあります。
 あーっ! また一行かよ! っていうのとパスの部分だけ変えたメソッドが大量にあるのが、個人的には好みではありません。
 どっかに定数を定義しておいて、例えば loadBitmap( ImgPath.PICTURES, filename, hue, true ) みたいに書ける方が好みです。このパスって他でも使い回す可能性あるんで、いわゆるハードコーディングを避けられる。
 この場合は文字列なので、マジックナンバー問題はあんまりないですけど。

 閑話休題、 ImageManager.loadBitmap() です。
 これパスを書けるということは、指定のフォルダにこだわらず自由に配置できちゃうってことですね。
 アップロード・デプロイ時にオプションで[未使用ファイルを含まない]を選択した時に消えちゃいそうな気がしますが、試してないてす。

 ともかく、これ使えば Sprite_Picture 使わなくても画像配置できるってことですね。
 セーブ後に現状復帰の必要がない(セーブ時に画面にない)画像なら、むしろ Sprite_Picture 使わない方が良さそうです。
 多分、顔イメージとかそうですよね。コード見て見ましょう。

顔イメージ

 顔イメージは 'img/faces/' フォルダにあるので、この文字列で検索してみます。
 てゆーか、すぐ近くにImageManager.loadFace()ってありますね。検索するまでもありませんでした。
 Window_Base.drawFace() で使われて、読み込んだ bitmapthis.contents.blt( bitmap, sx, sy, sw, sh, dx, dy ); という形で使われてます。
 Bitmap.blt()ってのは画像の一部を切り取って別の画像に貼り付けるメソッドです。
 これで見事ウィンドウの画像にペタッと貼り付けられたわけですね。
 Window_Base.contents の描画はまだ調べてなですが、ウィンドウの方がいい感じにやってくれるはず。

 しかしこれ、静止画の場合はいいんですけど、アニメーションさせたいという場合は毎回画像転送するのか、というか転送前に前描いた部分を消去しないとずっと残るんでは? てゆーか消したらもともとそこにあった他の画像も消えるのでは? みたいな心配が必要になりますね。
 やはりSprite_Pictureが必要ってことでしょうか?

Sprite

 Sprite_Picture の親をたどって行くと Sprite ってのが現れます。
 そこにはPRGツクールMVでは、ImageManagerで画像ファイルから読み込んだBitmapを、コンストラクタ引数に指定してSpriteを生成し、StageなどのコンテナオブジェクトにaddChildする、という手順で画像を表示する。って書いてあります(自作自演がひどい)
 addChild() で追加しているので、前記した updateChildlen() でアップデートもされます。
 この方法で行けば重ね合わせなど、いい感じに処理してくれそうです。

 追加する対象は WindowSpriteset_Base あたりになるのかな。
 まだウィンドウ関連を調べてないんで使い分けがピンと来ませんが、Spriteset_Base なら前に出てきた Spriteset_Base.createUpperLayer() メソッドあたりに追記しておけば良さげです。

APNG!!

 ところで2019年末。突然革命がおきまして、ピクチャでAPNG(アニメーションするPNG)を再生できるプラグインがでました。
 トリアコンタンさんの作った、APNGピクチャプラグイン(ApngPicture.js) です。
 サトヤガンジさんが解説動画【ツクールMV】ApngPicture.jsのトリセツ☆Apngアニメーションを表示するまでの全手順 - YouTubeを作ってるので、導入も安心ですね。

 そこで結論。

画像は意外と簡単! ツクール偉い! PIXI.js すごく偉い!!