プラグインコマンドについて調べてみた

今回いよいよプラグインの中核的機能[プラグインコマンド]です。

コマンドが使えれば複雑で大規模なスクリプトも1行だ!

ところで

 ツイッターの公式アカウントでツクールの新作の開発決定の発表がありましたね。
 RPGツクールMV のダメなところを徹底的に改修して良いところをぐーんと伸ばし、RGPアツマールのようなサイトとの連携も強化され、使いやすい素材マーケットも準備される、そんなバラ色の未来を想像して嬉しくなっちゃいますね…そんな想像ができるのも今だけの楽しみですからね(笑)

 さて、それはそれとして、鳶嶋工房では引き続きパソコンツクールの最新版RPGツクールMVのプラグインの作り方をやっていきます。
 今回でプラグインについては一応最後ですね。では、張り切っていきましょう!!

プラグインコマンドとは

 [イベントコマンド]の[3]タブの[上級]に配置されている、プラグインに対する指示をする機能が[プラグインコマンド]です。
 メモ欄みたいにF1キーかコンテクストメニューで、[プラグインヘルプ]を確認できます。
 大抵はヘルプに使用例が書いてあるので、それをコピーして適宜編集して使うと良いと思います。

[イベントコマンド]ウィンドウの[プラグインコマンド]

 今時の開発環境なら当たり前にあるコマンド名のオートコンプリートとか、引数のタイプに合わせた入力用UIとか用意して欲しかったところです。
 ないので、しょうがないですけど。

 イベントコマンドは コマンド名 引数1 引数2 引数3... という感じに、コマンド名を書いてその後ろに空白区切りで必要な引数を書く、という書式です。
 こうしておけば、コマンド名の文字列と、引数の配列にRPGツクールMVの方で分解してくれます。
 [プラグインパラメータ]ほどではないですが、メモ欄のタグよりはちゃんとしてますね。

 それ以外の書式を採用しているプラグインコマンドもありますが、引数が文字列として渡されるので、それを適当にプラグイン側で解釈しているだけで、特にRPGツクールMVの方で何かやっているわけではありません。
 ちなみに、プラグイン名 命令 引数1 引数2... みたいな作りになっているものも見かけますが、それは単にコマンド名をプラグイン名に合わせて、引数1 を 命令 としてプラグイン側で解釈しているだけです。
 例によって、僕のプラグインではTF_をコマンド名の接頭辞として使用しています。
 あとコマンド名が、大文字・小文字を区別しないものだったり、日本語と英語と用意されていたりするプラグインもありますが、それはそのプラグインの作者が優しいだけです。
 トリアコンタンさんの半分は優しさでできています。僕は優しくないので全部英字の大文字です(笑)

プラグインコマンドを受け取る

 プラグインコマンドを作る上で、欠かせないのが Game_Interpreter.pluginCommand() メソッドです。
 このメソッドの引数として、コマンド名、それからコマンドパラメータの配列が渡されます。
 空の関数なので、何も考えずに上書きしちゃいそうですが、それはやめた方が良いです。
 複数のプラグインが、同じことをやると上書きがさらに上書きされて、最後に上書きしたコードしか残らなくなってしまいます。

 そこで使うのが「一旦メソッドを変数(定数)に退避して、上書きしたメソッドから退避したメソッドを呼ぶ」という手法です。
 個人的には「ここは addEventListener() でイベントリスナを登録させるべきじゃないの?」と思うものの、提供されてないので変数に退避方式を使うしかありません。
 前回を読んでいる方は、めっちゃデジャブですね。

