v.2.0で思いっきりいじったので記事にしてみます。
今日はちょっと真面目だよ!
ドールの行動ルーチン
地下ドールは敵も味方も自動戦闘で戦うゲームです。
自分のドールが、他のプレイヤーの画面で敵として出てくるゲームなので、
自動戦闘でどういう動きをするのか、ということをプレイヤーにわかりやすくしたいというのもありますが、
対戦回数が多いゲームなので、毎回コマンド考えてたら疲れちゃうよね……という気持ちもあり、
こういうゲームになっています。
RPGツクールMVのデフォルト戦闘AIについて
コアスクリプト見ればわかるのですが、RPGツクールMVの戦闘AIの基本は、
「相手に対してダメージ計算してみて、威力が高いやつを優先的に使う」という動きをします。
(途中に乱数も入っているので、実際はちょっと弱いやつを使うこともあります)
とってもシンプルでいいとは思うのですが、いくつかネックがあります。
その中でも一番大きいのは、いわゆる「バイキルト」みたいなバフ系スキルや、
異常ステートの回復スキルのような、ダメージ量 or 回復量が存在しないスキルについて、
まともに使ってもらえなくなってしまう、という問題があります。
地下ドールは、バフ・デバフ系のスキルがそこそこあるので、これが使ってもらえないと厳しいです><;
そのため、v.1の頃はプラグインの力で、ダメージが発生しないスキルについても、
「擬似的なダメージ値」を計算するようにして、使ってもらえるようにしていました。
AIおばかさん問題
コメントやフィードバックでも結構頂いていましたが、
v.1では、すごくドールの行動がバカに見えるという問題がありました(◞‸◟)
そもそも「AIがバカ」というのはかなりふわっとした言葉で、
正確には「プレイヤーの期待に全然応えてくれない」という状態のことだと思っています。
(プレイヤーが思いつきもしないミラクルプレイをしなくても、バカとは言われない)
地下ドールの戦闘でプレイヤーの期待を裏切ってしまう行動は、
僕自身は、主に下のようなパターンかなと感じていました。
- スキルを全然使わずに通常攻撃ばっかりしている
- → 本当は装備したスキルを使ってほしかった
- もう敵を倒せるのに、何故か自分を回復している
- → 倒せるときは、すぐに倒してほしかった
- 2回行動ができるチャンスで、同じバフスキルを連続で使ってしまう
- → せっかくのチャンスを無駄にしないでほしかった
(1) については、おそらく乱数の問題だと思います。
RPGツクールMVの行動選択は結構乱数の影響を受けるため、場合によってこういう行動を取ります。
これは想像ですが、このしくみを作った方は、
「例えどんな風に作られたゲームであっても、なんか考えてるっぽく、人間味がある行動をさせたい」
という気持ちで作られているんだろうなと思います。
そして残念なことに、地下ドールは人間味がある行動をして欲しくない、
つまり最適行動以外しないで欲しいタイプのゲームだったので相性があまり良くないのだと思います><
そのため、(1) については乱数の影響をもっと小さくすることで解消ができそうです。
(2) と (3) については、とても複雑な話ではあるのですが、
一言で言うならば「もっと周りを見て行動を決めてほしい」なのだと思います。
敵の現在HPだったり、今の自分のステータス状態だったりを踏まえた上で、
「今のチャンスだったら、こういうことをしてくれ…!」とプレイヤーが思う行動ができれば、
こういう不満はもっと少なくできそうです。……むずくね???
地下ドールv.2.0の行動ルーチン決定方法
そういった仮説を元に、v.2.0では行動ルーチンの決定処理を一部書き換えています。
- 行動選択に乱数を使うのをやめ、一番優先度が高い行動をするようにした
- 優先度の計算処理をスキルごとに専用化した
(1) はそのままです。最適だと考えた行動以外、絶対にしないようにしました。
というわけで (2) について説明をします。
「もっと周りを見て行動を決めてほしい」を解決するためには、
全スキルで共通の汎用処理で優先度を計算するのは不可能です。
特に、今回2vs2のペアバトルを導入するつもりだったため、
- 使いたいスキルの情報
- 自分自身の状態
- パートナーの状態
- 敵(1〜2体)の状態
の4つから良い感じに優先度を決めてあげないと、プレイヤーの期待に応えられそうにありません。
なので、処理の共通化は考えず、スキルごとに優先度計算処理を書くことにしました。
// スキル12: 瞬刃 // 先制攻撃、相手に出血付与 export default function calcAction12({action, subject, friends, oppositions}: CalcActionParameter): ActionProbability[] { const result: ActionProbability[] = []; oppositions.forEach((target) => { if (target.isDead()) return; let damage = action.makeDamageValue(target, false); if (damage >= target.hp) damage *= 30; // 出血状態でなければ3ターン分のダメージを加算 damage += (!target.isStateAffected(21) && !target.isStateResist(21)) ? (target.mhp * 0.1 * 3) : 0; const newAction = JsonEx.makeDeepCopy(action); setTarget(newAction, target); result.push({ action: newAction, probability: damage }); }); return result; }
上は『瞬刃』というスキルの計算式です。
- 予測ダメージを計算する
- もしトドメを刺していたら、予想ダメージを30倍にする
- トドメを刺せそうな相手がいたら、先制攻撃ですぐに倒させるため
- 相手に「出血」ステートがついていなければダメージを追加する
- 「出血」は毎ターンHPが10%減るステート
- ここでは3ターン分のダメージを期待値として乗せる
- ここまでで計算した予測ダメージを優先度として設定する
というのを攻撃対象ごとにやります。敵が2人いたら2つ作ります。
処理をスキルごとに全部個別に用意することで、
「人間だったら、このスキルはこういうときに有利だと思うよね」というものを
そのままコードに落とし込むことができるようになったので、
今までに比べて、だいぶそれっぽい行動ができるようになったはずです!
……僕の作業量はめっちゃ増えるんですけどね><;
ふとしたタイミングでプレイヤーの期待を上回らせる
上記の方法で以前よりは改善されるはずですが、
プレイヤーの視点で考えると、残念ながら大馬鹿者がちょっとマシになったレベルです><;
「お、こいつやるじゃん」なんてことは絶対に起きません。
いくらスキル個別の計算をするようになったとはいえ、
やっぱり期待に応えられないことは多々あるので、そのマイナスを帳消しできるくらい別の強さも必要です。
というわけで、CPUキャラ特有のイカサマとして、『後出し行動』ができるようにしました。
通常、RPGのコマンド戦闘の行動はターンの開始前に決めてそれに従って行動をするのですが、
ドラクエの仲間のAIとかは、自分の行動の番が回ってきたときに、
周りの状況に応じて行動を変えることがあります。
これによって「そのターンに受けたダメージを、そのターン中に回復する」のような
人間にはできない未来予知的な行動をしてくれます。完全に後出しのイカサマですね。
地下ドールでもこれに習い、後出しのイカサマ行動をします。
ターンの開始時に一応行動を全員決めるのですが、自分の番が回ってきた瞬間に行動を再計算します。
これによって、ちょっとずるい行動ができるようになったのはもちろんのこと、
仲間同士で同じバフをかけてしまった!みたいな事故も起こらなくなっています。
(再計算したタイミングで、そのバフをつけるスキルの優先度が下がっているため)
もう!ケロスが!!2回歌うことは!!!ない!!!!
という話だったとさ
……みたいなことをしていました。
この記事は妙に長いですが、やってる処理自体はめちゃくちゃ愚直なものです。
これならまともに動いてくれそうだね、という部分に至るまでは結構悩んだので、
のちのちの僕のためにも、今回記事にしてみました><
後出し行動をしていることを認識していると、考えられるドールの編成とかもあるかもしれませんね。
イカサマをうまく活用してチャンプを目指せ!
……本当はこういう感じの処理をもっと汎用化できれば、プラグイン素材にするとかできるんだろうけど、
スキルごとに個別設定している時点で、まったくもって汎用化できそうにない(◞‸◟)