プラグインパラメータについて調べてみた

やってなかったプラグインパラメータの使い方を調べていこうと思います。

食材(type)によって使い分けるべし

パラメータの設定

 [プラグインパラメータ]ってプラグインのどこで設定するのかは、適当なプラグインのコードを見るのが手っ取り早いです。
 大体最初に延々コメント文があって、そこで設定してるんですね。
 [プラグインパラメータ]を[プラグイン管理]ウィンドウに表示するまでは、特にスクリプトを書く必要がないわけです。
 なかなか気が利いてます! ブラボー!

 さて、そのパラメータの種類の確認しようにもRPGツクールMVがリリースされてからかなり追加されていて、ヘルプの記述内容が役に立ちません。
 RPGツクールMV ver1.5.0以降のプラグインパラメータに関して - RPGツクール公式ブログに追加情報が書いてありますが完全ではありません。
 おなじみ、むーてぃさんのRPGツクールMV プラグインパラメータのtypeまとめ | 趣味に生きる。は画面キャプチャもあって、非常に見やすいです。
 ツクマテ:プラグインパラメータの「@​type」についてで、公式で書いてない部分もまとめてあります。
 ツクマテにある !ParamSample.js ファイルはダウンロードして、書き換えて[プラグイン管理]で動作を確認しつつ理解を進めると良いと思います。

 あっちこっち読むのも大変だし、どこを見たら全体像がわかるんだー!
 と思ったあなた、Class: MV.PluginSettings に最新の情報がまとまっているので、まずは見ておけば大丈夫です。

[プラグインパラメータ]の受け取り方

 さて今度は、[プラグインパラメータ]の受け取り方を解説していきます。

 前回は音量設定の最小変更単位を10から5に変更しましたが、プラグインパラメータで指定した方が、プラグインを使う側(ユーザ)としては便利ですね。
 自分しか使わないプラグインでも、できれば導入したいものです。

 まず、パラメータを受け取るにはプラグインのファイル名が必要です。
 なので、迂闊にプラグインのファイル名を変えてしまうと、動作しなくなってしまいます。
 僕のプラグインのファイル名は頭に必ず'TF_'という接頭辞をつけてますが、Tobishima-Factory の頭文字にアンダーバーつけたもので、他のプラグインの名前とかぶらないようにするための、ちょっとした工夫です。
 公式でなんらかの名前が衝突しない仕組みを用意して欲しかった気もしますが、まさかこんな大量にプラグインが作られるとは。
 プラグインの集積サイト#ツクプラを見ると現在で2,637本のプラグインがあります。当然実際はもっともっとあるわけです。
 僕がRPGツクールMVの設計担当だったとしても、この量は「ないない」と一蹴してしまった気もします。

 さて、プラグインのファイル名が MyPlugin.js だとすると、次のように拡張子 .js を取った名前を渡してパラメータの値を取り出せます。

const pluginParams = PluginManager.parameters( 'MyPlugin' );

 PluginManager を見ると、帰ってくるのはオブジェクトデータです。
 具体的には、pluginParams.パラメータ名 もしくは pluginParams[ 'パラメータ名' ] で値を取り出せます。
 返ってくるのは必ず文字列なので、構文解析(パース)型変換(キャスト)が必要です。

型変換

 文字列から他の型へ変換するのはRPGツクールMV特有のものではなく JavaScript のテクニックなのですがプラグインを作る時には必須です。
 [プラグインパラメータ]全体の値はすでにpluginParamsに入ってて、パラメータ名はparamということにします。適宜入れ替えてご理解・ご使用ください。
 次のように JSON.parse() にパラメータを放り込めば、いい感じに変換してくれます。

const parsedParam =  JSON.parse( pluginParams.param );

 どうも @type で設定した場合、JSON.parse() で変換可能なデータを返すように作られているようなのです。
 ただし一回 JSON.parse( )を通すだけでは、配列やオブジェクト構造形式のデータに内包されている値はすべて文字列になっています。
 そこで、次のようなコードで内包されたで文字列も含めて変換します。