( function() {
    'use strict';
    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function( command, args ) {
        _Game_Interpreter_pluginCommand.call( this, command, args );

        if( command === 'ALERT' ) {// 渡されたコマンド名をチェック
            alert( args[ 0 ] );// コマンドの実行部分
        }
    };
} )();

 これが最低限の[プラグインコマンド]を使うためのコードです。
 優しい作者はここで command.toUpperCase() とかやって大文字小文字の区別なく受け付けたり、日本語名のコマンドを用意したりしてます。
 …メンドクセ、と思って僕はほったらかしです!!

 さらに、機能考えるの面倒なので、JavaScript に最初からついてる alert() 呼び出してます。
 引数はちゃんと分解されて配列変数の args に入っているので、必要なやつを使えば良いです。
 例によって渡されるのは文字列なので、に解説した parseInt() などの変換スクリプトを駆使して、必要な型へ変換します。

 特にこのメソッドはプラグインがあったら半分は使ってるぐらいの重要メソッドなので、_Game_Interpreter_pluginCommand( command, args ); なんて呼び方してはいけません。
 そうした場合スコープの関係で thisGame_Interpreter オブジェクトではなくなって、それを期待して他のプラグインが Game_Interpreter のメソッドを使っていた場合、this が変わって呼び出せなくなってしまうのです。
 そんなわけで、call()apply() を使って関数呼び出しの際に現在の this を渡しているわけです。

 なお、[実行内容]の[スクリプト]に書く JavaScript の場合は、thisGame_Interpreter なので this.pluginCommand() で自由に他のプラグインのコマンドも呼び出せるので、とっても便利です。

 以下、前回書き忘れてた注意。
 _Game_Interpreter_pluginCommand って定数名はなんでもいいんですが、RPGツクールMVではこういう元の クラス.prototype.メソッド名_クラス_メソッド名に変換した名前を使うのが定石です。
 繰り返しますが、別にこの定数名はなんでもいいです。なので別の書式で書いてる人もいます。
 ですが、他と揃えておくことにします。他と揃えれば他人も読みやすいコードになりますし、3日後の自分は他人です。
 以上、前回書き忘れてた注意でした。

 コマンドが増えてくると if よりも switch 使った方が良いです。
 あと、コマンドごとに関数にして分けた方が良いです。

( function() {
    'use strict';
    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function( command, args ) {
        _Game_Interpreter_pluginCommand.call( this, command, args );

        switch( command ) {
            case 'ALERT': alert( args[ 0 ] ); break;
            case 'CONSOLE': console.log( args[ 0 ] ); break;
            case 'MESSAGE': showMessage.call( this, args[ 0 ] ); break;
        }
    };

    function showMessage( messageText ) {
        $gameMessage.add( messageText ); // メッセージを設定して
        this.setWaitMode( 'message' ); // 入力待ち状態にします
    }
} )();

 ここでは、3つのコマンドを受けつててます。
 ALERT と CONSOLE は JavaScript がもともと持ってるメソッドを呼び出してます。
 MESSAGE はRPGツクールMVのメッセージを表示する仕組みを使う showMessageって関数を作って呼んでます。
 関数を呼び出したところで args[ 0 ] みたいな中身のよくわからん変数から名前のついた引数になって、とっても読みやすいです。

 これ、関数に分けずに if ブロックで処理やると、コードの見通しがめちゃめちゃ悪くなります。
 僕のプラグインでも、実際そうして見通しが悪くなってるのあるから、気をつけろ! (長井秀和 風に)

プラグインをアップデート

 前回はタグだったので、特に機能追加するところがみつからなかったんですが、今回は[プラグインコマンド]で、いつでも音量単位の設定ができるようにしてみましょう。
 ついでなので、最初にバージョン情報やMITライセンスに必要な情報をコメントしておきました。
 ライセンスについては、大事なことなので各自しっかり調べて、納得できるライセンスを表示しておきましょう。
 かならずしも MITライセンスを選択する必要はありませんが、何のライセンス表示もないと利用できない見せるだけプラグインになります。
 あと、[プラグイン管理]ウィンドウで出る[説明] @plugindesc、[作者] @author、[ヘルプ] @help の設定もしておきました。
 詳細は MV.PluginSettings で確認できます。

