RPGツクールMVで独自のウィンドウを作成するためのメモ

RPGツクールMVでプラグインで拡張しようとなると、独自のウィンドウを作成していく必要が出てくる。

この記事ではWindowの特徴についてまとめていく。

今回の記事では以下の組み込みクラスについて紹介していく。

  • Window_Base
  • Window_Selectable
  • Window_Command

Window_Base クラス

さて、独自のWindowクラスを自作するには、まずはWindow_Baseクラスを継承したクラスを作ることを検討していくとよい。

Window_Baseは名前の通り、ウィンドウを作成するのにベースとなるクラス。 ツクールMVでは様々なウィンドウの元となるクラスを用意してくれているが、全ての大元となるクラスといえる。

例えばWindow_ShopDetailsというウィンドウを作成したい場合は以下のようにする。

Window_Baseを継承して、initializeメソッドを作成する。 引数のxyはウィンドウの表示の開始位置、widthheightはウィンドウの大きさを示している。

そして、描画のためにrefreshメソッドを用意してあげる。 これはinitializeメソッドで自力で呼んであげる必要があるが、ツクールMVの都合で何かしらと再描画しないと 行けなくなったときに、refreshメソッドが自動で呼ばれる。

function Window_ShopDetails() {
    this.initialize.apply(this, arguments);
}

Window_ShopDetails.prototype = Object.create(Window_Base.prototype);
Window_ShopDetails.prototype.constructor = Window_ShopDetails;

// x, y ウィンドウの開始位置 width, height ウィンドウの大きさ
Window_ShopDetails.prototype.initialize = function (x, y, width, height) {
    Window_Base.prototype.initialize.call(this, x, y, width, height);
    this._item = null;
    this.refresh();
};

Window_ShopDetails.prototype.setItem = function (item) {
    if (this._item !== item) {
        this._item = item;
        this.refresh();
    }
};

Window_ShopDetails.prototype.refresh = function () {
    this.contents.clear();
    if (this._item) {
        // アイテム画像の表示(アイコンとして)
        var iconY = 20;
        var iconSize = 96; // 大きめのアイコン
        var iconBoxWidth = iconSize + 4;
        var iconBoxHeight = iconSize + 4;
        this.drawIcon(this._item.iconIndex, (this.width - iconBoxWidth) / 2, iconY, iconSize);

        // アイテム名
        this.drawText(this._item.name, 0, iconY + iconBoxHeight + 20, this.contents.width, 'center');

        // アイテムの説明
        var descY = iconY + iconBoxHeight + 60;
        this.drawTextEx(this._item.description, 10, descY);

        // 追加情報(効果など)
        var effectY = descY + this.lineHeight() * 3;
        if (this._item.effects) {
            this._item.effects.forEach(function (effect) {
                if (effect.code === Game_Action.EFFECT_RECOVER_HP) {
                    this.drawText("HP回復: " + effect.value1 * 100 + "%", 10, effectY);
                    effectY += this.lineHeight();
                }
                // 他の効果の表示も追加可能
            }, this);
        }
    }
};

Windowの描画のために覚えておいたほうがよいメソッドは以下の通り。

// Windowの中身をすべて削除する
this.contents.clear();

// テキストを描画する
this.drawText(name, x, y, width, height);

// テキストの色を変える
this.changeTextColor(this.systemColor());

// テキストの色をもとに戻す
this.resetTextColor();

// ラインの高さを取得する。色々とそろえるのに便利
this.lineHeight();

// img/pictures/ にある sample.png を取得する
var bitmap = ImageManager.loadPicture("sample");
// より柔軟に画像を取得したい場合は以下のやり方
var bitmap = Bitmap.load("img/pictures/image1.png");

// 画像を表示する
this.contents.blt(
    bitmap,
    0, 0,          // 元画像の切り取り開始位置
    bitmap.width, bitmap.height,  // 切り取るサイズ
    x, y,          // 描画位置
    iconSize, iconSize   // 描画サイズ
);

Window_Selectable

Window_Selectableはアイテム一覧などの一覧表示を良い感じにやってくれる。 ここではWindow_ShopItemListクラスを作成する例を示す。

function Window_ShopItemList() {
    this.initialize.apply(this, arguments);
}

Window_ShopItemList.prototype = Object.create(Window_Selectable.prototype);
Window_ShopItemList.prototype.constructor = Window_ShopItemList;

// 例として、this._goodsプロパティを作成して、ここに一覧にしたいデータを格納していく。
// ここはthis._goodsという名前でなくてもよい。
Window_ShopItemList.prototype.initialize = function (x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
    this._goods = [];
    this._hoverHandler = null;
    this.refresh();
};

Window_ShopItemList.prototype.setGoods = function (goods) {
    this._goods = goods;
    this.refresh();
};

そして、重要なのがdrawItemメソッドとmaxItemsメソッドを定義してあげることだ。

drawItemメソッドは一覧の各アイテムを描画するときに呼ばれるメソッドで、 maxItemsメソッドは一覧のアイテムの総数を表すメソッドだ。