const parsedParams = JSON.parse( JSON.stringify( 
    pluginParams ,
    ( key, value ) => {
        try { return JSON.parse( value ); } catch( e ) {}
        return value;
    }
) );

 この処理には JSON.stringify() というJavaScriptの値をJSON文字列化するメソッドを利用しています。
 JSON.stringify() には値の入れ替えを行うコールバック関数を渡せるので、そこで各値を JSON.parse( ) で変換して入れ替えています。
 変換が成立しない(エラーが発生した)場合は、文字列のまま扱います。
 このように JSON.stringify() で一旦文字列化した後、 JSON.parse() でオブジェクト構造データに戻す処理は、意外に高速に動作します。
 これで階層化データの変換ができるようになりました。
 !ParamSample.js もこれと同じ方式で、さらに eval() を使ってJavaScriptであれば実行する処理も入ってるので計算式なども渡せます。

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

 では以上のことを踏まえて、音量設定の変更単位を変えるプラグインを[プラグインパラメータ]に対応させると以下のようになります。

/*:ja
 * @param volumeOffset
 * @text 音量単位
 * @desc 音量設定の変更単位を設定します(規定値: 5)
 * @type number
 * @default 5
 * @min 1
 * @max 100
 */
( function() {
    'use strict';
    const pluginParams = PluginManager.parameters( 'MyPlugin' );
    pluginParams.volumeOffset = JSON.parse( pluginParams.volumeOffset );
    Window_Options.prototype.volumeOffset = () => pluginParams.volumeOffset;
} )();

 これで[プラグイン管理]ウィンドウにパラメータ設定が追加されて、ゲームのオプションの音量単位を変更できました。
 @author 作者名とか、MITとかのライセンス説明とかつけたら公開してもいいぐらいの出来栄えです。
 繰り返しますがコメント文に書く設定の詳細は Class: MV.PluginSettings です。

個別の型変換

 さて、この JSON.parse() 方式、どのプラグインもこうやっているかというと、そうでないものも多いのです。
 基本的には @type が導入される前の作法が残ってるのだと思います。
 "312" みたいな値を文字列として扱いたい時も、数値に型変換されちゃうのが問題になる事があるかもしれません。

 そんなわけで、昔の方式を採用しているプラグインを読むときのガイド、みたいな感じで以下は読んでください。

 ちなみに、僕のプラグインは JSON.parse() 方式に気づいたというか理解したのが最近なので古い形式で書いてたりします。
 あと、JsonEx.parse() を使う必要があるのかと思ってましたが、そうでもないと気づいたの、これ書いてる最中だったりします(笑)
 JsonEx はセーブデータを作る場合など、メソッドなど持ったオブジェクトを扱うために作られていて、単にJSONデータを変換するのに使う必要はなかったのでした。

整数型の変換

 一番多いと思われる整数の変換は、以下のような形で可能です。
 ちなみに2番目の引数の 10 はストリクトモードなら、なくても大丈夫のはずですがクセでつけてます。

const intParam = parseInt( pluginParams.param, 10 );

 空文字列はNaNになるので、もうちょっと処理加えてもいいかもいいかも。
 例えば NaN0 に入れ替えるには、次のように OR 演算子を使うパターンがあります。
 この方法だと 0 を値として利用できる場合、0 は偽判定されるので後ろの規定値は 0 以外にできなくなっちゃいますけどね。

const intParam = parseInt( pluginParams.param, 10 ) || 0;

 parseInt() の結果が NaN もしくは 0 でなかったら、そのまま値が代入されます。
 そうでなかったら、|| の後ろの値が代入されます。
 || を使うのはちょっと裏技っぽいので、三項演算子やifを使って場合分けしたほうがいいと思います。
 型変換(キャスト)は以下の形式でもできます。

const intParam = Number( pluginParams.param );

 ついでに、裏技っぽいというか裏技ですけど、単項演算子の + をつけると文字列を数値化できます。

const intParam = +pluginParams.param;

 なにやってるかわからなくなるので、こういう裏技やめたほうがいいですが、やってる人もいるのでそういうコードが読めるようになっておくのは悪くないです。
 僕は、後ろに単位とか入ってても数値として扱ってくれて、少数切り捨てもやってくれるので、parseInt()の方を使っています。
 とはいえ、コメント文で適切に[プラグインパラメータ]設定ができていれば変な値が返ってくることはないので、どっちでもいいとも言えます。
 空文字列などの想定されていない値が来た時に規定値にする処理は入れなくても、@default の設定で十分で、変な値が来た時はエラー出しときゃいいと思います。

 JavaScript は文字列そのままでも割と数値として扱っていい感じに処理しちゃったりするんですが、さすがにずっと文字列のままはやめといた方がいいです。
 意外な落とし穴があって、バグで悩むことになりかねません。

自然数型の変換

 小数点以下が含まれる値は次のように。

