Jelly Troops におけるマルチタスクAI(Bot)
はじめに
『Jelly Troops』がリリースされました!
けなげなスライムたちを使った、対戦型旗取りゲームです。
基本的に対人戦で遊ぶゲームですが、ソロミッションのいくつかなどでAI(いわゆるBot)が対戦相手となります。
今回はそのAIの仕組みについて解説します。
実装の方針
実装に際して、わけんさんにヒアリングしながら、いくつかの方針を立てました。
- できるだけ人間らしいAIにする
- できるだけズルをしない(重要な情報はAIの周辺から得た知識から判断する) - それなりに強いものを目指すが、理不尽なものにはしない
- パラメータで強さや性格を調整しやすくする
採用手法の位置づけ
まず、いま「AI」というとディープラーニングで学習させたものを思い浮かべる方も多いかもしれませんが、今回はそういう手法を使っていません。
採用したのは、ゴール指向プランニング(Goal-Oriented Action Planning = GOAP) と呼ばれるものです。
ただし、同時並行でスライムを操るというゲームの性質に合わせて、工夫が必要でした。
ゴール指向プランニング(GOAP) とは?
『F.E.A.R.』というゲームで採用されて有名になりました。
事前に、[前提 - アクション - 結果] という組を複数用意します。
AIは、結果=最終ゴールに至る組を検索し、その前提条件が満たされていなければ、またその前提を満たす組を検索…
これを連鎖で繰り返して、いまの状態が前提条件と合致する組のツリーを見つけ出し、それを計画とします。
その計画順にアクションを重ねていくことで、目的を達成するまで自律的に行動するAIを作ることができます。
この手法は、一つ一つ単純なアクションを実装するだけで、AIは状況に応じて柔軟に計画を立てて実行してくれる点がメリットです。
Jelly Troops における課題と解決法
このゲームはたくさんのスライムを操って、あっちの実とこっちの旗を移動させつつ、壁を作ったり壁を破ったりと、プレイヤーが同時にあれこれ考えながら行動する必要があります。
つまり、計画上のアクションを実行して、完了を待ってから次へ、という順番待ちの戦い方は不自然です。
そこで、目的となる結果=ゴールに至るツリーを都度検索するのではなく、欲求リスト(キュー)を作り、目的を「こうなって欲しい欲求」としてプールする方式としました。
欲求リスト |
---|
お腹を満たしたい |
リストのなかの未達成な欲求から、[前提 - アクション - 結果] の [結果] が欲求に合致する組み合わせを具体的なプランとして生成されます。
プランリスト |
---|
リンゴを食べる [前提:リンゴを持っている] |
生成されたプランのうち、[前提] が満たされていないものは、さらにその前提を満たす欲求を連鎖的に生成して、リストにプールします。
欲求リスト | プランリスト |
---|---|
お腹を満たしたい | リンゴを食べる [前提:リンゴを持っている] |
リンゴを持ちたい | リンゴを買う [前提:果物屋にいる][前提:お金を持っている] |
リンゴを取る [前提:リンゴがそばにある] |
欲求リスト | プランリスト |
---|---|
お腹を満たしたい | リンゴを食べる [前提:リンゴを持っている] |
リンゴを持ちたい | リンゴを買う[前提:果物屋にいる][前提:お金を持っている] |
リンゴの木に行きたい | リンゴを取る [前提:リンゴがそばにある] |
リンゴの木に向かう [前提:リンゴの木がある] ← 実行 |
こうして生成されたプランのうち、[前提] が満たされたものの中から、欲求度が高く、コストが安いものを実行します。
また、実行中のアクションの中で、自身が何もしなくても達成される(スライムが運んでくれている)ものは除外し、それ以外のアクションに実行を移します(プランは残したまま)。
このように、欲求やプランを並列でプールさせることで、複数の計画を同時に進めるAIを作ることができました。
「並列欲求型GOAP」とでも呼びましょう。
具体的な実装例
『Jelly Troops』における具体的な実装説明です。
まず、ゴールとなる欲求の定義を考えます。
ゲームとしては相手に勝利することが最終目的なので、それをゴール欲求と定義しても良さそうです。
しかし今回は、視界に入ったものから「実を運びたい」「大旗を運びたい」「魔法が欲しい」などをゴール欲求として生成することにしました。
このほうが、それぞれの欲求度合いをパラメータで調整することで、AIの性格付けを変えやすくなるからです。
一方、見えたものに対して何かしたくなるという、やや短絡的な振る舞いになりそうですが、人間も餓死を避けようなどと考えることなく、おいしそうなモノを見たら欲しくなりますよね?
次に、欲求に対するプランとそこから派生する欲求について考えます。
「小旗を運びたい」を例にあげて説明します。
1.PlayerBrainが視界に入った小旗をKnowledgeに記憶します。
2.記憶された小旗の情報から「小旗を運びたい」という欲求が生まれます。
3.「○○を運びたい(DesireMoveBaggage)」から「○○を運ぶ(PlanMoveBaggage)」というプランが生成されます。
このプランの前提条件は
- 「○○(小旗)に十分なスライムがくっついている(DesireEnoughWorker)」
- 「○○(小旗)の運搬ルートが安全である(DesireSecureBaggageRoute)」
の2つです。
欲求リスト | プランリスト |
---|---|
小旗を運びたい | 小旗を運ぶ [小旗に十分なスライムがくっついている] [小旗運搬ルートが安全である] |
4.次に、このプランから(満たされていなければ)
- 「○○に十分なスライムがくっついている」は「○○に十分なスライムをくっつけたい」
- 「○○(小旗)の運搬ルートが安全である」は「○○の運搬ルートを安全にしたい」
を生成します。
5.ここから必要に応じて
- 「十分なスライムをくっつける」プラン
- 「十分なスライムが隊列にいる」欲求
- 「スライムを集める」プラン
- 「十分なスライムが隊列にいる」欲求
- 「(運搬経路上の)ガーディアンを誘導する」プラン
のように、欲求とプランを派生させていきます。
欲求リスト | プランリスト |
---|---|
小旗を運びたい | 小旗を運ぶ [小旗に十分なスライムがくっついている] [小旗運搬ルートが安全である] |
小旗に十分なスライムをくっつけたい | 小旗に十分なスライムをくっつける [十分なスライムが隊列にいる] |
十分なスライムが隊列にいる | スライムを集める |
小籏運搬ルートを安全にしたい | 小籏経路上のガーディアンを誘導する |
各アクションの実装は省略しますが、これだけ準備すれば、スライムが足りなければ拾うし、ガーディアンがいれば誘導してスライムを守るといった振る舞いが実現できます。
ステージ上にはたくさんの実や旗がありますが、自陣からの距離や性格によって、欲求度合いが変わるので、自然に優先順位をつけながら行動していきます。
また、スライムが運んでいる(進行中)プランは実行せず、自身がやるべき他のアクションを実行します。
プランリスト | アクション |
---|---|
小旗を運ぶ | ←進行中 |
実を運ぶ | ←進行中 |
ガーディアンを誘導する | ←実行 |
それぞれの条件が満たされているかどうかは思考ループのたびに再チェックされるので、状況が変わった場合も適時対応できます。
例えばガーディアンが移動してスライムが運んでいた経路が塞がれたとき、運ぶアクションはスライム任せですが、前提(運搬ルートが安全である)を満たすためにガーディアンの誘導を行うようになります。
成果
このように並列欲求型GOAPを使うことで、スライムで運びつつガーディアンを誘導したり壁を作ったりといった、マルチタスクの行動をするAIを作ることができました。
状況が変わっても適時柔軟に対応するので、ある程度人間らしいAIにもなっています。
いくつか苦手とする攻撃パターンもあるので最強とは言えませんが、ある程度の腕前のプレイヤーとも戦えるものになっていると思います。
また、ミッションごとに性格を変えられるので、個性を持ったAIも実現できました。
宣伝
スライムたちと一緒に楽しむぷにぷに旗とりバトル!スライムをたくさん増やしてフィールドの旗を集めよう!
Steamストア
Switchストア
Discussion