//========================================
// VolumeOffset.js
// Version :1.0.0.0
// For : RPGツクールMV (RPG Maker MV)
// -----------------------------------------------
// Copyright : Tobishima-Factory 2020
// Website : http://tonbi.jp
//
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
//========================================
/*:ja
 * @plugindesc 音量設定の変更単位を変更
 * @author とんび@鳶嶋工房(tonbi.jp)
 *
 * @param volumeOffset
 * @text 音量単位
 * @desc v1.0.0.0 音量設定の変更単位を設定します(規定値: 5)
 * @type number
 * @default 5
 * @min 1
 * @max 100
 * 
 * @help
 * 【プラグインコマンド】
 * VOLUME_OFFSET [音量単位]
 *  [音量単位]  音量設定の変更単位(1〜100)
 * 
 * 例: VOLUME_OFFSET 2
 * 
 * 利用規約 : MITライセンス
 */
( function() {
    'use strict';
    const pluginParams = PluginManager.parameters( 'MyPlugin' );
    pluginParams.volumeOffset = JSON.parse( pluginParams.volumeOffset );
    Window_Options.prototype.volumeOffset = () => pluginParams.volumeOffset;

    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function( command, args ) {
        _Game_Interpreter_pluginCommand.call( this, command, args );

        if( command === 'VOLUME_OFFSET' ) {
            pluginParams.volumeOffset = parseInt( args[ 0 ], 10 );
        }
    };
} )();

 音量設定の変更単位をゲームの途中で変えたくなることはないと思いますけどね(笑)
@desc の頭にバージョンナンバーつけておくと便利ですが、個人的には書き換え損ねるのでつけてません。@version が欲しいですね。
 あとは、英語での設定もあるとより良いと思います。
 最近は google や yohoo! の翻訳をはじめ、DeepL などの翻訳ツールが充実しているので、英語が全く読めなくてもまぁまぁな翻訳が可能です。

ついでに[スクリプト]について

 [プラグインコマンド]に似た機能を持ったものとして[スクリプト]があって、JavaScript を直でかけます。
 ただ、ここからプラグインを呼び出すと eval() で処理されちゃって、[プラグインコマンド]に比べると重い(遅い)んですよね。
 でも[移動ルートの設定]には[プラグインコマンド]がないので使わざるを得ません。

 プラグインのグローバル領域に関数を定義すれば、いきなり関数名で呼べて便利! ではあるのですが、極力やめたほうがいいです。
 素材として公開する気なら「絶対やっちゃダメ」レベルです。

 じゃあどうすんのというと、[スクリプト]を実行している時に this に入っているクラスに追加しちゃう方法が、割と一般的です。
 [実行内容]の[スクリプト]が呼び出された時の thisGame_Interpreter です。
 [移動ルートの設定]の[スクリプト]の場合の this は、それぞれのキャラクタのオブジェクトなので、親クラスで考えると[イベント]でも[プレイヤー]でも Game_CharacterBase ってことになります。
 これらのクラスのメソッドとして、必要なものを登録しておくのです。
 これもあまりお行儀が良いとも思えないし、下手するとグローバル領域においた方がマシだったということもあるんですが…まぁ臨機応変に。

( function() {
    'use strict';
    Game_Interpreter.prototype.myMethod = function( value ) {
        alert( value ); // [実行内容]で実行される
    };
    Game_CharacterBase.prototype.myMethod = function( value ) {
        alert( value ); // [移動ルートの設定]で実行される
    };
} )();

 こんな感じです。[スクリプト]に this.myMethod( 'はろはろー' ) みたいに書いて使います。
 メソッド名が他のプラグインと衝突する可能性があるので、例によって僕のプラグインではTF_接頭辞をつけてます。
 ホントRPGツクールMVはプラグイン同士の衝突について、ほぼ何の対策も打ってないのヒドいよね。

 そこで結論。

イベント組むより[プラグインコマンド]で作った方が楽、という変態になれるぞ!!