スクリプトでパーティ交代イベントを作りたい

アバター
much
記事: 62
登録日時: 2017年11月09日(木) 01:01

スクリプトでパーティ交代イベントを作りたい

投稿記事by much » 2024年2月03日(土) 03:21

RPGツクールMVでスクリプトを作成して以下の使用のようなプラグインを作成したいです。

仕様:

・プラグインコマンドで「MemberChange」を実行することで処理が実行される

処理は以下に示す通り
・「交代する人物を選択してください」とメッセージで表示する
・現在のアクターを選択肢で表示する
・「誰と交代しますか?」とメッセージを表示する
・対象アクターを選択肢で表示する
・アクターを入れ替える。

対象アクターとは以下のアクターを指す
・データベースのアクターメモ欄に「<MemberChange>」と記載のある人物

質問

現在以下の部分まで、実装できています。
①プラグインコマンドを実行すると処理が実行される
②現在のアクターを選択肢で表示する

質問は以下の通りです。
・メッセージを表示するにはどうすればいいですか?
・アクターのメモ欄を参照するにはどうすればいいですか?
・ソースコードで修正すべき点があれば教えてください。

コード: 全て選択

(function () {
    var isMemberChange = false
    var nowMembers = [];
    var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function (command, args) {
        _Game_Interpreter_pluginCommand.call(this, command, args);
        if (command === "MemberChange") {
            //「誰を交代しますか?」を表示する
            nowMembers = $gameParty.members();
            const names = [];
            nowMembers.forEach(member => {
                names.push(member._name)
            });
            this.setupChoices([names, 100, 0, 2, 0]);
            isMemberChange = true;
        }
    };

    var _Game_Message_On_Choice = Game_Message.prototype.onChoice;
    Game_Message.prototype.onChoice = function (n) {
        if (isMemberChange) {
            //「誰と交代しますか?」を表示する
        }
        else {
            _Game_Message_On_Choice.call(this, n);
        }
    };
})();

アバター
Plasma Dark
記事: 669
登録日時: 2020年2月08日(土) 02:29
連絡を取る:

Re: スクリプトでパーティ交代イベントを作りたい

投稿記事by Plasma Dark » 2024年2月03日(土) 10:19

ひとつのプラグインコマンドに機能を詰め込みすぎて実装が複雑化するので、シンプルな実装を複数組み合わせてみると良いと思います。

例えば、以下のように分割できそうです。
- アクターを選択して、選択したアクターのIDを指定の変数に入れるだけのプラグインコマンド
- アクターIDを保存した変数を2つ指定して、パーティメンバーを入れ替えるだけのプラグインコマンド

メッセージの表示はデフォルトの文章の表示イベントコマンドがあるので、そちらを使ったほうがバグりにくくて良いんじゃないでしょうか。


とりあえずご質問に答えていくと

メッセージを表示するにはどうすればいいですか?


コード: 全て選択

$gameMessage.add('文章');


これを使えばある程度は解決できます。
ただし、選択後の表示に関しては onChoice をフックしても解決しないでしょう。
選択肢ウィンドウクラスで決定キーを押したときの処理を読む必要があります。
文章の表示だけで良ければそこをフックすれば解決しそうですが、その後に処理を続けるとなると実装の複雑化は避けられないでしょう。
故に、仕様を分割してシンプルな実装を組み合わせてみてはどうでしょうか、という話を書きました。

アクターのメモ欄を参照するにはどうすればいいですか?


変数 names をどのように作るか、ということですよね。
データベースで設定したアクターのデータは $dataActors に格納されます。
ゲーム中でアクターの情報を参照したいという場合は、基本的に Game_Actor インスタンスを参照するべきなのですが、全アクターのメモ欄に記述したメタタグを参照したいという場合は話が変わってきます。
$dataActors は、オブジェクトの配列になっており、関連する箇所の型情報だけ記すと以下のようになります。

コード: 全て選択

{
  meta: {[key: string]: boolean|string};
}


今回はキー名が MemberChange かつ型がbooleanということなので、例えばアクターID1のメモ欄を参照するのであれば、以下のように書けます。

コード: 全て選択

$dataActors[1].meta.MemberChange


ただし、これはあくまで例です。今回は全アクターについてメモ欄を参照したいので $dataActors を用いますが、そうでない場合は $gameActors を使うことも検討します。

コード: 全て選択

$gameActors.actor(1).actor().meta.MemberChange


(確かMVデフォルトの処理系ではoptional chainingは使えないので、適宜nullチェックする必要はあります)

さて、 <MemberChange> メモタグが記述されているすべてのアクターについて、名前の一覧を作りたいという場合には、 Array.prototype.filter() と Array.prototype.map() が使えます。

Array.prototype.filter()
Array.prototype.map()

コード: 全て選択

