UnityのNetcode for GameObjectsでマルチプレイヤーゲームを開発したときの話
「神戸電子専門学校 ゲーム技研部 Advent Calendar 2023」9日目の記事です。
(今週7日間書ききりました、、、!!)
はじめに
https://twitter.com/PrankHeart_Game
現在、私たちは数名のチームでゲームを制作しています。Steamのストアページも公開しているのでウィッシュリストよろしくお願いします!よろしければX(Twitter)のフォローよろしくお願いします!!(もう少しでSteamのストアページ公開できそうです、、、)
なぜ宣伝したかというと、このゲームの特徴としてオンラインマルチプレイであることです。この記事では、私がこの鋭意制作中のゲームのマルチプレイ部分を担当したということで、そのお話を少ししたいと思います。なお、この記事ではNetcode for GameObjects
をNGO
と略します。
NGOとは
NGO
の説明の前に、なぜこのライブラリを使ったかの説明を軽くしたいと思います。実はUnityには2023年現時点で様々な選択肢が存在しています。
ライブラリ名 | 公式か,無料か | 説明 |
---|---|---|
NetworkView | 公式,無料 | 廃止済み |
UNet | 公式,無料 | 非推奨(2022で廃止予定) |
NGO[旧MLAPI] | 公式,無料 | 後述 |
Mirror | 非公式,無料 | 有志が生まれ変わらせたUNet |
Photon Cloud | 非公式,無料有 | 大人数だと頭打ち |
Photon Quanhtum | 非公式,無料有 | 入力情報のみ送信する、専用の物理演算 |
モノビットクラウド | 非公式,無料有 | Photon Cloudの問題点をお金で解決 |
以上で紹介したのは、リアルタイム通信に特化したライブラリですが、ガチャやセーブデータ機能などのオンラインデータベースに特化したライブラリ(Game Server Service(GS2))なども存在します。
企画当初からネットワークマルチプレイの構想があったのですが、ネットワークに付随するお金
の面をクリアしないといけませんでした。そこで様々な検討をした結果、NGO
なら専用サーバーを介さずSteamとの連携も可能だった点、さらに公式が現在推奨しておりサンプル等もあったため、こちらを採択しました。
Steam経由で接続する方法も、前述した記事で紹介されています。
ネットワークに必要な機能はだいたい入ってる
前述したように、普通にマルチプレイゲームとして遊べるサンプルや、機能紹介のようなサンプルなど、ソースコードがいくつか公開されています。実際に触っていただくとわかると思うのですが、このライブラリのみで「ネットワークゲームといえばこんな感じだな」と思えるくらいのゲームにはできます。
ネットワーク同期に関してはかなり良好
Zennの3MBの制限でかなり圧縮しています。ご了承ください。
特に軽量化していなくても、大量の常に値が変動するオブジェクトも同期可能です。(100オブジェクト以上になると厳しいですが)
また、レイテンシ制御などのデバッグ機能やパケット数の表示などデバッグ表示も可能です。
ネットワークするだけなら簡単
スクリプトも少し書く必要がありますが、実はネットワークするだけなら簡単です。
NetworkManager
とUnityTransort
をアタッチしたGameObjectを用意します。これだけでネットワークの準備は完了です。
プレイヤーを同期したいなら、プレイヤーのプレハブにNetwork Object
をアタッチします。そしてNetwork Transform
でトランスフォーム、Network Animator
でアニメーターをアタッチするだけで同期できます。
実際に確認するために簡単なマッチメイキングをスクリプトから作成します。このスクリプトはUnityの公式サンプルを参考にして作成しています。IPアドレスとポートを指定して接続します。
プレイヤーモデルにはUnityChanを利用しています。
このように、プレイヤーとして同期できていることが確認できると思います。
NavMesh
を導入してNPCを追加してみました。全員一緒なのでわかりづらいですが、いい感じに同期されています。
その他の機能
そのほかにも機能があり、
- RPC(Remote Procedure Call)
関数をネットワーク経由で呼び出す機能 - NetworkVariable
簡単に変数の同期が可能 - NetworkSceneManager
簡単にシーン切り替えの同期が可能
があります。基本的に使う機能はRPC
になると思います。
このGIFの画面では左のお宝が盗まれる処理やマップへのプロットの処理、通知処理などは全てRPC
を通して処理しています。画面中央上にあるタイマーはNetworkVariable
を利用しています。
他にも、接続方法を決定するTransport
というものがあり、デフォルトのUnity Transport
ではIP&Portの直接接続のみですが、外部パッケージであるSteam Transport
を導入し切り替えるだけでSteam経由の接続が可能です。
実際の開発
では実際にゲームを開発してきてどう感じてきたかを紹介します。
デバッグが大変(地獄)
クライアント側のデバッグがParrelSync
というエディタを複製する外部パッケージを導入する、もしくはビルドするしかありません。UnityにはUnrealEngineのような単一エディター上で仮想プレイヤーウィンドウを作成するみたいな機能はありません、、、(Unity2023からはMPPM[1]というものが標準搭載されるようです。)
なので、基本的な挙動に関してはParrelSyncで確認を行い、同時入力が必要(=人数が必要)な場合の確認に関しては、ビルドデータを共有して確認していました。
在りし日のParrelSyncでのデバッグ(ワイドモニター1画面で4分割)
そして、ライブラリの内部構造がかなり複雑でNGO
側でエラーが出たときに特定しにくいと感じました。そしてそれがそもそもライブラリ内のバグであったこともありました。
さらに、内部でMono.Cecil
を利用している部分があるので、そもそもデバッグできない部分があります。
例えば、クライアントへ送信するRPCで、たった数行の処理ですが
このようにILSpyでアセンブリ後を見てみるとかなり処理が足されています。RPC関連でバグを見つけるのには本当に苦労しました、、、
ネットワークプログラミングは全員が使えるようにならないとダメ
これは開発に着手した直後に当たった問題でした。考えれ見れば当たり前なのですが、プレイヤーやNPC、アイテムなどネットワークに対応しないといけない処理はどこにでも発生します。これらの処理を各々で書かないといけなくなります。誰が(Host or Client)処理するのか、いつ(遅延が考慮されているか)を最低限全員が把握する必要がありました。そこで私はUnityChanを使った超簡単なサンプルを作成し、自分が把握してから全員にプロジェクトと処理の説明を共有しました。
さらに効率化するために
当初からバグや仕様に悩まされ始めていたため、NGO
をForkしてカスタムすることにしました。
- 属性の追加
BroadCastRpc
- イベントの追加
OnObjectSpawnedCallback
OnObjectDespawnedCallback
OnInitialize
OnShutdown
-
Shutdown
時にnull
にするようにOnServerStarted
OnClientStarted
OnServerStopped
OnClientStopped
OnClientConnectedCallback
OnClientDisconnectCallback
OnTransportFailure
ConnectionApprovalCallback
などの機能追加や、その他バグ修正などもしています。
また、関数内部が肥大化するという問題もありました。これは例ですが
private void Start()
{
if (IsHost) {
// タイマーの開始
}
if (IsOwner) {
// 追従カメラをインスタンス化
}
}
private void Update()
{
if (IsHost) {
// ゲーム勝敗判定の更新
}
if (IsOwner) {
// 各プレイヤーの移動処理
}
}
のように、Start()
やUpdate()
が肥大化していきました。それを解決するために、OnHostStart()
やOnHostUpdate()
のようなイベント関数のように分けるようにしました。
TGS2023で出展した際の技術説明書の一部
こうすることで、かなりコードの可視性が向上しました。
こちらもOSSでGitHubに公開しています。
おわりに
Unityでのネットワークマルチプレイゲームの開発がこんなに大変だとは思いませんでした。こうもう少しUnrealEngineのマルチプレイみたいにもっと簡単に開発できたらなと思います。
そして最後になりますが、このゲームは学生生活の中で一番時間をかけているので、リリースされた際には手に取って遊んでほしいです><
Discussion