RPGツクールMV で利用できるプラグインは、JS (JavaScript) ベースで Game Scripting System (JGSS) 環境上で動作します。これは JS 基本知識がある方が対象のプラグイン作成の入門資料っぽい何か、です。
4年ほど前に jgss-hack リポジトリ で公開した内容をもとに、今の環境用にリライトしてみました。これは第2回です。
関連リソース
- 簡単なプラグインを拡張してみる【このページ】
前回のまとめ
前回の 作成入門 (1) では、アクターの名前の後にIDを追加表示する、シンプルなプラグインを作成しました。 プラグインパラメータで最初に表示するかどうかを設定でき、またゲーム中にプラグインコマンドで表示の切り替えができましたね。
今回も引き続き、同じプラグインを拡張していきましょう。
セーブファイルへの状態の保存
前回作成したプラグインにはプラグインコマンドを用意しましたが、まだ欠点がありました。セーブファイルに状態を保存していないため、コマンドで On/Off を切り替えても、ゲームを再開すると初期値に戻ってしまいます。
そこで今回はまず、show_id の値をセーブファイルに保存し、再開しても状態が維持するように拡張してみます。
3種類のセーブファイル
RPGツクールMVで作成したゲームには、save フォルダ内に以下の3種類のセーブファイルが作成されます。
- config.rpgsave
- file*.rpgsave
- global.rpgsave
config.rpgsave
はゲーム設定を保存するためのファイルです。 タイトル画面やゲームメニューにある 'オプション' の状態を保持しています。 例えば以下のオプション画面に対し、
保存されているデータは以下になります。
{
"alwaysDash":false,
"commandRemember":false,
"bgmVolume":100,
"bgsVolume":100,
"meVolume":100,
"seVolume":100,
}
※ 実際にはファイル内容は圧縮されているので、上記は RTK1_Core プラグインの json パラメータを使って出力した結果を利用しました
file*.rpgsave
が実際のセーブファイルで、ゲームの様々な状態が保持されています。 セーブファイルは複数作成でき、*の部分には連番が入ります。 このファイルに格納されている内容については次のセクションで説明します。
global.rpgsave
はセーブファイルの選択画面で利用するデータを格納したファイルで、セーブファイルが作成・更新される時に同時に更新されます。 例えば以下の選択画面に対し、
保存されているデータは以下になります。
[
null,
{
"globalId":"RPGMV",
"title":"Qiita Sample",
"characters":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
"faces":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
"playtime":"00:00:09",
"timestamp":1583078030791
}
]
ゲームのセーブファイルの中身
file*.rpgsave
が実際のセーブファイルだとわかりましたが、どんな内容が保存されているのでしょうか?省略しつつ、主要部分をざっと紹介します。
セーブファイル自体はひとつの大きなオブジェクトなので、要素ごとに見ていきましょう。まずはゲーム全体に関わる system 要素です。
{
"_saveEnabled":true,
"_menuEnabled":true,
"_encounterEnabled":true,
"_formationEnabled":true,
"_battleCount":0,
"_winCount":0,
"_escapeCount":0,
"_saveCount":2,
"_versionId":9628589,
"_framesOnSave":560,
"_bgmOnSave":{"name":"","volume":0,"pitch":0,"pos":0,"@c":3},
"_bgsOnSave":{"name":"","volume":0,"pitch":0,"pos":0,"@c":4},
"_windowTone":null,
"_battleBgm":null,
"_victoryMe":null,
"_defeatMe":null,
"_savedBgm":null,
"_walkingBgm":null,
"@c":2,
"@":"Game_System"
}
この system 要素は、ゲーム中は $gameSystem
オブジェクトに、ほぼそのまま読み込まれていることがわかります。
次は画面周りに関係する screen 要素で、これは $gameScreen
オブジェクトとして読み込まれます。
{
"_brightness":255,
"_fadeOutDuration":0,
"_fadeInDuration":0,
"_tone":{"@c":6,"@a":[0,0,0,0]},
"_toneTarget":{"@c":7,"@a":[0,0,0,0]},
"_toneDuration":0,
"_flashColor":{"@c":8,"@a":[0,0,0,0]},
"_flashDuration":0,
"_shakePower":0,
"_shakeSpeed":0,
"_shakeDuration":0,
"_shakeDirection":1,
"_shake":0,
"_zoomX":0,
"_zoomY":0,
"_zoomScale":1,
"_zoomScaleTarget":1,
"_zoomDuration":0,
"_weatherType":"none",
"_weatherPower":0,
"_weatherPowerTarget":0,
"_weatherDuration":0,
"_pictures":{"@c":9,"@a":[]},
"@c":5,
"@":"Game_Screen"
}
次はタイマー関連の変数を格納する timer 要素で、これは $gameTimer
オブジェクトとして読み込まれます。
{
"_frames":0,
"_working":false,
"@c":10,"@":"Game_Timer"
}
次はイベント関連の変数を格納する variables 要素で、これは $gameVariables
オブジェクトとして読み込まれます。
{
"_data":{"@c":14,"@a":[]},
"@c":13,
"@":"Game_Variables"
}
次は同じくイベント関連のセルフスイッチを格納する switches 要素で、これは $gameSwitches
オブジェクトとして読み込まれます。
{
"_data":{"@c":12,"@a":[]},
"@c":11,
"@":"Game_Switches"
}
次は同じくイベント関連のセルフスイッチを格納する selfSwitches 要素で、これは $gameSelfSwitches
オブジェクトとして読み込まれます。
"selfSwitches":
{
"_data":{"@c":16},
"@c":15,
"@":"Game_SelfSwitches"
}
今回のサンプルではイベントをほとんど定義していませんので、変数やスイッチなどのセーブデータはほぼ空です。今後、イベント関連の話をする際に、もう少し詳しく見ていきましょう。
次はお馴染みの actors 要素で、これは $gameActors
オブジェクトとして読み込まれます。ちょっと長いですが、適時省略しつつ掲載してみます。
{
"_data":{
"@c":18,
"@a":[
null,
{ // 1人目のキャラクターに関するデータ ここから
"_hp":450,
"_mp":90,
"_tp":0,
"_hidden":false,
"_paramPlus":{"@c":20,"@a":[0,0,0,0,0,0,0,0]},
"_states":{"@c":21,"@a":[]},
"_stateTurns":{"@c":22},
"_stateSteps":{"@c":23},
"_buffs":{"@c":24,"@a":[0,0,0,0,0,0,0,0]},
"_buffTurns":{"@c":25,"@a":[0,0,0,0,0,0,0,0]},
"_actions":{"@c":26,"@a":[]},
"_speed":0,
"_result":{
// 省略
},
"_actionState":"",
// 省略
"_actorId":1,
"_name":"ハロルド",
"_nickname":"",
"_classId":1,
"_level":1,
"_characterName":"Actor1",
"_characterIndex":0,
"_faceName":"Actor1",
"_faceIndex":0,
"_battlerName":"Actor1_1",
"_exp":{"1":0,"@c":34},
"_skills":{"@c":35,"@a":[8,10]},
"_equips":{
"@c":36,
"@a":[
{"_dataClass":"weapon","_itemId":1,"@c":37,"@":"Game_Item"},
{"_dataClass":"armor","_itemId":1,"@c":38,"@":"Game_Item"},
{"_dataClass":"armor","_itemId":2,"@c":39,"@":"Game_Item"},
{"_dataClass":"armor","_itemId":3,"@c":40,"@":"Game_Item"},
{"_dataClass":"armor","_itemId":0,"@c":41,"@":"Game_Item"}
]
},
"_actionInputIndex":0,
"_lastMenuSkill":{"_dataClass":"","_itemId":0,"@c":42,"@":"Game_Item"},
"_lastBattleSkill":{"_dataClass":"","_itemId":0,"@c":43,"@":"Game_Item"},
"_lastCommandSymbol":"",
"_profile":"",
"@c":19,
"@":"Game_Actor"
}, // 1人目のキャラクターに関するデータ ここまで
{
// 2人目のキャラクターに関するデータ 省略
},
{
// 3人目のキャラクターに関するデータ 省略
},
{
// 4人目のキャラクターに関するデータ 省略
},
"party":{
"_inBattle":false,
"_gold":0,
"_steps":2,
"_lastItem":{
"_dataClass":"",
"_itemId":0,
"@c":120,
"@":"Game_Item"
},
"_menuActorId":0,
"_targetActorId":0,
"_actors":{"@c":121,"@a":[1,2,3,4]},
"_items":{"@c":122},
"_weapons":{"@c":123},
"_armors":{"@c":124},
"@c":119,
"@":"Game_Party"
},
"map":{
// 省略
},
"player":{
// 省略
}
省略した map や player データに関しては、また別の機会に見ていきましょう。
とまあ長々とお見せしてきましたが、ここで理解しておくべきは「ゲームのセーブファイルとは、$game変数の値を一時的にファイルに保管しておくもの」ということだけ、だったりします。
どの $game 変数を利用するか
また今回も前置きが長くなってしまいました…
これら $game変数はセーブファイルに保存されることがわかりましたから、利用しちゃいましょう!これらのうち一つを選んで値を格納しておけば、ゲームシステムのほうにセーブ/ロード関連は任せられそうです。
個人的によく利用するのは $gameSystem なので、今回もこれを使ってみます。
保存するときの名前ですが、あまり一般的ですと他のプラグインとの競合が心配です。 そこでお勧めの名称は、プラグインの名前そのまま、もしくは名前にデータ種別を追加したものです。
実際の処理
まずは show_id を利用する部分を拡張します。
var _Game_Actor_name = Game_Actor.prototype.name;
Game_Actor.prototype.name = function() {
var ret = _Game_Actor_name.apply(this, arguments);
var f = $gameSystem[N + "_show_id"]; // 足した行
if (f === undefined ? show_id : f) { // 変更した行
return ret + ":" + this.actorId();
} else {
return ret;
}
};
変更はわりと少なめで、1行足して、if文の条件式を変更しただけです。
足した1行は $gameSystem に保存した値を f 変数に読み込む処理です。 名称は N に定義したプラグインの名前に、"_show_id" と保存する変数名を足したというベタなものです。
そしてif文の条件式ですが、保存した値がなければ f が undefined になりますので、代わりにこれまでどおり show_id を使用します。 保存した値があればその値を使用します。
保存した値を利用する部分はこれでOKですが、値を保存する処理がありませんね。 プラグインコマンドの部分を修正しましょう。
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
var ret = _Game_Interpreter_pluginCommand.apply(this, arguments);
if (command == N) {
if (args[0] == "show_id") {
if (args[1] == "on") {
$gameSystem[N + "_show_id"] = 1; // 変更した行
} else if (args[1] == "off") {
$gameSystem[N + "_show_id"] = 0; // 変更した行
}
}
}
return ret;
};
うーん、こちらも修正点は少ないです。 show_id に代入するかわりに、$gameSystem に値を設定しているだけですね。
そして、これでセーブファイル対応は完了です。 1行足して、3行修正しただけで済みました。 簡単でしょ?
修正前は以下のような仕様でした
- show_id の初期値はプラグインパラメータで指定される
- プラグインコマンドで show_id の値が変更される
- show_id の値はセーブファイルに保存されない
修正後は以下のような仕様になっています
- show_id の値はプラグインパラメータで指定され、以後は変更されない
- プラグインコマンドで $gameSystem.RTK_Test_show_id の値が変更される
- 実行時は $gameSystem.RTK_Test_show_id の値が使用される
- ただし見つからない場合には show_id 値がかわりに使われる
show_id 変数が主役から、初期値を表現する脇役に退いたわけです。代わりに $gameSystem.RTK_Test_show_id 変数が使用され、これは $gameSystem に含まれるのでセーブファイルで自動保存される、というわけです。
この仕組みにはメリットがひとつあって、プラグインコマンドを使わない限りはセーブファイルの領域を消費しません。
ひと工夫してみよう
前のセクションで値の保存ができるようになりました。 動作としてはこれで十分です。 このセクションではオマケとして、ちょっとした小さな工夫について述べます。
いま利用している仕組みでは、show_id 変数はプラグインパラメータの値を反映したもの、でしたね。 つまりはゲーム開発時に設定した値になるので、実際にゲームが配布された後に変更されることはないわけです。
この前提のもとで、更にセーブファイルの領域を節約するコードが以下になります。
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
var ret = _Game_Interpreter_pluginCommand.apply(this, arguments);
if (command == N) {
if (args[0] == "show_id") {
if (args[1] == "on") {
$gameSystem[N + "_show_id"] = 1;
} else if (args[1] == "off") {
$gameSystem[N + "_show_id"] = 0;
}
if ($gameSystem[N + "_show_id"] === show_id) { // 追加
delete $gameSystem[N + "_show_id"]; // 追加
} // 追加
}
}
return ret;
};
ちょっと複雑になりましたね。 このコードのコンセプトとしては以下になります。
- 初期値と同じ場合には、わざわざ値を保存する必要はない
- 不要な値はきちんと delete してサイズ削減しよう
今回は単に値一つなので、ここまでこだわる必要は無いかもしれません。ただ「マナーの良いコードを心がける」ことは大事で、常に意識していただきたいため、ひと工夫の例としてここで紹介させていただきました。
真面目にセーブ処理を実施したい場合
今回のサンプルでは、$game変数を利用して、ある意味手抜きをしてしまいました。 ちゃんとした処理を記述したい方のために要点をお伝えしておきます。
まず保存時は、以下のようにセーブデータを作成する処理を拡張すると良いでしょう。
var _DataManager_makeSaveContents = DataManager.makeSaveContents;
DataManager.makeSaveContents = function() {
var contents = _DataManager_makeSaveContents.apply(this, arguments);
// ここで自分のデータを contents に設定する
return contents;
};
そして設定したデータを読み込むために、以下のようにセーブデータを処理する処理を拡張すると良いでしょう。
var _DataManager_extractSaveContents = DataManager.extractSaveContents;
DataManager.extractSaveContents = function(contents) {
var ret = _DataManager_extractSaveContents.apply(this, arguments);
// ここで自分のデータを contents から読み込む
return ret;
};
プラグインに機能を追加してみよう
せっかくですから、プラグインにもう少し機能を追加してみましょう。 アクターだけでなく、敵の名称にも ID を表示させてみてはどうでしょうか?
戦闘中、コンソールでクラスを探ってみます。 $game変数のなかで $gameTroop がそれっぽいので、起点にしてみましょう。
むむ、以前と違って素直にコードを追えませんね。仕方ないので、この部分のコードを検索してみると、rpg_object.js ファイルの4408行目あたりにありました。
Game_Enemy.prototype.name = function() {
return this.originalName() + (this._plural ? this._letter : '');
};
plural
って何だろう?とソースファイルの中を検索してみると、5279行目あたりに以下のようなコードがありました。 グループ(Troop)内に同じ名前があると true がセットされるようです。 あれですね、同じ敵がいる時に A とか B とか名前の後につくやつだろうと想像できます。
this.members().forEach(function(enemy) {
var name = enemy.originalName();
if (this._namesCount[name] >= 2) {
enemy.setPlural(true);
}
}, this);
さて、敵の name 関数を拡張していきましょう。 まずはコードを追加せずに拡張の下準備だけしてみます。
var _Game_Enemy_name = Game_Enemy.prototype.name;
Game_Enemy.prototype.name = function() {
var ret = _Game_Enemy_name.apply(this, arguments);
// ここに処理を記載
return ret;
};
時間があるときは、この状態でゲームをテストプレイすることをお勧めします。 自分のコードをデバッグする前に、拡張することでゲームに何か悪影響が出ないか、そういった心配を先に潰しておくわけです。
いや、あるんですよ、実際。 何時間も悩んだ結果、この簡単な第一歩でスペルミスとかで失敗してて、肝心のコードが動いてなかった、なんて間抜けなことが… いや、僕だけかもしれませんが。
では、実際のコードを追記しましょう。 まあ、アクターの場合とほぼ同じなのですが…
var _Game_Enemy_name = Game_Enemy.prototype.name;
Game_Enemy.prototype.name = function() {
var ret = _Game_Enemy_name.apply(this, arguments);
var f = $gameSystem[N + "_show_eid"];
if (f === undefined ? show_eid : f) {
return ret + ":" + this.enemyId();
} else {
return ret;
}
};
一応、扱う変数を show_eid と変えてみました。 ですのでプラグインパラメーター、およびプラグインコマンドの部分も拡張が必要ですね。
まあアクターとほぼ同じですので、コードの説明は省きます。 最後にコードを全部置いておきますので、そちらで追加されたコードを確認してみてください。実行すると、以下のように敵キャラにもID表示されます。
プラグインを完成しよう
さて、これまで説明してきたプラグインですが、キリも良いのでいったん完成させましょう。
名前も 'RTK_Test' のままでは格好悪いですね。 'RTK_ShowID' とでも名付けておきますか。 いや、そんな格好良い名前を貰えるほどのプラグインじゃないんですけどね… あ、ファイル名の変更の際、最初の N 変数の定義も変更することを忘れないでください。
ヘルプなども不十分 (プラグインコマンドはありません、と書いたままですが、実際はありますよね) なので、いろいろ表記を追加・変更します。
//=============================================================================
// RTK_ShowID.js 2020/03/03
// The MIT License (MIT)
//=============================================================================
/*:
* @plugindesc アクターとエネミーにID表示するプラグイン (プラグイン作成講座のサンプル)
* @author Toshio Yamashita (yamachan)
*
* @param アクター名の後にIDを表示
* @desc アクター名の後にIDを表示する (0:OFF 1:ON)
* @default 1
*
* @param エネミー名の後にIDを表示
* @desc エネミー名の後にIDを表示する (0:OFF 1:ON)
* @default 1
*
* @help
* プラグインコマンド:
* RTK_ShowID show_id on
* RTK_ShowID show_id off
* RTK_ShowID show_eid on
* RTK_ShowID show_eid off
*
* アクターとエネミーにID表示するプラグインです
*
*/
(function(_global) {
var N = 'RTK_ShowID';
var param = PluginManager.parameters(N);
var show_id = Number(param['アクター名の後にIDを表示'])||0;
var show_eid = Number(param['エネミー名の後にIDを表示'])||0;
var _Game_Actor_name = Game_Actor.prototype.name;
Game_Actor.prototype.name = function() {
var ret = _Game_Actor_name.apply(this, arguments);
var f = $gameSystem[N + "_show_id"];
if (f === undefined ? show_id : f) {
return ret + ":" + this.actorId();
} else {
return ret;
}
};
var _Game_Enemy_name = Game_Enemy.prototype.name;
Game_Enemy.prototype.name = function() {
var ret = _Game_Enemy_name.apply(this, arguments);
var f = $gameSystem[N + "_show_eid"];
if (f === undefined ? show_eid : f) {
return ret + ":" + this.enemyId();
} else {
return ret;
}
};
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
_Game_Interpreter_pluginCommand.apply(this, arguments);
if (command == N) {
if (args[0] == "show_id") {
if (args[1] == "on") {
$gameSystem[N + "_show_id"] = 1;
} else if (args[1] == "off") {
$gameSystem[N + "_show_id"] = 0;
}
if ($gameSystem[N + "_show_id"] === show_id) {
delete $gameSystem[N + "_show_id"];
}
}
if (args[0] == "show_eid") {
if (args[1] == "on") {
$gameSystem[N + "_show_eid"] = 1;
} else if (args[1] == "off") {
$gameSystem[N + "_show_eid"] = 0;
}
if ($gameSystem[N + "_show_eid"] === show_eid) {
delete $gameSystem[N + "_show_eid"];
}
}
}
};
})(this);
これでプラグイン開発に関して、最低限の説明はできたとおもいます。 次回以降はTipsとか小ネタをまとめたものになるかも。 いろいろプラグインを作成してみて、楽しんでください!
ではまた!