const names = $dataActors
  .filter(dataActor => dataActor && dataActor.meta.MemberChange)
  .map(dataActor => $gameActors.actor(dataActor.id).name());


$dataActors 配列から、 .meta.MemberChange が真であるような要素を .filter() によって抜き出し、それを .map() で名前に変換しています。
名前はゲーム中で動的に変わることがありますので、 $dataActors ではなく Game_Actor インスタンスのほうを参照します。

ソースコードで修正すべき点があれば教えてください。


インデントの深さが全体を通して統一されていないようなので、エディタのコードフォーマットを活用してみてください。
VSCodeであれば Shift + Alt + F で実行できます。(フォーマット戦略についてもカスタマイズできるので、気になる場合はググってみてください)

varを使う理由は今の時代もう存在しませんので、基本的にconstを使用してください。
唯一、 isMemberChange のみ再代入が必要なロジックになっているので、そこは let にしておく必要があります。
ただ、この手のフラグによる挙動の分岐はそもそもアンチパターンとして、可能な限り避けるのが定石ではあります。
今回は全体の設計からもう少しシンプルにするほうが良いので深追いしませんが、どうしても他に良い方法が思いつかない場合のみ使う最終手段だと思っておいてください。
アバター
much
記事: 62
登録日時: 2017年11月09日(木) 01:01

Re: スクリプトでパーティ交代イベントを作りたい

投稿記事by much » 2024年2月03日(土) 17:13

なるほど!そのように分割できるのですね…。
勉強になります。
実装する時間がなくて、さらっと確認しただけなのですが、仕様を元にツクラーさん視点で設計テコ入れしてくださるだけでも助かります(実装方法から悩みましたので汗)

実装再開するとおそらく質問が出てくるかもですが、まずは感謝を返信で送信させていただければと思います。

ありがとうございます!
アバター
much
記事: 62
登録日時: 2017年11月09日(木) 01:01

Re: スクリプトでパーティ交代イベントを作りたい

投稿記事by much » 2024年2月05日(月) 00:27

ほぼほぼ完成形まで作ることができました!
追加であと少し質問させてください!!

質問

①現状、「MemberChange out」「メッセージ」「MemberChange in」「メッセージ」「MemberChange exec」の順にイベントを作らないと、各プラグインコマンドの実行を待たずに次のイベントに進んでしまいます。イベントの終点を決めるにはどうすればいいですか?
②IN,OUTともに、生きているアクターのみ選択可能にするにはどうすればいいですか?
③今回作成したプラグインコマンドを改変して、戦闘中全滅したら、メンバーを入れ替えるというようなイベントを作りたいのですが、可能でしょうか?
④このあたりの定義って不要だったでしょうか?

コード: 全て選択

    let isMemberChange = { flg:false, mode:"" }
    let outMembers = [];
    let inMembers = [];

⑤その他、ソースコードでおかしい点や修正したほうがいい点があれば教えてください!

コード: 全て選択

//=============================================================================
// MemberChange.js
//=============================================================================

/*:ja
 * @plugindesc アクターを入れ替えるためのプラグインコマンド集です
 * @author Ajitama
 *
 * @param OutVarID
 * @desc 入れ替え対象のアクターIDを保存するための変数IDを指定します。この変数IDはほかの処理やイベントで使用しないものを指定してください。
 * @default 1
 *
 * @param InVarID
 * @desc 入れ替え対象のアクターIDを保存するための変数IDを指定します。この変数IDはほかの処理やイベントで使用しないものを指定してください。
 * @default 1
 *
 *
 * @help
 *
 * Plugin Command:
 *   MemberChange out         # 外す人物を指定します
 *   MemberChange in          # 加える人物を指定します
 *   MemberChange exec        # 入れ替えを実行します
 *
 * Actor Note:
 *   <actor:off>              # このアクターはゲームに参加しません
 */

