clusterのリアルタイム通信サーバーの漸進的な進化のような仕組みを理解したいなと思い、手習い用にMQTT+Protocol Buffersを使ってリアルタイムに2次元位置を同期するサーバーを書いてみている。今回はMQTT+Protocol Buffersを使ってリアルタイムに2次元位置を同期するサーバーを書いてみたの続きで、弾の情報も追加して、server内での一定周期の状態更新と配信について学んだ。
最終的にはプレイヤーが弾を打って当たると負けるみたいな動作を実現したいが、最初からそこに向かうのは大変なので、まずは次のような仕様で弾情報を追加する。
- 弾は一定時間ごとにランダムで出現して、一定スピードで移動する
- 弾は盤面から外に出ると消える
- まだやらないこと
- プレイヤーが弾を発射できる
- プレイヤーと弾の当たり判定をする
できたもの
自分を緑、他の人を赤、弾を黄色で表示している。プレイヤーはキーボードの矢印キーで移動し他のプレイヤーに位置同期しつつ、弾は自動で移動する。弾は盤面外に出たら消える。
実装コードはこの辺りにある。
- https://github.com/shibayu36/terminal-shooter/pull/5
- https://github.com/shibayu36/terminal-shooter/blob/f4266e2daf495587063aea663b56cf5f4581c79e/README.md
大まかな作戦
プレイヤーについては、client -> serverでプレイヤー状態変更のPUBLISHを受けたらserverが全クライアントにプレイヤー状態をブロードキャストするようにしていた。しかしアイテムについてはserver側で挙動をコントロールしたい。
そこで次のような作戦とした。
- アイテムの状態(位置情報など)をserver側で一定周期で更新する
- アイテム状態が更新されたらserver -> clientにアイテム状態をMQTTでブロードキャストする
イメージはこんな感じ。
実装
mainでアイテム状態の一定周期更新 + アイテム情報配信のgoroutineを2つ起動
まず(1) GameStateを一定周期で更新し続けるgoroutineと、(2) GameStateの更新の通知を受けたらclientにMQTTで配信するgoroutineの2つを起動する。
- (1) gameState.StartUpdateLoopを実行すると、GameStateを更新し続けるgoroutineを起動し、更新通知を受け取るchannelを取得できる
- (2) controller.StartPublishLoopを実行すると、itemsUpdatedChに通知があったらアイテム状態を配信するgoroutineを起動する
また実行して様子を見れるように弾はランダムに自動生成するようにしている。
gameState.StartUpdateLoopでは一定周期でゲーム状態の更新をする
GameState側の実装から見ていく。
StartUpdateLoopでは、time.NewTickerを使って16.7ms = 60fpsでゲーム状態の更新をする。これはGameState.updateを一定周期で呼んでいるだけ。
- 今のアイテムリストを取ってきて、1つずつ状態更新をしていく。状態更新ロジックはアイテム側のItem.Updateの実装に任せるようにして、Item.Updateがtrueを返してきた時は状態が変わったと見なす
- 盤面から出たかの判定はアイテムというよりゲーム状態側に共通でロジックを持っていいかなと思ったので、盤面外の検知+削除を行う
- もし1つでもアイテム更新があれば、updatedItemsChを通して更新通知をする
- 更新されたアイテム情報をchannelから通知してもいいが、いったん今回は更新があったことだけを通知する
弾の更新はItem interfaceを実装したBullet.Updateで行う。Bulletは30回更新がかかったら(= 60fpsで0.5秒)、指定した方向に1マス動くということにして、
- Updateのたびにtickをインクリメントしていく
- tickが指定した更新回数=moveTick以上になったら移動し、tickをリセットする
のように実装した。
以上で、一定周期でゲーム状態を更新し、アイテム状態に更新があったらchannelで通知できるようになった。
controller.StartPublishLoopでアイテム状態更新があったらアイテム状態をclientに配信する
ロジックはこのあたり。
まずitemsUpdatedChに通知が来たら、publishItemStatesメソッドを呼び出してアイテム状態の配信を開始する。
- クライアントの状態更新のためActiveなアイテム状態の送信と、削除されたアイテム状態の送信をする
- アイテム状態の配信は、item_stateトピックで、Protocol Buffersで定義したItemStateをPayloadとして持たせて配信する
まとめ
今回はリアルタイムに2次元位置を同期するサーバーにアイテム情報を追加し、弾が自動で動くようにしてみた。一定周期での状態更新や、更新があった時のみserver -> clientにMQTTパケットを送信するみたいなやり方について学べて良かった。