プラグインにはもちろん様々な書き方がありますが、ここでは公式プラグインの『TextPicture.js』をお手本にして、一般的なプラグインの記述法について解説します。
お手本の実装を見てみると、コメント以外のほぼ全ての処理が以下の記述の中に含まれていることが分かります。
(() => {
// 全ての処理がここに記述されている
})();
カッコが多く複雑に見えますが、やっていることは『関数を定義してその場で実行する』だけです。
すぐに実行するのにわざわざ関数を作って処理を記述するのは以下の二つの理由があります。
前章で説明した通り、関数の外で変数を宣言するとその変数は『グローバル変数』になります。
グローバル変数は他のプラグインも含めた全ての箇所から参照できるため、もし複数のプラグインで同じグローバル変数を定義してしまうと、競合の原因になります。
グローバル変数は本当に必要なもの以外はみだりに定義しないのが基本原則です。
関数内で変数を定義することで変数のスコープ(有効範囲)をプラグイン内だけに限定できます。
use strictについては、お手本の公式プラグインには含まれていませんが、記述しておくとバグの原因になりやすい記述を事前に教えてくれるのでオススメです。
少し変わった使い方ですが、以下のように関数の先頭に文字列『use
strict』だけを定義します。
(() => {
'use strict';
// do something
})();
たとえば、変数を宣言せずに使おうとするとグローバル変数として扱われてしまいますが、use strictが有効になっていると、エラーとして止めてくれます。
次にお手本のコメント部分を見てみましょう。
MZのプラグインでは『/*:』から始まる部分は特別なコメントとして扱われます。
コメントには@から始まる記述がいくつか見られると思いますが、これらはMZのエディタにヘルプ情報として読み込まれ解析されます。
これを『アノテーション』と呼びます。
/*:
* @target MZ
* ....
*/
アノテーションには多数種類があり、次の章で解説しています。
いよいよ実装部分の解説に入っていきます。
コアスクリプトが膨大な関数の集まりであることは以前に解説しました。
プラグインは、そのコアスクリプトが定義する関数を『再定義』することでMZのゲーム動作の変更を可能にします。
以下が再定義の基本的な書き方です。
const _Game_Picture_show = Game_Picture.prototype.show;
Game_Picture.prototype.show = function() {
_Game_Picture_show.apply(this, arguments);
// do something
};
まず、1行目で、変数『_Game_Picture_show』を宣言し、コアスクリプトの関数『Game_Picture.prototype.show』を代入しています。
そして2行目で関数『Game_Picture.prototype.show』を新しく定義し直します。
最後に、3行目で変数『_Game_Picture_show』に代入していた関数を実行しています。
applyというのは変数に代入していた関数を実行するための関数(メソッド)です。
この一連の処理により何が行われるかというと、コアスクリプトの関数『Game_Picture.prototype.show』の元の処理を呼び出したあとで、新しくプラグイン作者が追加したい処理を追加しています。
このようにして関数の再定義を繰り返しつつ、プラグインは作られていきます。
関数を呼び出すための関数
先ほどapplyという関数の説明をしたとき「関数の実行は小カッコじゃないのか」と思ったかもしれません。たしかに『_Game_Picture_show()』でも実行はされるのですが、ある理由によりこれではエラーが起きてしまいます。
エラーが起きる理由は、オブジェクト指向やJavaScriptに対する充分な理解が必要なため、残念ながらここでは割愛します。
MZではプラグインコマンドの仕様が刷新されました。
MVではコマンド名も引数も直接入力でしたが、MZではパラメータと同じ記法に従って、アノテーションでコマンド名や引数の情報を定義します。
そして実際のコマンド処理は以下のように記述します。
PluginManager.registerCommand("プラグイン名", "コマンド名", args => {
// ここにコマンド実行時の処理を記述
});
第一引数にプラグイン名、第二引数にコマンド名、第三引数にコマンド実行時に呼び出される関数を指定します。
第三引数の関数の引数『args』には、コマンドを呼び出すときに指定したパラメータがオブジェクト形式で格納されます。
前作、MVでは配列で渡されていたのでここも変わっています。
お手本では、以下のように使われています。
const pluginName = "TextPicture";
let textPictureText = "";
PluginManager.registerCommand(pluginName, "set", args => {
textPictureText = String(args.text);
});
もし、Game_Interpreterのメソッドを呼び出したい場合、アロー関数を使わずに関数を記述したうえで、thisを付けて呼び出します。
PluginManager.registerCommand("プラグイン名", "コマンド名", function(args) {
this.character(0);
});
クロージャについて
お手本のコマンド処理では、関数の外側で定義された変数『textPictureText 』の値を書き換えていました。これは一見すると奇妙に思えますが『クロージャ』という仕組みによって成立しています。
textPictureTextのスコープは定義されたブロックの範囲内ですが、それは入れ子になっている関数内も含まれます。
これは関数を定義した時点でスコープが決定される(レキシカルスコープ)仕様のためです。
仕組みについてはともかく、この仕様はプラグインを制作するうえで大いに有用です。
letとconstの使い分け
お手本で使っている変数『textPictureText』は『let』で定義されていますが『pluginName』は『const』で定義されています。
constもletと同様に変数を宣言するときに使いますが、letと異なり再代入ができません。
textPictureTextがletで定義されているのは、再代入をしたいためです。
コアスクリプトでは原則としてconstが使われ、再代入をしたい場合のみletが使われています。
このように適切に使い分けることでコード制作者の意図が伝わりやすくなります。
関数『createTextPictureBitmap』の中に、以下のような記述があります。
const tempWindow = new Window_Base(new Rectangle());
この『new』という記述はオブジェクトを生成したいときに使います。
ですが、以前にオブジェクトの生成について以下のように記述すると説明しました。
const tempWindow = {};
{}で作成すると空っぽのオブジェクトが生成されます。
一方、newを使ったオブジェクトの生成は、簡単に説明すると『あらかじめ決めておいた設計図に従って、プロパティや関数が定義された状態で』オブジェクトを生成するものです。
上記の例では、設計図としてWindow_Baseを指定しているので、tempWindowは最初からウィンドウとしての機能を備えた状態で生成されます。
コアスクリプトは関数の集合体だと解説しましたが、実際にはこの設計図の集まりと言った方が適切かもしれません。
コアスクリプトでもnewは多用されているので、ここで使い方を覚えておきましょう。
この設計図はオブジェクト指向において『クラス』と呼ばれます。
ここまで説明したところで、ようやくTextPicture.jsの具体的な実装について解説できます。
このプラグインは、プラグインコマンドで指定した文字列をピクチャとして表示するものです。
では、どのようにして実現しているか解説します。
まず、プラグインコマンドの実装です。
ここではsetというコマンドを実行したときに変数『textPictureText 』にピクチャとして描画したい文字列を保存しています。
let textPictureText = "";
PluginManager.registerCommand(pluginName, "set", args => {
textPictureText = String(args.text);
});
次にピクチャ表示処理に追加します。
ピクチャを表示しているメソッドは『Game_Picture.prototype.show』です。
具体的な実装を知らなくてもなんとなく名前から分かると思います。
const _Game_Picture_show = Game_Picture.prototype.show;
Game_Picture.prototype.show = function() {
_Game_Picture_show.apply(this, arguments);
if (this._name === "" && textPictureText) {
this.mzkp_text = textPictureText;
this.mzkp_textChanged = true;
textPictureText = "";
}
};
ここで追加した処理は、ピクチャ名が空(画像を『なし』で指定)でかつ、プラグインコマンド実行済みだった場合に、描画文字列を保持してフラグを立てる、です。
ポイントは『この時点では描画はしていない』ということです。
というのもコアスクリプトでは、ピクチャの状態を保持するクラス(Game_Picture)と、ピクチャ画像自体のクラス(Sprite_Picture)は別に存在しているからです。
ピクチャ画像自体のクラスでは、毎フレームGame_Pictureの状態を監視しています。
このクラスに限らず、updateという名前のついたメソッドは毎フレーム実行されて状態を確認、更新するメソッドである可能性が高いです。
const _Sprite_Picture_updateBitmap = Sprite_Picture.prototype.updateBitmap;
Sprite_Picture.prototype.updateBitmap = function() {
_Sprite_Picture_updateBitmap.apply(this, arguments);
if (this.visible && this._pictureName === "") {
// 描画が必要かどうか判定する処理
} else {
this.mzkp_text = "";
}
};
実際の判定処理は少し長いのでコードを確認してください。
実装を簡単に説明すると、Game_Pictureの内容を監視して、必要と判断したら描画と破棄を実行しています。
描画と破棄は、それぞれ以下の関数で実装されています。
破棄は正しく動かすだけなら必ずしも必要ありませんが、画像のオブジェクトはメモリ使用量が大きいので、不要になったタイミングで破棄することで安定した動作が期待できます。
createTextPictureBitmap
destroyTextPictureBitmap
SpriteとBitmap
先ほど、『Sprite_Picture』をピクチャ画像そのもののクラスと説明しましたが、正確には画像を入れる箱に近い存在です。
この箱のことを『Sprite』と呼びます。
一方、画像自体のクラスは『Bitmap』です。
Spriteはプロパティとして『bitmap』を持ち、ここにBitmapのオブジェクトが格納されます。
Spriteは、画像の表示位置や拡大率、色調などの情報を保持し、Bitmapは単純にX座標Y座標ごとの色情報を保持します。