Push: Ctrl+Y

 技術で遊んで ときどき進む

アプリ ゲームAI

Kotlin × ゲームAI(ステート駆動エージェント)③:複数キャラの通信

2016/09/15

ステート駆動エージェントの最後の回として、複数キャラでイベントをやり取りする部分を作成していきます!ここまで来ると、なんだかゲームのキャラを動かしているんだなあという実感が湧いてわくわくしてきますね。

 

キャラを増やす

複数キャラを作るにあたって

キャラをIDで管理するため、列挙型のクラスをひとつ作ります。

今までエージェントを作るとき直接入力していたIDを、このEntityNamesから取得するように変更します。

メッセージを送信しあう際、このIDを基に受信者を特定するので、この作業は地味に重要です。

 

Monsterを生み出す

獲物キャラクター(Monster)をとりあえず増やしましょう。これまでFoxに持っていた獲物の情報もこちらに移し、通信することでやり取りできるようにします。

Stateとかも以下で作っていきますが、まずは雰囲気だけ!

 

メッセージの送信・受信機構

やり取りするパケット:Telegram

メッセージの送信は、Telegramというデータパケットをやり取りする形で実現します。中身はこんな感じのただのハコです。

送信者・受信者・メッセージ・送信時間(特定の時間だけ遅らせる場合、送信時刻が入る)・追加情報を持ち、それをFoxMonster間でやり取りして連携を取っていく感じです。

特定時間だけ遅らせるメッセージのために、compareToを実装しています。遅らせるメッセージはMessageDispatcherpriorityQ(後述)にTreeSet型(重複なし・自動ソート)で格納していくのですが、その自動ソートをコントロールするための対応です。

それでは、そのTelegramをやり取りする部分を作っていきましょう。

 

パケットをやり取りする:MessageDispatcher

FoxMonsterがどんなやり取りをするかという状態遷移はこんな感じ。

gameai1-3-2

メッセージの送信を担当するのは、MessageDispacherというシングルトンのクラスです。MessageDispatcherTelegramを作成し、しかるべきタイミングでTelegramを送信します。

時間をおいて送信されるメッセージはpriorityQに一時保存され、順次実行されていきます。その順次実行は、MainActivityのメインループの中に実装しましょう。

即時で送信されるメッセージはdischargeが実行され、受信者のhandleMessage()を実行しています。このとき重要なのは、受信者のhandleMessage()を実行できるようにすることですが、そこへのアクセスを確保するために必要なのがEntityManagerというクラスです。

 

受信者を特定する電話帳:EntityManager

EntityManagerFoxMonsterなど場に存在するBaseGameEntityを管理する子で、IDを渡せばBaseGameEntityを教えてくれる電話帳です。この電話帳を使うことで受信者の情報をゲットし、MessageDispatcherでメッセージを受け取った後の処理を続けることができます。

続いてBaseGameEntityを継承したエージェント(FoxMonsterなど)を新しく作ったときに、このマップに登録するように変更します。

 

 

受信したメッセージを処理する:BaseGameEntity / State / StateMachine

ここまででメッセージを送信することはできるようになりました。あとは、メッセージを受信するための変更を行います。

まず、BaseGameEntityFoxMonsterの継承もと)にメッセージを受信したときの処理を加え、継承先に実装を移譲します。

 

FoxMonsterへの実装が義務づけられたので、これも実装していきましょう。(とりあえずFoxのみ記載しますが、やることはいっしょ)

 

で、受信したときの処理はStateを変更するところにたどり着くので、芋づる式にStateMachine -> State -> 各実装State に処理内容を実装する必要があります。

こんな感じで!これで送信 -> 受信の機構は出来上がりです。すごくシンプルで良い。ここから実際に、通信する処理を作っていきます。

 

実際にメッセージを送受信する

メッセージ送受信の状態遷移はこんな感じです。

黒:Fox  青:Monster  赤:メッセージ

gameai1-1-3

ここでは、

  1. Monsterが散歩していることをFoxに伝える
  2. Foxがメッセージを受け取って「あしあと発見」状態に移行する
  3. Monsterは焼かれてから1分間経過したら生き返って散歩状態に戻る

 

という3つを解説します。

1. Monsterが散歩していることをFoxに伝える

散歩状態はWalkFieldというステートで表現します。

こんな感じで、散歩モードのenter処理でFoxにメッセージを送ります。

MessageDispatcher.dispatchManagerの中で、受け取り先のFoxのIDからBaseGameEntityを取得 -> handleMessage -> m_currentState.onMessage という流れでメッセージを受け取った後の動作が呼び出されます。

 

2. Foxがメッセージを受け取って「あしあと発見」状態に移行

このときFoxのm_currentStateはSearchSomethingなので、ここでメッセージを受け取ります。

あしあと発見状態は、FindFootprintというステートを新たに作成します。特に変わったことはしないので省略です。

 

Monsterは1分たったら生き返って散歩し始める

一定時間たったらメッセージを送信する例として、焼かれた1分後に自分にメッセージを送るパターンです。

なんかすごい柔軟・・!

 

まとめ

これでステート駆動エージェント編はおしまいです!

なんとはなしに始めたのですが、やってよかったなあと思うのは意外にもデザインパターンを振り返れたことかなと思います。ステートはもちろん、シングルトン・ジェネリクスなんかも出てきてなかなかに楽しかった!

あとは、Kotlinの書き方がすごく気に入ったことかなと思います。シンプルに短く書けるし、なによりnull安全が役に立ちすぎる。すてき。

そんな感じでゲームAI編はいったん脇に置いて、今度こそIoT系のことやりたいなと思います。やりたいことはいくつかたまってるので、がんばろう。

 

-アプリ, ゲームAI
-, ,

おすすめの記事

1
React + Redux入門① - Reduxの概念を理解

React+Reduxでサイトを作っているので、復習がてら「検索画面を作成する」 ...

2
React + Redux + D3.js アニメーション:ドラッグ&ドロップでキングスライムを作った

はい、今回は要素のドラッグ&ドロップを中心に作ってみました。 Mater ...