const floatParam = parseFloat( pluginParams.param );

 これも先に書いたように変な文字列はやってこないはずなので、次のようにやっちゃってもいいんですけどね。

const floatParam = Number( pluginParams.param );

論理型(真偽値)の変換

 じゃあ真偽値はこうだ、となりそうです。

const booleanParam = Boolean( pluginParams.param );

 しかし、RPGツクールMVは 文字列で 'true''false' を返してくるんで、Boolean( 'false' ) の値は trueになっちゃうんですね。
 いろいろ方法はありますが簡単なのは、次のように文字列比較しちゃうことです。

const booleanParam =  pluginParams.param === 'true';

 以前のバージョンでは @type がなかったので、 'true' だけでなくて 'True' とか 'TRUE' とか、いろんな値が返ってくる可能性があったので、pluginParams.param.toUpperCase()pluginParams.param.toLowerCase() で英字の大小を揃えて比較するとか、正規表現で pluginParams.param.search( /true/i ) === 0 とかやったりします。
 なお、JSON.parse( pluginParams.param ) の場合は全部小文字の true false 以外は受け付けません し、それ以外の文字列が来たら、別の型として扱われます。
 昔は @type boolean がなかったので、文字列のON/OFFを使ってるプラグインもあります。
 他にも記憶によると、空文字列か適当な文字列でfalseとtrueの代わりにしてるプラグインもあったような。そういう場合は Boolean() での変換が行えます。
 ただ、今あえて @type boolean を指定して true/false を返す方法を採用しない理由は、僕みたいに「その方法知らなかった」ということ以外にないと思います(笑)

配列の変換

 僕は、@type *[] 導入以前のプラグインは配列をどう扱っていたのかよく知りません。
 もしかしたら、CSV( Comma Separated Values )の文字列を渡して正規表現とか split() で分割して使う、みたいなことやってたんでしょうか?
 新参者なのでわかりません!

 余談ですが、この配列の入力ウィンドウ、ドラッグで入れ替えできます!!

追記:
 文字列を空白区切りで入力するようにしてもらう、という手法があるそうです。
 RPGツクールMVの[プラグインコマンド]は空白区切りで受け付けるので馴染みありますし、@type *[] よりも入力がしやすい面もありますので、なかなかいい方式ですね。
 const arrayParam = pluginParams.param.trim().split( ' ' ) ); って感じ?

構造データの変換

 これも @type struct<*> 以前のプラグインがどう扱っていたのかよく知りません。
 JSON文字列渡して JSON.parse() してたのか、<タグ>で渡して DataManager.extractMetadata() してたのか、はたまた正規表現でゴリゴリ解析して変換していたのか。

noteの変換

 @type note は文字列を渡す型ですが、編集時に改行を入れられることが通常の文字列と異なります。

 また、JSON文字列に変換して渡す特殊な文字列です。
 例えば "{"x":20}" という文字列をJSON文字列に変換すると ""{\"x\":20}"" となります。
 それを JSON.parse() すると "{"x":20}" の元の文字列となって、データ構造オブジェクトにはなりません。
 何か入力して[テキスト]タブに切り替えてみれば、" を \" に、 \ を \\ に、改行を \n に変換して、前後に" を足しているのが分かります。
 @type string は変換せずにそのまま文字列として使えますが、@type note はこのようにJSON文字列化されているのでパースの必要があります。

const noteParam = JSON.parse( pluginParams.param );

 通常は構造データを渡したい場合は @type struct<*> を使うわけですが、あえてJSONデータを生でここに書くという方法もあります。
 その場合は次のように二回パースします。

const noteParam = JSON.parse( JSON.parse( pluginParams.param ) );

 すると ""{\"x\":20}""─ 1回目 → "{"x":20}" ─ 2回目 → {"x":20} と構造データになります。

 なお、JSONはキー(上のコードだとx)をダブルクォーテーション( " )で括らないと正しいフォーマットとみなされず、パース時にエラーを発生させます。
 これは仕様通りの正しい挙動ですが、知らないと「???」となりがちです。

 ちなみに、先ほど紹介した階層化データの変換のスクリプトだと、文字列はそのまま使われ、JSON文字列はパースされるので、両方とも普通の文字列として格納されます。

その他の変換

 他に @type file など沢山のタイプがありますが、入力時のインタフェースが変わるだけで、値の変換に関しては変換せずに文字列のまま使うか、整数に変換するかの2択でいけると思います。

 そこで結論。

標準機能で @type に合わせて変換まで済ませて欲しい

JSON.parse() がそうかもしれないけど…