RPGツクールMVにおける自動戦闘を改善してみる?その1 (JGGSメモ)

RPGツクールMV (英語名: RPG Maker MV) のJavaScriptベースの実行環境 JGSSに関するメモ。

自動戦闘について

自動戦闘の仕組みについては、昨日のメモにまとめました。仕組みがわかったので、自分のゲーム用に少し調整してみましょう。

通常攻撃をさせない

実は私のゲームにはパーティメンバーにが居りまして。噛みつく、とか吠える、などのスキルを実装してあり、自動戦闘で戦いを助けてくれます。通常の攻撃をさせないことで、より犬っぽい戦闘になるかもしれません。

通常攻撃を完全に外しても良いのですが、スキルが使えない状況では何も行動しない、のも寂しい。本来の仕組みを利用し、通常攻撃の評価値を下げることで、アクションがほとんど選択されないように設定してみます。

評価値の関数

前回も登場しましたが、アクションの評価値は以下のメソッドが計算しています。通常攻撃に限りこの評価値を操作すれば、実現できそうですね。

Game_Action.prototype.evaluate = function() {
var value = 0;
this.itemTargetCandidates().forEach(function(target) {
var targetValue = this.evaluateWithTarget(target);
if (this.isForAll()) {
value += targetValue;
} else if (targetValue > value) {
value = targetValue;
this._targetIndex = target.index();
}
}, this);
value *= this.numRepeats();
if (value > 0) {
value += Math.random();
}
return value;
};

ここで RPGツクールMV プラグイン作成入門 (1) のページにある、プラグインのスケルトンファイル(RTK_Test.js)をダウンロードしておいてください。プラグイン作成はこのファイルの「// ここにプラグイン処理を記載」の部分に処理を記載すればokです。

さて実装するプラグインは非常に簡単。

var _Game_Action_evaluate = Game_Action.prototype.evaluate;
Game_Action.prototype.evaluate = function() {
if (this.isAttack()) {
return 0;
} else {
return _Game_Action_evaluate.apply(this, arguments);
}
}

幸いにもアクションには isAttack というメソッドがあり、通常攻撃かどうか判別できます。なので通常攻撃ならば低い評価値を返し、そうでなければ元の evaluate メソッドを呼んであげればok。

これだけで通常攻撃せず、スキルだけを使う自動戦闘になるはず…

自動戦闘のバグ?

機能をテストしていると、妙なことに気がつきます。通常攻撃の評価を下げた時、普通の攻撃スキルは問題ないのですが、「防御」や「様子を見る」などの行動をとりません。これらの行動に共通するのは、ダメージも回復も与えないため、評価値がゼロであることです。

評価値を処理している makeAutoBattleActions メソッドをもう一度見てみましょう。

Game_Actor.prototype.makeAutoBattleActions = function() {
for (var i = 0; i < this.numActions(); i++) {
var list = this.makeActionList();
var maxValue = Number.MIN_VALUE;
for (var j = 0; j < list.length; j++) {
var value = list[j].evaluate();
if (value > maxValue) {
maxValue = value;
this.setAction(i, list[j]);
}
}
}
this.setActionState('waiting');
};

おや? Number.MIN_VALUE が怪しいです。JavaScript におけるこの値は少し変わっていて「0に近い正の最小値」であり、実は0よりも大きい値です。なので評価値が0のアクションは決して maxValue の値を越えないため、if 文が成立せず、自動戦闘の対象にはならない、ことがわかります。

これでは防御などの行動スキルや、味方を強化するスキル(バフ)や敵を弱体化させるスキル(デバフ)などが自動戦闘では使用されないことになります。これは困った。

通常攻撃のダメージが0になることはまず無かったので、これが問題になることは無かったでしょう。そういった意味では、これはバグでなくて仕様かもしれませんね。

とはいえ、我がパーティの犬さんの(低レベルの間の)行動は評価値が0のものばかりなので、このままでは何も行動してくれません。ちょっとプラグインで手を加えてみます。