(function () {

    var parameters = PluginManager.parameters('MemberChange');
    const outVarID = String(parameters['OutVarID'] || '1');
    const inVarID = String(parameters['InVarID'] || '2');

    let isMemberChange = { flg:false, mode:"" }
    let outMembers = [];
    let inMembers = [];

    var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function (command, args) {
        _Game_Interpreter_pluginCommand.call(this, command, args);
        if (command === "MemberChange") {
            if(args[0] === "out"){
                outMembers = $gameParty.members();
                const names = [];
                outMembers.forEach(member => {
                    names.push(member._name)
                });
                this.setupChoices([names, 100, 0, 2, 0]);
                isMemberChange.flg = true;
                isMemberChange.mode = "out";
            }
            else if(args[0] === "in"){
                inMembers = $dataActors.filter((dataActor) => {
                    if(dataActor && dataActor.meta && dataActor.meta.memberChange){
                        if(dataActor.meta.memberChange == "on" &&
                        !outMembers.some(outMember => outMember._actorId === dataActor.id)){
                            return dataActor;
                        }
                    }
                });
                names = inMembers.map(inMember => inMember.name);
                this.setupChoices([names, 100, 0, 2, 0]);
                isMemberChange.flg = true;
                isMemberChange.mode = "in";
            }
            else if(args[0] === "exec"){
                console.log($gameVariables.value(inVarID));
                console.log($gameVariables.value(outVarID))
                $gameParty.addActor($gameVariables.value(inVarID))
                $gameParty.removeActor($gameVariables.value(outVarID))
            }
        }
    };

    var _Game_Message_On_Choice = Game_Message.prototype.onChoice;
    Game_Message.prototype.onChoice = function (n) {
        if (isMemberChange.flg) {
            if(isMemberChange.mode === "in"){
                $gameVariables.setValue(inVarID,inMembers[n].id);
            }
            else if(isMemberChange.mode === "out"){
                $gameVariables.setValue(outVarID,outMembers[n]._actorId);
            }
            isMemberChange.flg = false
            isMemberChange.mode = ""
        }
        else {
            _Game_Message_On_Choice.call(this, n);
        }
    };
})();
アバター
Plasma Dark
記事: 669
登録日時: 2020年2月08日(土) 02:29
連絡を取る:

Re: スクリプトでパーティ交代イベントを作りたい

投稿記事by Plasma Dark » 2024年2月05日(月) 02:52

各プラグインコマンドの実行を待たずに次のイベントに進んでしまいます。


選択肢を実現するイベントコマンドのコードは Game_Interpreter.prototype.command102 に書かれています。
適切にウェイトモードを設定してください。

IN,OUTともに、生きているアクターのみ選択可能にするにはどうすればいいですか?


Game_Actor インスタンスは isAlive メソッドで生存判定が可能です。

戦闘中全滅したら、メンバーを入れ替えるというようなイベントを作りたいのですが、可能でしょうか?


不可能ではないと思いますが、実装を始める前に仕様を言語として書き出してみることをオススメします。

前衛が全滅した場合に控えと入れ替えるプラグインを公開しています。MV向けは古いコードですが、規模は小さいので簡単に読めるかと思います。

このあたりの定義って不要だったでしょうか?


不要にする書き方のほうがスッキリしそうだなとは思います。
isMemberChangeは Game_Message.prototype.onChoice の挙動を制御するために利用されているようですが、choiceCallbackを適切に設定してあげればそもそもonChoiceをフックして挙動を書き換えなくても良さそうです。
変数のIDをプラグインコマンドの引数として渡してあげれば、inとoutを別コマンドとして分ける必要もないですね。

outMembersは名前一覧を作るための一時変数のようなので、outコマンドのifスコープの中に入れてしまえますし、そもそもforEachで一覧を作る必要はなく、mapを使えば不要です。
inコマンドでは今パーティにいないメンバーを加えるという意味であればoutMembersではなく$gameParty.members()をそのまま使えば良いはずです。
inMembersも同様に、名前一覧を作るための一時変数であり、書き方次第では不要になります。

ソースコードでおかしい点や修正したほうがいい点があれば教えてください!


varが残っているのを気にされていないようです。

再代入はコードの可読性を犠牲にしやすく、可能な限り減らすほうが良いです。
基本的に再代入不可のconstを使用し、letはどうしても必要な場合のみ用いるようにすることが望ましいですね。

厳格モードでないため、未定義の変数namesに対する代入を行ってしまっていることに気づけていません。
既存のプラグインを参考に use strict を記述してみてください。

変数IDはnumber型のほうが自然かなと思います。

Game_Actorインスタンスのメンバには、._actorIdではなく.actorId()、._nameではなく.name()でアクセスするほうが良いですね。
コアスクリプトにおいて_で始まるメンバはprivateな変数であり、これは対象クラスの外から直接参照するべきでないという目印になっています。
アバター
much
記事: 62
登録日時: 2017年11月09日(木) 01:01

Re: スクリプトでパーティ交代イベントを作りたい

投稿記事by much » 2024年2月14日(水) 22:38

ご連絡ありがとうございます!
$dataActorsで取得している場合、生存確認ってどのように行いますか?
アバター
Plasma Dark
記事: 669
登録日時: 2020年2月08日(土) 02:29
連絡を取る:

Re: スクリプトでパーティ交代イベントを作りたい

投稿記事by Plasma Dark » 2024年2月15日(木) 02:17

Game_Actor インスタンスは isAlive メソッドで生存判定が可能です。

$dataActorsからアクターのデータ定義を引いてくるのであれば、そのIDからGame_Actorインスタンスを取得できます。メモタグが記述されているアクターの名前一覧を作る際のスクリプトが参考になります。

“MV:質問” へ戻る