🎮

UnityのNetcode for GameObjectsでマルチプレイヤーゲームを開発したときの話

2023/12/09に公開

「神戸電子専門学校 ゲーム技研部 Advent Calendar 2023」9日目の記事です。
(今週7日間書ききりました、、、!!)

https://qiita.com/advent-calendar/2023/kdgamegiken

はじめに

https://store.steampowered.com/app/2654360/Prank_Heart/

https://twitter.com/PrankHeart_Game
現在、私たちは数名のチームでゲームを制作しています。Steamのストアページも公開しているのでウィッシュリストよろしくお願いします!よろしければX(Twitter)のフォローよろしくお願いします!!(もう少しでSteamのストアページ公開できそうです、、、)

なぜ宣伝したかというと、このゲームの特徴としてオンラインマルチプレイであることです。この記事では、私がこの鋭意制作中のゲームのマルチプレイ部分を担当したということで、そのお話を少ししたいと思います。なお、この記事ではNetcode for GameObjectsNGOと略します。

NGOとは

NGOの説明の前に、なぜこのライブラリを使ったかの説明を軽くしたいと思います。実はUnityには2023年現時点で様々な選択肢が存在しています。

ライブラリ名 公式か,無料か 説明
NetworkView 公式,無料 廃止済み
UNet 公式,無料 非推奨(2022で廃止予定)
NGO[旧MLAPI] 公式,無料 後述
Mirror 非公式,無料 有志が生まれ変わらせたUNet
Photon Cloud 非公式,無料有 大人数だと頭打ち
Photon Quanhtum 非公式,無料有 入力情報のみ送信する、専用の物理演算
モノビットクラウド 非公式,無料有 Photon Cloudの問題点をお金で解決

以上で紹介したのは、リアルタイム通信に特化したライブラリですが、ガチャやセーブデータ機能などのオンラインデータベースに特化したライブラリ(Game Server Service(GS2))なども存在します。

企画当初からネットワークマルチプレイの構想があったのですが、ネットワークに付随するお金の面をクリアしないといけませんでした。そこで様々な検討をした結果、NGOなら専用サーバーを介さずSteamとの連携も可能だった点、さらに公式が現在推奨しておりサンプル等もあったため、こちらを採択しました。

Steam経由で接続する方法も、前述した記事で紹介されています。

https://yuru-uni.com/2023/02/22/multiplay-tutorial6/
https://yuru-uni.com/2023/02/27/multiplay-tutorial7/

ネットワークに必要な機能はだいたい入ってる

前述したように、普通にマルチプレイゲームとして遊べるサンプルや、機能紹介のようなサンプルなど、ソースコードがいくつか公開されています。実際に触っていただくとわかると思うのですが、このライブラリのみで「ネットワークゲームといえばこんな感じだな」と思えるくらいのゲームにはできます。

ネットワーク同期に関してはかなり良好

Zennの3MBの制限でかなり圧縮しています。ご了承ください。

特に軽量化していなくても、大量の常に値が変動するオブジェクトも同期可能です。(100オブジェクト以上になると厳しいですが)
また、レイテンシ制御などのデバッグ機能やパケット数の表示などデバッグ表示も可能です。

ネットワークするだけなら簡単

スクリプトも少し書く必要がありますが、実はネットワークするだけなら簡単です。

NetworkManagerUnityTransortをアタッチした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してカスタムすることにしました。

https://github.com/shirokuma1101/com.unity.netcode.gameobjects

  • 属性の追加
    • 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に公開しています。

https://github.com/shirokuma1101/unity-ngo-manager

おわりに

Unityでのネットワークマルチプレイゲームの開発がこんなに大変だとは思いませんでした。こうもう少しUnrealEngineのマルチプレイみたいにもっと簡単に開発できたらなと思います。
そして最後になりますが、このゲームは学生生活の中で一番時間をかけているので、リリースされた際には手に取って遊んでほしいです><

脚注
  1. About Multiplayer Play Mode (MPPM) ↩︎

神戸電子専門学校ゲーム技術研究部

Discussion