この記事は Go (その2) Advent Calendar 2016 の24日目の記事です。
この記事では
去年からずっと趣味で Go を使ってリアルタイム通信ゲームサーバー作っていて、それなりに動くものができたので、それを紹介したいと思います。
リポジトリはこちらです。
https://github.com/shiwano/submarine
一部 Unity の有償アセットを除いている以外、クライアントもサーバーもすべてのコードをここで公開しているので、もし気になる実装があったら見てみてください。
以下、簡単なディレクトリの説明です。
.
├── art # デザインまわりのファイル置き場
├── client # Unity クライアント (C#)
├── contract # 通信定義ファイル置き場 (TypeScript)
├── server
│ ├── api # Rails 製の WebAPI サーバー
│ └── battle # Go で書かれた WebSocket サーバー
└── tools # 各種ツール置き場
今回紹介するサーバーのコードは server/battle
ディレクトリにあります。
どんなゲームなの?
下記 GIF アニメを見ていただくのが一番早いのですが、ざっくりいうと、各プレイヤーが共通のフィールドで潜水艦を操作しオンライン対戦するゲームです。
このうち、一番の肝となるリアルタイムでのオンライン対戦機能を担当するサーバー(バトルをするサーバーなのでバトルサーバーと呼んでいます)を Go で書いています。
バトルサーバーは何ができればいいの?
上記はプレイヤーが新規にバトルを開始し終了するまでの流れなのですが、バトルサーバーは基本的にずっとこのシーケンスを繰り返すだけです。つまり、バトルサーバーはこのシーケンスを完了するための仕事が最低限できればいいです。
具体的には以下の3つです。
- クライアントとの双方向通信
- API サーバー(Rails)との通信
- ルーム単位でのバトルのロジックの実行
この3つの仕事を行うために、具体的にどういう実装を行っているのか、この後簡単に紹介します。
クライアントとの双方向通信
リアルタイム性のオンライン対戦を実現するために、クライアントとの双方向通信は必須です。ということで、バトルサーバーはクライアントと WebSocket プロトコルで通信しています。
Go で WebSocket を簡単に扱えるパッケージを作った - Qiita
WebSocket まわりの処理は上記記事で紹介した websocket-conn を使って書いています。
通信定義からのコードの自動生成
declare module Submarine.Battle {
enum ActorType {
Submarine,
Torpedo,
Decoy,
Lookout,
}
interface Actor {
id: integer;
userId: integer;
type: ActorType;
isVisible: boolean;
}
var actor: Actor;
}
クライアントや API サーバーとの通信ですが、TypeScript で上記のような通信定義ファイルを作り、拙作 typhen というツールで C#, Ruby, Go の通信用コードを自動生成することによって、できるだけ楽してできるようにしてます。
- Go のサーバー用に生成されたコード
- Unity 用に生成されたコード
- Rails 用に生成された生成コード
バトルサーバーではこの自動生成されたコードを、session
という型や、
https://github.com/shiwano/submarine/blob/master/server/battle/server/session.go
webAPI
という型を通して利用することで、クライアントや API サーバーと通信しています。
https://github.com/shiwano/submarine/blob/master/server/battle/server/webapi.go
バトル
最後にバトルですが、バトルのコードは battle
パッケージにまとめていて、
https://github.com/shiwano/submarine/tree/master/server/battle/server/battle
一部経路探索、衝突判定まわりのコードは navmesh
という別のパッケージにまとめています。
https://github.com/shiwano/submarine/tree/master/server/battle/lib/navmesh
ルームが battle.Battle
のインスタンスを持ち、バトルに起こった変化を自身が管理しているクライアントに伝えたり、反対にクライアントからのメッセージをバトルに渡してあげることによって、ルーム単位のオンラインバトルを実現しています。
デバッグ異常につらかったので golang.org/x/exp/shiny
使ったデバッグウィンドウを表示する機能とかも手間暇かけて作ったりしました。
おわり
以上、まだまだ開発中で未実装な部分も多く、中途半端な内容になってしまいましたが、ご容赦ください。最終的に「Go でリアルタイム通信ゲームサーバー書くのすごく勉強になりますし、面白いですよ」ということが言いたかったのです(´・ω・`)。よろしければ、みなさんもぜひ書いてみてください。
それでは、よいクリスマスを。