【WrapEarth】VXAceをMZに移植④~ウィンドウ調整プラグインその2【RPGツクール】

●はじめに

VXAceで作成したゲームもMZに移植し、アツマールで公開しようと思います。

ラップアース移植、メニュー最適化用にプラグインを作成中。

●公式コード調査

▷setHandlerとaddCommandを使う?

setHandlerやaddCommandとかを見かけたので、
その辺使えば簡単にできるだろうと思ったけど甘かった。
setHandlerだけでは変化しないし、addCommandがWindow_Selectableにない模様。

甘く見た結果

せっかく見れるんだから、ちゃんと公式のコードを確認しよう。

▷Window_MenuCommand調査

メインメニューにあたるWindow_MenuCommandを見てみる。

Window_MenuCommandはWindow_Commandを継承し、そいつのaddCommandを使っている。
addCommandはmakeCommandListの中で使っていて、
makeCommandList自体はWindow_Commandのrefreshを使っている。
実体化したウィンドウのsetHandlerでハンドラを関連付けるようだ。
なるほど、(自分の実力では)実装したら色々ハマるだろうけど、思ったよりシンプル。

Window_Commandを継承してinitializeでaddCommand、
ウィンドウ作成後にsetHandlerでハンドラ指定か~。
refreshでは親のrefreshを行儀よく呼ばないと駄目だな。

行けそうなので、一旦プラグインを整理して作り直す。

●プラグイン修正

▷プラグイン

がっつり変更。
ラップアース用ならWindow_Commandの継承だけで済むかもだけど、まぁ一応変えられる余地は残しておこう。

/*:ja
 * @target MZ
 * @plugindesc ラップアース用メニュー構成
 * @author dar9matter
 *
 * @help d9mWindow.js
 *
 * ラップアース用メニュー構成
 */
(() => {
    // シーン登録
    d9mSceneEntry = function (scene, windowId, funcs) {
        let root = this;
        let windowName = windowId.charAt(7).toLowerCase() + windowId.slice(8);
        let createWindowId = 'create' + windowName.charAt(0).toUpperCase() + windowName.slice(1) + 'Window';

        // オブジェクトへ関数を登録
        if (funcs) {
            for (let targetClassId in funcs) {
                let obj = root[targetClassId];
                if (!obj) {
                    continue;
                }
                for (let id in funcs[targetClassId]) {
                    obj.prototype[id] = funcs[targetClassId][id];
                }
            }
        }

        // 作成ウィンドウの登録
        if (!scene.prototype[createWindowId]) {
            switch (windowId) {
            case 'Window_MenuCommand':
                scene.prototype[createWindowId] = scene.prototype.createCommandWindow;
                scene.prototype.commandWindowRect = scene.prototype[windowName + 'WindowRect'];
                break;
            case 'Window_MenuStatus':
                scene.prototype[createWindowId] = scene.prototype.createStatusWindow;
                scene.prototype.statusWindowRect = scene.prototype[windowName + 'WindowRect'];
                break;
            default:
                scene.prototype[createWindowId] = function () {
                    let rectFunc = scene.prototype[windowName + 'WindowRect'];
                    if (!rectFunc) {
                        rectFunc = function() { return new Rectangle() };
                    }
                    this['_' + windowName + 'Window'] = new root[windowId](rectFunc());
                    this.addWindow(this['_' + windowName + 'Window']);
                }
                break;
            }
        }
    }

    // シーン初期化
    d9mInitScene = function(sceneId, initParams) {
        let windowIdList = [];

        // 各ウィンドウ設定
        for (windowId in initParams) {
            let sceneParams = initParams[windowId];
            let windowName = windowId.charAt(7).toLowerCase() + windowId.slice(8);

            // 基底クラス選択
            let baseId;
            if (sceneParams.menuItems) {
                baseId = 'Window_Command';          // メニューがある場合
            } else {
                baseId = 'Window_Selectable';       // メニューがない場合
            }

            // ウィンドウクラスが無かったら作成
            if (!this[windowId]) {
                this[windowId] = function() {
                    this.initialize(...arguments);
                    this.refresh();
                }
                this[windowId].prototype = Object.create(this[baseId].prototype);
                this[windowId].prototype.constructor = this[windowId];
            }

            // 登録用データ
            let entryParams = {};
            entryParams[sceneId] = {};
            entryParams[windowId] = {};

            // シーンに「ウィンドウ操作関数」を登録
            if (sceneParams.rect) {
                // ウィンドウ矩形取得関数を登録
                entryParams[sceneId][windowName + 'WindowRect'] = function () {
                    return new Rectangle(
                        sceneParams.rect[0],
                        sceneParams.rect[1],
                        sceneParams.rect[2],
                        sceneParams.rect[3]
                    );
                }
            }

            // cols/rows指定
            if (sceneParams.matrix) {
                entryParams[windowId].maxCols = function() { return sceneParams.matrix[0]; }
                entryParams[windowId].numVisibleRows = function() { return sceneParams.matrix[1]; }
            }
            // ウィンドウに「関数」を登録
            if (sceneParams.arrange) {
                // メニュー指定がある場合
                if (sceneParams.menuItems) {
                    entryParams[windowId].makeCommandList = function() {
                        for (let i = 0; i < sceneParams.menuItems.length; i++) {
                            let itemInfo = sceneParams.menuItems[i];
                            if (!itemInfo.enabled) itemInfo.enabled = true;
                            if (!itemInfo.ext) itemInfo.ext = null;
                            this.addCommand(itemInfo.name, itemInfo.symbol, itemInfo.enabled, itemInfo.ext);
                        }
                    }
                }

                for (let funcId in sceneParams.arrange) {
                    entryParams[windowId][funcId] = sceneParams.arrange[funcId];
                }
            }

            // シーンに登録
            d9mSceneEntry(this[sceneId], windowId, entryParams);

            windowIdList.push(windowId);
        }

        // シーン作成関数を上書き
        this[sceneId].prototype.create = function() {
            d9mProtoCall(this, 'create');

            windowIdList.forEach(windowId => {
                // ウィンドウ作成
                let createWindowId = 'create' + windowId.slice(7) + 'Window';
                if (this[createWindowId]) {
                    this[createWindowId]();
                }

                // ハンドラ設定
                let sceneParams = initParams[windowId];
                if (sceneParams.menuItems) {
                    let wnd = this['_' + windowId.charAt(7).toLowerCase() + windowId.slice(8) + 'Window'];
                    for (let i = 0; i < sceneParams.menuItems.length; i++) {
                        let itemInfo = sceneParams.menuItems[i];
                        wnd.setHandler(itemInfo.symbol, itemInfo.handler.bind(this));
                    }
                }
            });
        };
    }

    // protoの関数を呼び出す
    d9mProtoCall = function (wnd, funcName) {
        if ([...arguments].length >= 3) {
            wnd.__proto__.__proto__[funcName].call(wnd, ...[...arguments].slice(2));
        } else {
            wnd.__proto__.__proto__[funcName].call(wnd);
        }
    }
})();

