はじめに
「Photon」シリーズは、ドイツのExit Games社が開発し、GMOグローバルサイン・ホールディングス株式会社が日本展開を行っているオンラインゲーム開発向けネットワークエンジンです。
Exit Gamesは専用のホスティングサービスも合わせて提供しており、自分でサーバーを用意しなくても簡単にオンライン要素のあるゲームを作り始める事が出来ます。
10年近い実績もあるため、Unity開発者の皆さんは「Photon Unity Networking 2 (PUN2)」としてPhotonを触ったことがあるのではないでしょうか。
本記事では、そんなPhotonの新しい製品である「Photon Fusion」について紹介していきます。
動作確認環境
Windows 10 Home 21H2
Unity 2020.3.27f1
Fusion SDK 1.0.0 F Build 439
Photon Fusionとは
概要
Photon Fusionは2022年3月にリリースされました。位置づけはPUN2の後継ではありますが、バージョンアップではなくリニューアルであり、以下のような特徴があります。詳細については後述します。
- 状態転送ネットコードSDK
- ティックベースシミュレーションによる予測、補完、補償
- 複数のネットワークトポロジー(接続方法)対応
「Photon」シリーズは「Photon Cloud」としてホスティングサービスを含めたシステムを提供しています。そのなかでリアルタイム通信が発生するシリーズを「Photon Realtime」と呼び、以下5つが提供されています。
今回紹介するFusionはいわゆる「オンラインでのマルチプレイ」の開発に使用します。
Quantumも同様にオンラインマルチプレイ用ですが、プレイヤーのインプットだけを同期するシステムとなっており、かなり先端的な製品です。実装についてもECS(Entity Component System)をベースにしており、Unity標準の数学ライブラリや変数・物理エンジンが使えず、実装にはそれなりの知識が必要となってきます。
- Realtime
- Fusion
- Quantum
- Voice
- Chat
ティックベースシミュレーションとは
ティックベースシミュレーションは根幹に関わる大きな特徴なのですが、聞き慣れない単語かと思います。ざっくりいうと以下のような説明になります。
- ティック
実時間とは切り離された、Photon Fusionが持つ内部時間単位のこと - スナップショット
各ティック毎に保存されている、全クライアントに共通の”正しい状態”を表すデータ - 各クライアントはスナップショットを基準にゲームを進行
これにより、同期処理実装の簡易化や、予測補間処理による安定した同期が可能になっています。
小難しい印象を受けますが、内部で処理されておりなんとなく理解できていれば実装上問題ありません。
ただし、1点注意が必要です。実時間やクライアントの時間に依存せずゲームが進行するため、時間依存の処理を Time.DeltaTime で実装してしまうとズレが発生する可能性があります。
そのため、ゲームプレイに影響のある時間処理(スキルのクールタイムなど)はPhotonが提供する Runner.DeltaTime で実装する必要があります。
ネットワークトポロジー(接続方法)に複数対応
オンラインゲームではネットワークトポロジー、簡単に言うとプレイヤーがどういう形でオンライン接続するか、ということを決めて実装する必要があります。Photon Fusionでは以下に挙げる4種類の接続方法から選んで実装することが出来ます。
- サーバー型
一般的にイメージされるオンラインゲームの構成
サーバーが存在し、そこに各プレイヤーが接続します - プレイヤーホスト型
P2Pの構成
プレイヤーのうち一人がホストとなり、プレイヤー同士で通信を行います - 共有モード
上記2つの中間のような構成
Photonサーバーがホストの役割をし、そこにプレイヤーが接続をします - シングルモード
一切通信が必要のないオフラインモード
プレイヤーホスト型のホストと同じような動作をします
ネットワークトポロジーの接続処理例
Photon Fusionでネットワークトポロジーを設定するには、接続処理のオプションを変更するだけで出来ます。以下のコードのように、StartGameという接続用メソッドがあります。そのGameModeというオプションに、任意のEnumの値を入れるだけです。
これ以外の部分には影響しないため、モード毎に専用の実装をする必要はありません。
await _runner.StartGame(new StartGameArgs()
{
GameMode = mode,
SessionName = "TestRoom",
Scene = SceneManager.GetActiveScene().buildIndex,
SceneObjectProvider = gameObject.AddComponent<NetworkSceneManagerBase>()
});
- modeに入れるもの
GameMode.Host ホストとして動作
GameMode.Client クライアントとして接続
GameMode.Single ローカルで動作
GameMode.Shared 共有モードで接続
GameMode.Server サーバーに接続
新機能の紹介
物理挙動の実装
物理挙動を同期するための新しいコンポーネントとしてNetworkRigidbodyが追加されました。RigidbodyがついたオブジェクトにNetworkRigidbody(とNetworkObject)をアタッチするだけで同期させることが可能であり、非常に簡単に実装できます。
その他機能
async/await対応&WebGL対応
これは公式サイトに記載されてはいませんが、async/awaitベースで実装されています。そのため、pun2みたいなコールバック地獄にならずに済ます。
UnityのWebGLビルドはasync/awaitに対応していないのですが、PhotonFusionの対応(ver1.1.1)によりWebGLビルドができるようになりました。
その他
特徴として挙げられている機能は以下のものがありますが、本記事では扱いません。今後別記事で詳しく紹介する予定です。
- ラグ補償付きのヒットボックス
- スナップショット補完(通信が悪い状態でのスムーズなレンダリング)
- レプリケーションシステム
導入方法
日本語の公式チュートリアルに一通り書いてあるため、その通りに進めれば問題ありません。
https://doc.photonengine.com/ja-jp/fusion/current/fusion-100/overview
- アカウントを作成
公式サイト右上「サインイン」より - Unityプロジェクトを作成
Unity 2020.3.x LTS以降が対象 - SDKをダウンロード(.unitypackageファイル)
https://doc.photonengine.com/ja-JP/Fusion/current/getting-started/sdk-download
普通通りプロジェクトにインポート - PhotonダッシュボードでAppIDを作成、コピー
UnityからPhotonのサービスに接続するための識別子です
アプリ毎に1つ作成します - UnityEditor上でプロジェクトの設定
Fusion → Fusion Hub → Fusion Setupを開く
Fusion App IdにAppIDをペースト - 完了
サンプルプロジェクトの一つ、Fusion Kartsでの設定例
PUN2との機能比較
Photon FusionとPUN2の実装方法について比較してみます。
PUN2では同期頻度(同期手段)で実装方法が分かれているのに対し、Fusionでは目的に近い形で実装が可能なのが特徴です。
PUN2 | Fusion | |
---|---|---|
入力 | オブジェクト同期 PhotonStream |
NetworkInput |
高頻度 | オブジェクト同期 PhotonStream |
オブジェクト同期 [Networked] |
低頻度 | カスタムプロパティ | オブジェクト同期 [Networked] |
不定期 | RPC | RPC |
入力値
プレイヤーの入力を同期する手法はあまり変わりません。
1点、PUN2では入力を送受信する際、それが自分のデータかどうかを判別する必要があります。
PUN2 | Fusion | |
---|---|---|
入力の保存 | プロパティに取得 | INetworkInputを継承した構造体で保存 |
同期方法 | PhotonStream.SendNext(Object) PhotonStream.ReceiveNext() |
NetworkInput.Set(INetworkInput) NetworkInput.GetInput(INetworkInput) |
自他の判別 | 必要(とはいえifで分岐するだけ) | 不要 |
高頻度 オブジェクトの位置やステータス
PUN2では先程の例と同じで、SendNext,ReceiveNextメソッドを利用し都度送受信する必要があります。Fusionではプロパティに[Networked]属性をつけるだけで同期が可能です。
PUN2 | Fusion | |
---|---|---|
名称 | オブジェクト同期 | オブジェクト同期 |
同期方法 | IPunObservableを実装 | NetworkBehaviourを継承 |
その他 | SendNext,ReceiveNextメソッドで送受信する | プロパティに[Networked]をつける |
低頻度 ルームやアイテムの情報
PUN2ではカスタムプロパティというものが用意されていますが、Hashtable(Dictionary)しか扱うことは出来ません。Fusionではプロパティに[Networked]をつけるだけでよく、更に多くの型に対応しています。
PUN2 | Fusion | |
---|---|---|
名称 | カスタムプロパティ | オブジェクト同期 |
同期方法 | SetCustomProperties CustomProperties[key] |
プロパティに[Networked]をつけるだけ |
その他 | Hashtable(Dictionary)しか扱えない | 型はわりと何でもOK |
不定期 入力やゲームの進行
不定期、確実な通信などの場合はどちらもRPC(Remote Procedure Call)を利用します。
RPCの設定方法はあまり変化がありませんが、呼び出し方が大幅に変わりました。PUN2では呼び出し用のメソッドの引数に文字列で指定する必要がありましたが、Fusionでは普通のメソッドのように呼び出せばよく、簡単で素早く安全に実装できるようになりました。
PUN2 | Fusion | |
---|---|---|
設定 | メソッドに[PunRPC]属性をつける | メソッドに[Rpc] 属性をつける |
実行方法 | PhotonView.RPCメソッドで呼ぶ メソッド名を文字列で指定する |
通常通りメソッドを呼ぶ |
PUN2での実装例
// 設定方法
[PunRPC]
public void ChatMessage(string a, string b){ }
// 実行方法
PhotonView photonView = PhotonView.Get(this);
photonView.RPC("ChatMessage", RpcTarget.All, "jup", "and jup.");
Fusionでの実装例
// 設定方法
[Rpc(RpcSources.InputAuthority, RpcTargets.All)]
public void RPC_SendMessage(string message){ }
// 実行方法
RPC_SendMessage("Hello world!");
まとめ
PUN2に比べ各種実装が簡単になっており、またネットコードについても強力なものが備わっています。またPUN2はレガシーとなっており、新規機能の追加もないと公言されています。そのため、特に理由がなく、新規でネットワークライブラリを導入するならばPhoton Fusionがオススメです。