この記事はツクールフォーラムアドベントカレンダー2018の21日目の記事です。
はじめましての人もそうでない人もこんにちはこんばんは。aoitaku(あおいたく)と申します。ゲーム制作サークル Lunatlazur(るなとらじゅーる)でドット絵を打ったりプログラムを組んだりしています。
きょうはRPGツクールMVのプラグインを作るときに考えていることについて書いていきます。
ちいさなプラグインを作る
プラグインを作るときは、ひとつの目的に絞って、単機能のちいさなものにすることを意識して作るようにしています。
2点うれしいことがあります。
- 機能が少ないと設定が簡単になる
- プラグインがちいさいと他のプラグインと競合したときに原因を把握しやすい
1つめ。機能が少ないと設定が簡単になる。 「多機能プラグインのこの機能だけ使ってます、他の設定項目はよくわからないので……」 という人も少なからずいると思いますが、そういう人こそ単機能のちいさなプラグインをおすすめしたいです。 他の機能がほしくなったら? そのときは、その機能のためのちいさなプラグインを導入すればよいのです。
2つめ。 大きなプラグインと大きなプラグインの競合が起きたとき、どうやって解決しますか? とりあえず片方のプラグインを止めれば、競合は起きなくなります。 競合が起きてる箇所の特定は……プラグイン開発者でも困難なので、ユーザにとってもっと困難なのは言うまでもありません。
機能が小さければ競合が起きないかというと、そんなことはありません。ありませんが、原因の特定は簡単になります。3000行のプラグインと300行のプラグインだったら、後者のほうが中身を把握しやすいことが多いです。中身を把握しやすければ、それだけ原因の特定が簡単になります。
もちろん、やむをえず巨大なプラグインになってしまうことはあります。ちいさくできるときはちいさくまとめましょう、という感じです。
むやみにグローバルに名前を生やさない
名前というのは、たとえばクラス名とか関数名のことを言います。もちろん変数名もそうですが、プラグインがイベントコマンドのスクリプトやほかのプラグインに対して名前を公開したいケースというと、たぶん大抵はクラス名か関数名になるでしょう。
「そのクラスや関数、本当にスクリプトやほかのプラグインから利用できるように公開する必要がありますか?」という話をします。
JavaScript では、トップレベルに定義したクラスや関数はグローバルオブジェクトに対するプロパティの定義になります。これをグローバルに名前を生やすと呼ぶことにします。
たとえば、RPG の中に横スクロールアクションのミニゲーム用の独自のシーンを作るとして、Scene_ActionMiniGame みたいなクラスを作るとする。
class Scene_ActionMiniGame extends SceneBase {
// ここに定義
}こうすると、Scene_ActionMiniGame はグローバルにアクセスできるので、たとえばイベントコマンドのスクリプトから Scene_ActionMiniGame を生成したりできます。
ここでちょっと考えてほしいのですが、本当にスクリプトから Scene_ActionMiniGame を生成したりする必要がありますか?
Scene_ActionMiniGame のようなわかりやすい名前は、ほかのプラグインと衝突する可能性を秘めています。かといってわかりにくい名前にすることはそれはそれで問題です。名前空間を作ってその内側に閉じ込める。それでもいいかもしれません。
でも、大抵の場合はそのような自由な使い方を想定しなくてもよく、単に Scene_ActionMiniGame に遷移するための API が公開されているだけでいいはずですね。
(function () {
class Scene_ActionMiniGame extends SceneBase {
// ここに定義
}
const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand
Game_Interpreter.prototype.pluginCommand = function(command, args) {
// ここにシーン遷移用のプラグインコマンドを書く
}
)()プラグインコマンドがあれば充分です。イベントコマンドのスクリプトから自由に操作しようとしてできるようなユーザは、プラグインを書き換えられるレベルなので、ツクールのプラグインに関しては、そこまでプログラマフレンドリーなインターフェースを晒しておかなくても大丈夫です。
また、プラグインコマンドはイベントからだけでなくほかのプラグインからも呼べます。イベントから呼ぶのとは挙動が異なるので実装には注意が必要ですが、このほうが必要なプラグインが存在しなかったときの処理を制御しやすいと思います。
プラグイン間で設定やデータを共有したいことはあるかもしれません。その場合でも、ゲーム変数があるのでそれを使うのが望ましいです。 共有する変数番号はハードコードせず、プラグインパラメータとして設定できるようになっているのがよいでしょう。
オーバーライドは節度をもってやる
たいていの場合、関数のエイリアスを定義して、あたらしく作った関数内でエイリアスされたオリジナルの関数を呼び出しながら、追加の処理を行うという感じで拡張するのがよいです。 既存の処理の前後になにかしら処理を追加するならこれでいいのですが、既存の処理の途中に書き換えたい部分があるときはこれではできないので困ります。で、オーバーライドする。
ちょっと待ってください。
本当にそれ、オーバーライドしないとダメですか?
元の関数をエイリアスしておいて、関数内でなにかしらのフラグを見て、フラグが立っていたら新規の処理を、フラグが立ってなかったらオリジナルの処理にフォールバックするようにすることで、既存の処理を破壊せずに書き換えることができます。
フラグには気をつける必要がありますが、プラグインコマンドで ON/OFF を切り替えるものだと、プラグインコマンドでプラグインを有効化しない限りは既存の処理を行う、ということができるため、安心感があります。
これでも競合を避けられるわけではありませんが、少なくとも「この機能のためにこの関数を書き換えている」ということがコードから読み取れるようになるので、無闇にオーバーライドするよりはずっといいと考えています。
エイリアスにつける名前に接頭辞は特になくていい
無名関数でラップして局所化しているので、どんな名前をつけても衝突することはありません。
const _SomePlugin_Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand
Game_Interpreter.prototype.pluginCommand = function(command, args) {
// …
}_SomePlugin_Game_Interpreter_pluginCommand の _SomePlugin の部分は不要です。
const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand
Game_Interpreter.prototype.pluginCommand = function(command, args) {
// …
}これで OK です。
プラグインコマンドは「自分で消費しなかったら次にパススルーする」ほうが健全
プラグインコマンドを書くとき、だいたいこんなふうに
const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand
Game_Interpreter.prototype.pluginCommand = function(command, args) {
_Game_Interpreter_pluginCommand.apply(this, arguments)
switch (command) {
// …
}みたいな感じで、まず他のプラグインで定義されている pluginCommand を叩いてから自身の pluginCommand の処理を行う、という感じでやることが多いです。ぼく自身そうしています。
なんですけど、これってたとえば同じ名前のプラグインコマンドがあったときに多重に実行されたりしませんか?
多重に実行されても構わないようなコマンドならそれでもいいのかもしれませんが、基本的にプラグインコマンドは被らない前提のような気がしています。
それなら「まず自分でプラグインコマンドのマッチ処理を行って、マッチしなかったら既存の pluginCommand を叩く、というほうが健全なのではないか?」ということを最近考えています。
const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand
Game_Interpreter.prototype.pluginCommand = function(command, args) {
switch (command) {
case 'コマンド名':
// なにか処理して関数から抜ける
return
// …
}
// ここまでで return してなければ既存の pluginCommand を呼ぶ
_Game_Interpreter_pluginCommand.apply(this, arguments)
}プラグインコマンドの名前が衝突したときは先に呼ばれたほうが勝つようになります。 どちらか片方のプラグインコマンドが無視されますが、無視されることによって、意図しない挙動は回避できるわけです。どちらがいいのかはときと場合に寄りそうですが、個人的にはこっちのほうが制御しやすい気がしています。
ライセンス MIT が推奨されてるけど個人的には Zlib がオススメ
MIT ライセンスなのにライセンスの全文またはパーマリンクを含まないで配布してる人はいないとは思いますがそれは MIT ライセンスでは正しくないのですぐやめましょう。
Zlib の場合、原著作者を詐称してはいけない、改変物であることを明示すれば、改変物に原著作者のクレジットを必ずしもいれなくてもよく、オリジナルのソースコードの入手先を示しさえすればよいです。ぼくはこのゆるさが気に入っているので Zlib にしています。 そこまでゆるくしたくない人にとって、MIT はちょうどいいライセンスだと思います。
おきもち
プラグインを書くのは難しいけど楽しいのでどんどん書きましょう。
宣伝
Lunatlazur で作ったゲームと、ゲーム用に作ったプラグインを公開しています。興味がある人は見てみてください。