▷独自メニュー作成

jsonでウィンドウの情報を定義、
ウィンドウ名が公式に用意されているものだったらそれを使う。
存在しなかったら独自ウィンドウとして新規作成。

独自メニューとして「魔法」「セーブ」「ゲーム終了」を用意。
自分用なのでd9mProtoCallの後に直接記述して済ませる。

    d9mInitScene('Scene_Menu', {
        Window_MenuCommand: {
            matrix: [2, 4],
            rect: [0, 52, 400, 200],
        },
        Window_MenuStatus: {
            matrix: [4, 1],
            rect: [84, 376, 640, 240],
            arrange: {
                drawItemImage: function(index) {
                    const actor = this.actor(index);
                    const rect = this.itemRectWithPadding(index);
                    const w = Math.min(rect.width, 144);
                    const h = Math.min(rect.height, 144);
                    const lineHeight = this.lineHeight();
                    this.changePaintOpacity(actor.isBattleMember());
                    this.drawActorFace(actor, rect.x, rect.y + lineHeight * 0.7, w, h);
                    this.changePaintOpacity(true);
                },
                drawItemStatus: function(index) {
                    const actor = this.actor(index);
                    const rect = this.itemRectWithPadding(index);
                    const x = rect.x;
                    const y = rect.y;
                    const width = rect.width;
                    const bottom = y + rect.height;
                    const lineHeight = this.lineHeight();
                    this.drawActorName(actor, x, y + lineHeight * 0, width);
                    this.drawActorIcons(actor, x, y + lineHeight * 1, width);

                    this.drawActorLevel(actor, x, y + lineHeight * 3.7, width);
                    this.placeBasicGauges(actor, x, y + lineHeight * 4.5, width);
                },
            }
        },
        Window_Test: {
            rect  : [608, 52, 160, 44 * 3 + 24],
            menuItems: [
                { name:'魔法',       symbol:'skill',   handler: Scene_Menu.prototype.commandPersonal },
                { name:'セーブ',     symbol:'save',    handler: Scene_Menu.prototype.commandSave     },
                { name:'ゲーム終了', symbol:'gameEnd', handler: Scene_Menu.prototype.commandGameEnd  },
            ],
            arrange: {
            }
        }
    });

結局jsonを基にウィンドウ作ってるからトリアコンタンさんの使うべきだろうけど、高機能なのかめっちゃ長い。
基本他人のコード理解するのめんどいし、作るの楽しいからいいや。

動かした結果。右上のが追加したメニューウィンドウ。↓

一部既存メニューを呼べた

魔法選択後、キャラクターを選択すると固まる。
通常のメニューから復帰できるけど。

残念、今回はここまでにしよう。

●感想

▷今回のプラグインについて

それなりに整理できたし、後から得た知識を追加できる形になったかと思うので満足。

セーブとゲーム終了は正しく動いたけど、魔法でキャラクターを選択すると固まる。
通常のコマンドウィンドウとまだ何か違いがあるってことか。

うーん、やっぱりまだまだ甘かったかぁ。

▷公式コードについて

実装やテストしている時に何か違和感あると思ったら
Window_MenuCommandの作成がcreateCommandWindowで行われている。

作成関数がcreate[Menu]CommandWindowじゃないので上手く統一して扱えない。
まだ理解が追いついてない部分もあるんだろうけど、気持ち悪い。

create[Menu]CommandWindowにcreateCommandWindow、
[menu]CommandWindowRectにcommandWindowRectの関数を入れて統一する形にした。

・・・これが上の不具合に影響してるのかなぁ。

●次回へ

JSONでウィンドウを作成できるようにし、シーンを上書きできるようにした。
まだ不完全だが独自メニューも作れる取っ掛かりもできた。

次は独自メニューから既存メニューを辿れるようにしたい。

嬉しくはないが、RPG Maker Uniteは来年春になった。
もうちょっとだけ時間をかけてみよう。

※プラグインは自由に使ってもらって構いませんが、
 何が起きても責任は負いません、自己責任でよろしく。

コメント

タイトルとURLをコピーしました