RPGツクールMV (英語名: RPG Maker MV) のJavaScriptベースの実行環境 JGSSに関するメモ。
自動戦闘とは
アクターやクラスの特徴(Trait)で特殊フラグの「自動戦闘」をセットすると、戦闘時にプレイヤーの入力を必要とせず、自分の判断で行動するようになります。
自動戦闘の内部値
今回はクラスに設定したので、data/classes.json ファイルの値を確認してみましょう。
{
"id":2,
"expParams":[30,20,30,30],
"traits":[
{"code":23,"dataId":0,"value":0.2}, // 特殊能力値: 狙われ率 * 20%
{"code":22,"dataId":1,"value":0.3}, // 特殊能力値: 回比率 + 30%
{"code":41,"dataId":2,"value":0}, // スキルタイプ追加: 必殺技
{"code":62,"dataId":0,"value":1}, // 自動戦闘
{"code":52,"dataId":1,"value":1}, // 防具タイプ装備: 一般防具
{"code":51,"dataId":10,"value":1} // 武器タイプ装備: 爪
],
// 以下略
},
内部値が 62 なので、js/rpg_objects.js で該当する部分を探してみます。
Game_BattlerBase.TRAIT_SPECIAL_FLAG = 62;Game_BattlerBase.prototype.isAutoBattle = function() {
return this.specialFlag(Game_BattlerBase.FLAG_ID_AUTO_BATTLE);
};
isAutoBattle メソッドに抽象化されていました。
自動戦闘のロジック
アクターの自動戦闘は以下のメソッドで実装されていました。
makeActionList メソッドでアクションの配列を作成し、それらを順に evaluate で評価し、最大の値を返したアクションを実行します。
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');
};
ということは、アクションに実装された evaluate メソッドが鍵となりそうですね。
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;
};
itemTargetCandidates メソッドはこのアクションの対象の一覧を作成します。そして対象ごとに実行されている evaluateWithTarget メソッドが、今回の自動戦闘の機能に関する本日のメインターゲットです。
なお isForAll メソッドは、そのアクションの対象が集団である場合に true になります。具体的には以下の3つの対象のとき。ランダムは対象外のようです。
評価値は最大値が採用されますが、対象が集団のときに限り評価値は足し合わされます。集団に対して評価値が高くなりがち、な特徴があることを覚えておきましょう。
[2017/08/30 追記]このメソッド内で value に Math.random() が加算されていることを見逃していました。「分散」の他に、ここでのランダム値により行動をバラけさせているのですね。0~1の値が足されると、けっこう変動しそうです。同程度の効果のスキルがある場合など、うまくランダムに選んでくれそうですね!
ターゲットごとの評価値
さて本日のメインターゲットですが、実際のコードは意外とシンプルです。まずは眺めてみましょう。
Game_Action.prototype.evaluateWithTarget = function(target) {
if (this.isHpEffect()) {
var value = this.makeDamageValue(target, false);
if (this.isForOpponent()) {
return value / Math.max(target.hp, 1);
} else {
var recovery = Math.min(-value, target.mhp - target.hp);
return recovery / target.mhp;
}
}
};
makeDamageValue はダメージ量(回復の場合は回復量)を計算するメソッドで、中身が興味深いですが今回はスルーします。第二引数がクリティカル計算のフラグで、評価する際には false を渡している、つまりクリティカル値を評価に含めていないことは覚えておきましょう。
で、ダメージ量はそのまま評価値として使用するのではなく、敵の残り体力に対しての比率として利用しているのがポイントです。より弱い相手、より倒しきれる相手を先に倒す、という感じのロジックですね。
回復量も同じく比率で、最大体力の何割が回復できるか、が評価値になります。
自動戦闘のアルゴリズムについて
さて、これらのロジックをまとめると
- アクションは敵の残り体力に何割のダメージを与えるか、もしくは最大体力の何割を回復できるか、で評価される
- 集団を相手にしたアクションの場合、それぞれへのアクションの評価値が加算される
- 評価値が最も高いアクション、およびアクションの対象が選択される
と、なりますね。補足としては
- ダメージ値や回復値には「分散度」によるランダム値が適用されるので、同じ状況で常に同じ行動をとるわけではない
- 攻撃のダメージには上限がないが、回復は体力の最大値が適用されるので、攻撃の評価が高くなりやすい
となります。
相手が弱いとダメージ比率が高いこと、対象が多い場合に評価値が稼ぎやすいことから、雑魚敵を大量に出すと範囲攻撃ばかり繰り返すことになる、とおもわれます。
現在のアルゴリズムへの評価
実際に使ってみると、自動戦闘はそれなりにうまく動作しているようにみえます。いい感じ。わりと単純な内部ロジックを知ってから利用すると、猶更です。
私はこのアルゴリズムを「シンプルだが、全情報が見える強さがあり、そこそこ有効」と評価します。
自動戦闘アルゴリズムは、プレイヤーに開示されていないかもしれない敵モンスターの内部値まで知っています。つまり有利なのです。表示はされませんが、実際に全ての行動をとってみて、一番効果の出た方法で戦うわけです。行動自体は愚直ですが、プレイヤーからみて、それなりに賢く立ち回っているように見えるでしょう。
もしアルゴリズムを改良するなら
現在のアルゴリズムをベースに少し改良するのであれば、やはり評価値の部分でしょうか。ダメージの総量を割合でみているのは良いですが、もう少し評価を改良する余地があります。
例えばその攻撃で敵を倒せる場合、同じ敵が二体いて、片方がダメージを負っている場合、どちらが対象として選ばれるでしょうか?評価値は現在の残体力を分母にもつので、瀕死のほうを選んで倒してしまいます。ちょっと勿体ない。
また同様に一撃で敵を倒せる場合、強さの異なる二種類の敵が居たらどちらが選ばれるでしょうか?さきほどと同じ理由で、弱いほう、つまり体力の少ないほうを選んで倒してしまいます。
これは与える予定のダメージ量だけをみて、実際に与えるダメージを考慮していないことが理由です。相手の残り体力を越えて無駄になるダメージを考慮していないから、こういった動作になります。
また同じ倒すにしても、より強いキャラ、よりやっかいなキャラを優先して倒して欲しいですよね。例えば敵キャラのID番号などをみて、評価値を少し操作してあげても良いかもしれません。
またキャラごとにこの評価値を変化させることで、キャラの個性を出すのも面白そうですね。
…まあ、このあたりは、時間があったらプラグイン化してみるかもしれませんw
ゲーム制作のヒント?
自動戦闘のアルゴリズムでは、敵の体力量がけっこう重要であることがわかります。例えば嫌らしい補助魔法を使う敵キャラクターは、防御力を下げてダメージ量をあげたり、最大体力を下げることで、自動戦闘の攻撃対象になりやすくなります。仲間が厄介な相手を先に狙ってくれる、という賢い動作をするよう、敵キャラの設定でサポートしてあげるわけです。