(0004)特徴の追加作戦の続報
概要
(0002)特徴の追加作戦で問題点を認識していながら解決できていなかった部分を解決できました。
あと、当時はコアスクリプトを直接いじるという暴挙に出ましたが、そんなことをしなくてもいいことが判明しました。
コアスクリプトを直接いじらなくてもよかった
前回コアスクリプトをいじるという暴挙に出ましたが、実は公式さんのプラグイン講座「公式プラグインを読み解く」のページに「既存メソッドの再定義」という項目があります。プラグインを触り始めた当初は意味が理解できず、スルーしていましたが、これが超重要でした。
<引用ここから>
プラグインは、そのコアスクリプトが定義する関数を『再定義』することで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』の元の処理を呼び出したあとで、新しくプラグイン作者が追加したい処理を追加しています。
このようにして関数の再定義を繰り返しつつ、プラグインは作られていきます。
<引用ここまで>
イメージとしては、元々ある「ツクールデパート」という建物をそのまま「旧ツクールデパート」という建物に入れて、もっと大きな建物を建設して名前を「ツクールデバート」にし、その建物の中に「旧ツクールデパート」を入れて、残りのスペースで+αのことをする…みたいな感じですかね。「ツクールデパート」を訪れると、元々のツクールデパートの内容(ここでは旧ツクールデパート)も丸々あって、追加されたものもその流れで見れるという感じ…。
わかりづらいですよね、すみません…
で、話を戻し、なんでコアスクリプトをいじるという話になったのかを振り返ると、セーブデータにゲーム中の$dataActorsの内容が保存されず、$dataActorsはゲーム起動時にデータベースの情報から毎回生成されるから、ゲーム中にイベントコマンドでいじっても戻っちゃうという課題からでした。
というわけで、コアスクリプト内のDataManager.makeSaveContentsというメソッドを直接いじってゲーム中のdataActorsの情報をセーブデータに忍び込ませ、同じくコアスクリプト内のDataManager.extractSaveContentsというメソッドを直接いじってセーブデータ内から$dataActorsを読み込んでました。
ここで先述の既存メソッドの再定義を使うと、プラグイン側でDataManager.makeSaveContentsとDataManager.extractSaveContentsを再定義してやることで、コアスクリプトを直接いじる必要がなくなりました。
コアスクリプトに直接手を入れるのは悪手なので、皆さんもできる限りやらないようにしましょう。
既存メソッドの再定義の弱点?
ただ、ここで一つ問題が発生しました。上記の既存メソッドの再定義では、コアスクリプトに記載されている本来の処理の後にしか処理を追加できません。
「3行目で変数『_Game_Picture_show』に代入していた関数を実行」の前の行に処理を挿し込めば、本来の処理の前にも処理は追加できますが、本来の処理の中に処理を追加することは、少なくとも私の知識ではできません(方法をご存じの方がいらっしゃったら教えてください…)。
そのため、本来の処理を実行するのではなくコピペして、間に$dataActorsを保存、読み込む処理を挿し込んだもので置換する方式を採らざるを得ませんでした。
ニューゲームだと$dataActorsが更新されない問題
さて、問題の核心に迫っていきます。何が問題だったかというと、$dataActorsの変遷として、
【セーブ/ロードの場合(問題なし)】
ゲーム起動後データベースの情報が読み込まれて$dataActorsが生成される
ゲーム中にプラグインで$dataActors内の特徴が変化する
セーブするとセーブデータに$dataActorsの情報も書き込まれる
ロードするとセーブデータ内の情報から$dataActorsを再現して問題なし
【ニューゲームを再度選択した場合(問題あり)】
ゲーム起動後データベースの情報が読み込まれて$dataActorsが生成される
ゲーム中にプラグインで$dataActors内の特徴が変化する
セーブするとセーブデータに$dataActorsの情報も書き込まれる
タイトルに戻る($dataActorsは変化なし)
ニューゲームを選択する($dataActorsは変化なし)
ニューゲームなのに$dataActorsが変化したままになっているので問題あり
という感じになります。意図せず特徴が変化した状態ではじめからプレイできてしまうわけですね。
処理として問題になってくるのは、ロードの場合はその処理があったので、その処理を前述の方法で上書きし、$dataActorsをセーブデータから読み込んだ内容で上書きする処理を挿し込めましたが、ニューゲームの場合はデータベースから$dataActorsを作成するような処理がそもそも見当たらず、処理自体が無いので上書きする方法も使えないという点でした。
データベースの情報を取得するメソッドは見つけられた
で、いろいろ試行錯誤やコアスクリプトを読んだりした結果、データベースから情報を読み込んでいるメソッドを見つけました。rmmz_manager.js内のDataManager.loadDataFileというメソッドです。ここでよくわからんとなってしまったのがxhrでした。xhrはXML Http Requestの略で…(以下略)。
取り敢えずこれを使ってdataフォルダー内のActors.json等、データベースの内容をJSON形式で保存してあるファイルの内容を読み取っているようです。
ここで個人的に凄く疑問だったのが、メソッドが
xhr.send();
で終わっていることです。xhrは一行目で
const xhr = new XMLHttpRequest();
と書いてあるので、XMLHttpRequestのオブジェクトですね。
※補足
newは演算子の一つで、ざっくりいうと設計図を基に実体(オブジェクト)を作る処理をしてくださいという意味です。
「newする」という、プログラムを触っている人々には非常に聞き馴染みのある、逆に触っていない人々には「はあ?newは動詞じゃねぇしwww」と思われてしまう言葉がありますw
(補足ここまで)
で、sendは英単語で「送る」という意味ですよね。これでこのメソッドは終わりです。
…え?送って終わり?リクエストって、「情報くれよ~」でしょ?要求だけして処理なし?データベースの情報取得した後何もしないの???
と大混乱の中その日は就寝しました。
翌日、ちょっとこのXMLHttpRequestが気になってググってたところ、MDN Web Docsに情報がありました。JavaScriptを触る方々には御用達ですね。
結論から申し上げますとコールバック方式でした。
コールバックというのは、先にうまくいった場合はこうする、うまくいかなかった場合はこうすると先に決めておくようなイメージです。
例えば…
【当初の想定のイメージ】
おつかいを頼む
↓
買ってきてもらったものを使って何かする(豆腐を冷蔵庫に入れる等)
【コールバックのイメージ】
おつかいを頼むときにメモに
「スーパーで豆腐を買ってきて冷蔵庫に入れておいて。売り切れだったら電話して」
と書いて渡す
みたいな感じでしょうか。
この例でいうと、「豆腐を冷蔵庫に入れる」処理を、おつかいから帰ってきたあとのところだけを見て探していたので見つからなかったわけです。
スクリプトに戻ると、xhr.send();の前にxhr.onload = () => 以下略という箇所がありました。ここでXMLHttpRequestの処理がうまくいった場合にどうしてほしいかを書いているようです。コアスクリプトではDataManager.onXhrLoadというメソッドをcallしてますね。
どうする?
少し話が戻りますが、ニューゲームの際に$dataActorsがデータベースの情報から生成or更新される処理が実行されていないことが今回の問題点でした。つまり、DataManager.loadDataFileを上書きしてあ~だこ~だしても、そもそも発火しないので意味がありません。
というわけで、似たメソッドをプラグイン内で用意して、別途データベースの情報を読み込むことにしました。
DataManager.loadDataFileでは、引数としてnameとsrcを取っており、それは前回のセーブデータに情報を挿し込んだ際にも触れたDataManager._databaseFilesで定義されていました。今回はActorの情報があれば充分なので、
nameは"$dataActors"、
srcは"Actors.json"
に置き換えてみました。
加えて、前述の通り、コールバックで取得した情報をどうするのかを記述するので、xhr.onloadで取得したデータベースの情報、特に今回は必要なのは特徴の情報なので、$dataActors[アクターのID].traitsを上書きすることにしました。
これを初期化というプラグインコマンドで呼び出すようにして、ニューゲーム開始直後に実行することで、別データで特徴を変更していても、ニューゲームを選択したら必ずデータベースの内容で上書きすることで戻せるようになりました。
プラグインユーザーの方にとってはひと手間必要になってしまいましたが、私のプラグイン作成能力ではこれが限界ということで…。
他の自作プラグインとの併用における問題
これまでの内容で一旦目的を果たせるプラグインにはなりましたが、別のプラグインでアイテムの使用効果を追加/削除するプラグインや、価格を動的に変化させるプラグイン等も作っています。
セーブデータに$dataActorsの情報を挿し込む処理とセーブデータから$dataActorsの情報を読み込む処理を追加しましたが、これらのプラグインにもセーブデータに
$dataItemsの情報を挿し込む処理とセーブデータから$dataItemsの情報を読み込む処理
が必要になりました。
「既存メソッドの再定義の弱点?」で記載した通り、この処理は本来の処理に処理を追加しているのではなく、処理そのものを置換しています。
ということは…、
このプラグインを読み込んだ後にアイテムの使用効果を追加/削除するプラグインを読み込むと、$dataActorsの保存/読み込む処理を挿し込んだしたものが$dataItemsの保存/読み込みを行う処理を挿し込んだもので置換されてしまい、$dataActorsが保存されなくなってしまいます。
というわけで、仕方なく$dataActorsと$dataItemsの両方を保存/読み込むようにしました。
その結果、両方のプラグインを読み込むと、セーブ・ロードの処理が$dataActorsと$dataItemsの両方を保存/読み込む処理を追加したものに2回置換されることになるんですよね。それもイマイチだなぁと思い、結局トリアコンタンさんのPluginCommonBaseのように、別のプラグインに置換を移管し、併用する方式にしました。
これですっきりしましたね。
ただ、置換にしたことによる不安はまだあり…。勘の良い方はお気づきかもしれませんが、他の作者さんのプラグインでDataManager.loadDataFileに手を入れているものがあると、恐らく競合するんですよね…。
というわけで、折角完成しましたが、公開していいものかどうか悩んでます。
ちょっとしたぼやき
いくつかプラグインを自作しましたが、アイデアが浮かんだときはテンションが上がり、需要あるかも?公開しようかな?へへへってなりながら作るものの、問題点が見つかり、打開策を講じているうちに「自分で使う分にはいいけど、人様に使ってもらうにはなぁ…」という感じになってしまい、こうやって工程を公開するだけに留まってしまっていたりします。それも毎回。なんか淋しいですね(-_-)
もし、「それでも使ってみたいよ~」や、「こうやったら既存メソッドの再定義の弱点のところ打開できるよ~」というお声がありましたら、Twitterの方にご連絡をいただけますと幸いです。
ではでは。