Game_Actor.prototype.makeAutoBattleActions = function() {
for (var i = 0; i < this.numActions(); i++) {
var list = this.makeActionList();
var maxValue = Number.MIN_VALUE;
for (var j = 0; j < list.length; j++) {
var value = list[j].evaluate();
value += (value == 0 && !list[j].isRecover()) ? Math.random() / 10 : 0;
if (value > maxValue) {
maxValue = value;
this.setAction(i, list[j]);
}
}
}
this.setActionState('waiting');
};

真ん中へんに1行、追加されているのがわかりますか?評価値valueが0の場合、0~0.1の小さめの乱数値を加えるようにしてみました。これで「防御」などのアクションが処理の対象になると共に、複数あればランダムで選択されるようになります。

ただこのままだと体力が満タンの仲間に回復魔法をかけ続けてしまうため、条件に更に !list[j].isRecover() を加え、回復が0の場合の回復手段は対象から外すことにしました。

これで大丈夫かと思ったのですが、また通常攻撃が出るようになってしまいました!そう、プラグインが評価値0を返しているので、「防御」などと同じ扱いになり、乱数を加算されてしまうのです。よってさきほどのプラグインで、以下のように返す値を 0.01 とかなり小さめの値に変更することで調整しました。

var _Game_Action_evaluate = Game_Action.prototype.evaluate;
Game_Action.prototype.evaluate = function() {
if (this.isAttack()) {
return 0.01; // ここを0から変更
} else {
return _Game_Action_evaluate.apply(this, arguments);
}
}

とりあえず完成?

これでプラグインは一応完成、犬キャラは通常攻撃をしなくなり、防御したり、様子をみたり、戦闘中に無駄な行動をできるようになりました。

この機能の使い道は他にもあります。例えば騎士キャラを自動戦闘させるとすると、通常の戦闘アクションはこのプラグインで無効化し、あらためて複数コピー(つまり同じダメージ値に)して騎士の専用スキルとして登録しておきます。「右手突き」とか「突進突き」とか「回転突き」とか。これらの表示メッセージを変えれば、自動戦闘時にはランダムに選択され、単に攻撃しているだけでも、いろんな行動をしてくれるようにメッセージ表示される、というわけ。

今回のプラグイン

そんなに長くないので、中身をそのまま貼りますね。スケルトンのままで、コメント等を変更していないのはご容赦を。そのうち、ちゃんとします…

//==================================================
// RTK_Test.js 2016/07/30
// The MIT License (MIT)
//==================================================
/*:
* @plugindesc テスト用プラグイン
* @author Toshio Yamashita (yamachan)
*
* @help このプラグインにはプラグインコマンドはありません。
* テスト用に作成したものなので、実際に利用する場合には適当にリネームしてください
*/
(function(_global) {
// ここにプラグイン処理を記載
var _Game_Action_evaluate = Game_Action.prototype.evaluate;
Game_Action.prototype.evaluate = function() {
if (this.isAttack()) {
return 0.01;
} else {
return _Game_Action_evaluate.apply(this, arguments);
}
}
Game_Actor.prototype.makeAutoBattleActions = function() {
for (var i = 0; i < this.numActions(); i++) {
var list = this.makeActionList();
var maxValue = Number.MIN_VALUE;
for (var j = 0; j < list.length; j++) {
var value = list[j].evaluate();
value += (value == 0 && !list[j].isRecover()) ? Math.random() / 10 : 0;
if (value > maxValue) {
maxValue = value;
this.setAction(i, list[j]);
}
}
}
this.setActionState('waiting');
};
})(this);

今後の方針

なんか通常攻撃をOFFにするだけで、だいぶ時間を使ってしまいました。でもいろいろわかってきたので、次回ももう少し、自動戦闘あたりを自分なりに拡張してみたいとオモイマス。

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.