Window_ShopItemList.prototype.maxItems = function () {
    return this._goods.length;
};

Window_ShopItemList.prototype.drawItem = function (index) {
    var item = this._goods[index];
    var rect = this.itemRect(index);
    var priceWidth = 96;
    rect.width -= priceWidth;
    this.drawItemName(item, rect.x, rect.y, rect.width);
    this.drawText(item.price + ' G', rect.x + rect.width, rect.y, priceWidth, 'right');
};

処理の中でthis.itemRect(index)が使われているが、これは現在描画中のアイテムの部分のx, y, width, height の情報を格納してくれているRectangleクラスを返してくれる。

また、アイテムを選択した時やキャンセルボタン(右クリック)を押したときの挙動を設定したい場合は、 this.sethandler("ok", function)this.sethandler("cancel", function)を使って設定できる。

setHandlerに鑑定はWindow_Selectableクラスの中で設定するよりも、そのWindowクラスを使っている Sceneクラス内で設定するのが良いだろう。

雰囲気としては以下の感じのコードになる。

Scene_CustomShop.prototype.createItemListWindow = function () {
    var wx = Graphics.boxWidth / 2;
    var wy = 0;
    var ww = Graphics.boxWidth / 2;
    var wh = Graphics.boxHeight - this._goldWindow.height;
    this._itemListWindow = new Window_ShopItemList(wx, wy, ww, wh);
    this._itemListWindow.setGoods(this._goods);
    this._itemListWindow.setHandler('ok', this.onBuyClick.bind(this));
    this._itemListWindow.setHandler('cancel', this.popScene.bind(this));
    this._itemListWindow.setHoverHandler(this.onItemHover.bind(this));
    this.addWindow(this._itemListWindow);
};

Scene_CustomShop.prototype.onBuyClick = function () {
    this._buyWindow.show();
    this._buyWindow.select(0);
    this._buyWindow.activate();
};

また、Window_Selectableでどのアイテムが選択されているかを他のクラスから取得したい場合がある。 その時はindexメソッドを使うとよい。

const index = this._itemListWindow.index();

実際にはindex番号よりも、選択されているアイテムのオブジェクトが欲しいことが多いので、以下のようなメソッドを作成 しておくと便利。

Window_ShopItemList.prototype.getItem = function () {
    return this._goods[this.index()];
};

Window_Command

コマンドに特化したWindowクラス。先ほど紹介したWindow_Selectableを継承している。

Window_SimpleCommandを例にして解説する。使い方は簡単で、makeCommandListメソッドを ユーザーから定義してあげればよい。

makeCommandListメソッドには、this.addCommand("はじめる", "start");のように コマンドを定義してあげるとよい。

addCommandの第1引数は、表示するコマンド名で、第1引数はシンボル名だ。

// 1. 基本的なコマンドウィンドウの作成
function Window_SimpleCommand() {
    this.initialize.apply(this, arguments);
}

Window_SimpleCommand.prototype = Object.create(Window_Command.prototype);
Window_SimpleCommand.prototype.constructor = Window_SimpleCommand;

// 初期化
Window_SimpleCommand.prototype.initialize = function(x, y) {
    Window_Command.prototype.initialize.call(this, x, y);
};

// コマンドリストの作成
Window_SimpleCommand.prototype.makeCommandList = function() {
    // コマンドの追加
    this.addCommand("はじめる", "start");    // 表示名, コマンドID
    this.addCommand("設定", "options");
    this.addCommand("終了", "exit");
};

そして、Window_SimpleCommandを使うシーンにおいて、先ほどと同じようにthis._commandWindow.setHandlerなどの ようにしてあげるとよい。

例えば、Scene_SimpleMenuクラスと作ったとすると、以下のような感じで定義してあげる。

function Scene_SimpleMenu() {
    this.initialize.apply(this, arguments);
}

Scene_SimpleMenu.prototype = Object.create(Scene_MenuBase.prototype);
Scene_SimpleMenu.prototype.constructor = Scene_SimpleMenu;

Scene_SimpleMenu.prototype.initialize = function() {
    Scene_MenuBase.prototype.initialize.call(this);
};

Scene_SimpleMenu.prototype.create = function() {
    Scene_MenuBase.prototype.create.call(this);
    this.createCommandWindow();
};

/**
 * ここでsetHandlerの登録をする。
 * 
 */
Scene_SimpleMenu.prototype.createCommandWindow = function() {
    // コマンドウィンドウの作成(中央に配置)
    var x = (Graphics.boxWidth - 240) / 2;
    var y = (Graphics.boxHeight - 200) / 2;
    this._commandWindow = new Window_SimpleCommand(x, y);

    // コマンドが選択されたときの処理を設定
    this._commandWindow.setHandler('start', this.commandStart.bind(this));
    this._commandWindow.setHandler('options', this.commandOptions.bind(this));
    this._commandWindow.setHandler('exit', this.commandExit.bind(this));

    this.addWindow(this._commandWindow);
};