ウィンドウ描画の仕組み
ウィンドウを描くWindow_〇〇クラスは全てWindow_Baseクラスを継承しており、Window_BaseはWindowクラスを継承している。WindowクラスはJavaScriptでよく利用するwindowとは別のクラスでありrmmz_core.jsにて定義している。
独自ウィンドウを自作しWindow_〇〇クラスを作成する上でよく関わる継承関連クラスは以下のようになる。
Window | PIXI.Container | ウィンドウの枠や背景、矢印などの部品を管理 |
Window_Base | Window | ウィンドウを描画するための便利メソッドや各ウィンドウの共通値を用意 |
Window_Scrollable | Window_Base | スクロールバーやスクロールに関する処理 |
Window_Selectable | Window_Scrollable | コマンドカーソルの移動や入力判定など |
Window_Command | Window_Selectable | コマンド選択用のウィンドウ |
当記事は主にWindowクラスに関する内容だ。普通にウィンドウを作成する上で利用することは主にないが、ウィンドウをツクールのデフォルトから大きく変えたり場合は内容を知っていると便利だ。
Window(各ウィンドウの親クラスWindow_Baseの親クラス)はpixi.Containerを親クラスとしている。WindowクラスはこのScene_Baseの下にWindowLayerクラスを挟み子要素として配置することで描画を実現している。
以下は、ウィンドウ関連について、シーンからウィンドウを表示するまでに関わるPIXI(描画関連)のツリー構造だ。
さて、まずこの構造を知る利点だが、上の図で表すクラスや変数などは全てPIXIを継承しているため、PIXI関連のメソッドを丸ごと利用できる。
例えばデフォルトでは停止しているウィンドウも次のようにWindow_〇〇クラスのupdateでrotateをいじればすれば回転する。
以下はウィンドウが初期45度、毎フレーム1度回転するプラグインだ。
/*:
* @plugindesc サンプルPlugin
* @target MZ
* @help
* 独自ウィンドウを表示するサンプル
*
* @command openScene
* @text シーン開始
* @desc サンプルシーンの開始。
*
*/
(() => {
const pluginName = document.currentScript.src.match(/^.*\/(.*).js$/)[1];
PluginManager.registerCommand(pluginName, "openScene", () => {
SceneManager.push(Scene_Sample);
});
class Scene_Sample extends Scene_MenuBase {
create() {
super.create();
this.createWindowLayer();
const rect = new Rectangle(200, 100, 400, 200);
this._sampleWindow = new Window_Sample(rect);
this._sampleWindow.activate();
this._sampleWindow.setHandler("ok", this.popScene.bind(this));
this._sampleWindow.setHandler("cancel", this.popScene.bind(this));
this.addWindow(this._sampleWindow);
}
}
class Window_Sample extends Window_Selectable {
_rotate = 45;
initialize(rect) {
super.initialize(rect);
//初期値45度回転
this.rotation = this._rotate * Math.PI / 180;
this.refresh()
}
refresh() {
this.contents.clear();
this.drawText('Hello World', 10, this.lineHeight() * 0, 200);
}
//毎フレーム1度回転
update() {
this._rotate = this._rotate + 1;
this.rotation = this._rotate * Math.PI / 180;
}
}
})();
このようにpixiを継承したクラスはpixiのapiを利用することができる。なお、RPGツクールMZで利用しているpixi.jsはVersion5系なのでAPIのドキュメントはこちらになる。
ウィンドウのpixi構造を詳しくみる。
まず、ウィンドウをシーンから呼ぶ場合、必ず行うおまじない的なメソッドがある。過去の自作ウィンドウ会ではほぼ説明なしで紹介したが、以下の箇所だ。
/*:
* @plugindesc サンプルPlugin
* @target MZ
* @help
* 独自ウィンドウを表示するサンプル
*
* @command openScene
* @text シーン開始
* @desc サンプルシーンの開始。
*
*/
(() => {
const pluginName = document.currentScript.src.match(/^.*\/(.*).js$/)[1];
// Plugin Command
PluginManager.registerCommand(pluginName, "openScene", () => {
SceneManager.push(Scene_SampleMenu);
});
//サンプルウィンドウを管理するシーン
class Scene_SampleMenu extends Scene_MenuBase {
create() {
super.create();
//ウィンドウレイヤーを作成してScene_SampleMenuの子要素とする。
this.createWindowLayer();
const rect = new Rectangle(200, 100, 400, 200);
this._sampleWindow = new Window_Gold(rect);
//ゴールドウィンドウを作成してウィンドウレイヤーの子要素とする。
this.addWindow(this._sampleWindow);
}
}
})();
Scene_BaseはStage継承クラスだ。ウィンドウを作成する前に明示的にWindowLayerを生成し、Scene_Baseの子要素に加える。WindowLayerは実際にウィンドウを描画する処理が書かれたクラスだ。このクラスに上記で記したWindow関連のPIXI設定を加えることで実際に描画する。
さらに使いたいWindow_Base継承のWindow_〇〇クラスをWindowLayerの子要素に加える。具体的な実装はcreateWindowLayerはSceneクラスの子要素にウィンドウレイヤーを加え、addWindowはウィンドウレイヤーの子要素に各ウィンドウクラスを加える。
シーンとウィンドウの中間層
基本的概念は以上だ。次にWindow.jsで書かれている部品関連の内容だ。
まずWindowクラス直下の子要素に_container、_clientArea、_downArrowSprite、_upArrowSprite、_pauseSignSprite、_dimmerSpriteがある。
_dimmerSprite
Windowクラスではなく、Window_Baseに記述があるウィンドウ表示の仕方を制御するスプライト……というよりはエフェクト的な扱いが強いスプライト。ウィンドウとシーンの中間に位置しており、メッセージウィンドウで、イベントコマンド「文章表示」の背景の制御に利用している。
setBackgroundType(number)を使うことで_dimmerSpriteを操作して背景を変更できる。引数が0だとウィンドウ、1だと暗くする、2だと透明になる。具体的には以下のような処理を行っている。
0:ウィンドウ | 1:暗くする | 2:透明 | |
---|---|---|---|
ウィンドウの透明度 | 0 | 255(隠す) | 255(隠す) |
ディマー | 何もしない | ウィンドウ枠に特別なエフェクトをかける | 何もしない |
枠を隠す処理はウィンドウを丸ごと隠しているため、個別の枠のみを切り取りたいみたいなことはこのメソッドではできない。反対に1:暗くするの特別なウィンドウは好きなように改変できるので興味があればこのメソッドそのものを拡張すると面白いものが作れる。
0:ウィンドウ
1:暗くする
2:透明
/*:
* @plugindesc サンプルPlugin
* @target MZ
* @help
* 独自ウィンドウを表示するサンプル
*
* @command openScene
* @text シーン開始
* @desc サンプルシーンの開始。
*
*/
(() => {
const pluginName = document.currentScript.src.match(/^.*\/(.*).js$/)[1];
PluginManager.registerCommand(pluginName, "openScene", () => {
SceneManager.push(Scene_Sample);
});
class Scene_Sample extends Scene_MenuBase {
create() {
super.create();
this.createWindowLayer();
const rect = new Rectangle(200, 100, 400, 200);
this._sampleWindow = new Window_Sample(rect);
//背景を変更する
this._sampleWindow.setBackgroundType(2);
this._sampleWindow.activate();
this._sampleWindow.setHandler("ok", this.popScene.bind(this));
this._sampleWindow.setHandler("cancel", this.popScene.bind(this));
this.addWindow(this._sampleWindow);
}
}
class Window_Sample extends Window_Selectable {
initialize(rect) {
super.initialize(rect);
this.refresh()
}
refresh() {
this.contents.clear();
this.drawText('Hello World', 10, this.lineHeight() * 0, 200);
}
}
})();
一つ注意点があり「2:透明」はWindowのopacity(透明度)を0にすることで実現している。そのため、ウィンドウ背景や枠を変更したい場合にこの手法での透明化はあまりよろしくない。
各部品について
_downArrowSprite、_upArrowSprite
上矢印、下矢印のスプライト。それぞれ共にWindow.prototype._createArrowSpritesで生成し、Window.prototype._refreshArrowsで描画設定が行われる。アイテム画面などでスクロール処理ができる際にのみ表示される項目だ。それぞれdownArrowVisible、upArrowVisibleでVisible制御をしており、デフォルトはFalse。スクロール処理が必要な場合のみWindow_ScrollableクラスのupdateArrowsで表示の変更をしている。
描画は_refreshArrowsで実行しているため、この箇所を変更すれば好きなカーソルを作成できる。
_pauseSignSprite
ポーズボタンのスプライト。Window.prototype._createPauseSignSpritesで生成し、Window.prototype._refreshPauseSignで描画設定が行われる。メッセージ画面のユーザー入力待ち状態でVisible制御をしており、デフォルトはFalse。メッセージウィンドウでユーザー待ち状態の時のみtrueになる。
描画は_refreshPauseSignで行っており、この箇所を変更すれば好きなポーズボタンを作成できる。
_container
ウィンドウ背景のコンテナクラス。_backSprite(背景)と_frameSprite(四隅や辺の装飾)を内包する。
_backSprite
背景画像。生成はWindow.prototype._createBackSprite、描画設定はWindow.prototype._refreshBackで行われる。
_frameSprite
背景の枠画像。生成はWindow.prototype._createFrameSprite、描画設定はWindow.prototype._refreshFrameで行われる。
例えば以下の処理でデフォルトの背景、枠を削除し、背景画像を任意に変更できる。
/*:
* @plugindesc サンプルPlugin
* @target MZ
* @help
* 独自ウィンドウを表示するサンプル
*
* @command openScene
* @text シーン開始
* @desc サンプルシーンの開始。
*
*/
(() => {
const pluginName = document.currentScript.src.match(/^.*\/(.*).js$/)[1];
PluginManager.registerCommand(pluginName, "openScene", () => {
SceneManager.push(Scene_Sample);
});
class Scene_Sample extends Scene_MenuBase {
create() {
super.create();
this.createWindowLayer();
const rect = new Rectangle(200, 100, 400, 200);
this._sampleWindow = new Window_Sample(rect);
this._sampleWindow.activate();
this._sampleWindow.setHandler("ok", this.popScene.bind(this));
this._sampleWindow.setHandler("cancel", this.popScene.bind(this));
this.addWindow(this._sampleWindow);
}
}
class Window_Sample extends Window_Selectable {
initialize(rect) {
super.initialize(rect);
this.refresh()
}
refresh() {
this.windowskin = ImageManager.loadSystem('Window_bk');
this.contents.clear();
this.drawText('Hello World', 10, this.lineHeight() * 0, 200);
}
//ウィンドウスキンを表示するスプライトを空にする。
_refreshBack() {
this._backSprite.bitmap = ImageManager.loadBitmapFromUrl("img/pictures/Actor1_1.png");
//こちらは背景画像を直性再生成している。
//this._backSprite = new Sprite(
// ImageManager.loadTitle1($dataSystem.title1Name)
//);
}
//ウィンドウ枠を表示するスプライトを空にする。
_refreshFrame() {
this._frameSprite = new Sprite();
}
}
})();
_cursorSprite
カーソル画像。_createCursorSpriteで生成し、_refreshCursorで描画設定が行われる。
_ContentsBackSprite
コンテンツに関わるエフェクトをかけるためのスプライト。contentsBackプロパティのGetSetに指定されている。
_ContentsSprite
実際にWindow_〇〇で定義したコンテンツをツリー下で格納していくスプライト。contentsプロパティのGetSetに指定されている。Window_〇〇で実際によく使われるのはthis.contents.clear();。Window_〇〇で定義した各種UIを一度初期化する。リフレッシュ時にまず初期化してから、UI描画処理を記述していく。
InnerChildren
Window_〇〇でオリジナルのボタン等を作る場合は_ContentsSpriteではなくこちらに格納する。innerChildrenじたいはただの変数であり、実際は削除の処理などでフラグとして利用するためinnerChildrenにキーを格納しながら_clientAreaに追加する。
メソッドaddInnerChild(追加するpixi要素)で追加。通常のコンテンツとの違いは「this.contents.clear();」で削除できないUI要素をWindow_〇〇で作るために用意したものかと思われる。
関連リンク
当サイトのRPGツクールMZプラグイン開発のトップページ。他の記事へのリンクをまとめている。