This document is about: FUSION 1
SWITCH TO

Fusionイントロダクション

概要

FusionはUnity向けの新しい高性能な状態同期ネットワークライブラリです。一つのAPIで、根本的に異なる二つのネットワークトポロジーだけでなく、ネットワーク接続を行わないシングルプレイモードにも対応しています。

シンプルさを念頭に置いて開発されていて、一般的なUnityのワークフローと自然に統合できるようにしながら、データ圧縮、クライアントサイド予測、ラグ補償等の高度な機能を標準で提供しています。

例えば、RPCやネットワーク上の状態は、メソッドの属性やMonoBehaviourのプロパティで定義され、明示的にシリアライズを行うコードを必要としません。またネットワークオブジェクトは、Nested PrefabやPrefab VariantなどのUnityの新しい機能を使ったプレハブで定義することもできます。

Fusion内部では最小のCPUオーバーヘッドで帯域幅を削減する最先端の圧縮アルゴリズムを使用しています。データの転送は、圧縮された完全なスナップショット(ホストモードのみ)か、結果整合性のある部分的なチャンクで行われ、後者の場合は、自由にカスタマイズ可能な関心領域(AOI)システムによって大人数のプレイヤーにも対応できます。

Fusionは堅牢なティックベースシミュレーションを導入していて、ホストモードまたは共有モードのどちらかで動作します。二つのモードの主な違いはネットワークオブジェクトに対する変更権限で、これによってFusionのどの機能が利用できるかが規定されます。

ホストモード / サーバーモード

ホストモードでは、ヘッドレスの専用サーバーを動作させる場合でも、クライアントとサーバーを兼用する端末で動作させる場合でも、サーバーが全てのオブジェクトに対する完全かつ独占的な状態権限を持ちます。例外はありません。

クライアントは、入力をサーバーに送信する(そしてサーバーがその入力を反映する)か、RPCを使用して変更のリクエストを送信することでのみ、ネットワークオブジェクトを更新できます。

クライアントが直接更新するネットワーク上の状態は全てローカルの予測となり、サーバーから実際の正しいスナップショットを受信した際に上書きされます。クライアントがサーバーから受信した状態にロールバックした後、ローカルの(予測していた)ティックまでの再シミュレーションを行う処理は、リコンシリエーションと呼ばれます。

以前の予測が正確だった場合の処理はシームレスとなり、正確でなかった場合は状態が更新されます。後者の場合、ネットワークの状態とレンダリングの状態は分かれているため、新しい状態に描画をスナップするか、様々な形式の補間・誤差修正・スムージングを使用してビジュアルアーティファクトを軽減しながら描画することになるでしょう。

ホストモードでは、Fusionはラグ補償ヒットボックスをサポートします。これによって、各クライアントが見ている他のクライアントは過去のものであり、通常のレイキャストではサーバーが見ているものとは一致しないという問題を解決します。ラグ補償によって、サーバーは効率的に入力受信時のプレイヤー視点で世界を見ることができるようになります。

ホストモードをファイアウォールやルーターの内側で実行する場合、Photon Cloudは必要に応じてUDPホールパンチングやパケットリレーを行いますが、あくまでセッションの所有者はホストになるため、ホストが切断されるとセッションは失われます。Fusionはホストマイグレーションの仕組みによって、現在のホストが切断された場合に別のクライアントに権限を移譲することができますが、共有モードとは異なり、クライアント側で特別な処理が必要になることに留意してください。

共有モード

共有モードでは、ネットワークオブジェクトの権限は全クライアントに分散されます。具体的には、各クライアントは最初に自身がスポーンしたオブジェクトの状態権限を持ちますが、他のクライアントに状態権限を自由に渡したり、任意で状態権限の取得を許可制にしたりすることができます。

共有モードでのデータ転送モードは常に結果整合性となり、ラグ補償、予測およびロールバック等の機能は使用できません。シミュレーションは全クライアント同じティックレートで常に先に進行しますが、クライアント間でティックが一致する保証はないことに注意してください。

共有モードのセッションはPhoton Cloudが所有していて、接続しているクライアントがいる限りは生き続けます。Photon Cloudはパケットリレーとして動作し、Unity無しでネットワーク状態に完全にアクセスできるため、専用サーバーを起動することなく軽量なサーバーロジックやデータ検証(例:チート対策)を実装することが可能です。

共有モードは多くの点でPUNに類似していますが、より機能が充実しており、高速で、ランタイムアロケーションのオーバーヘッドもありません。

PUN、Bolt、Fusionの比較

FusionはPhotonの既存製品(PUNとBolt)を置き換えて、より進化させるために開発されました。従来サポートしているアーキテクチャに加えて、さらに多くのアーキテクチャにも対応します!

PUNとBoltは安定したネットワーキングソリューションですが、さらなる最適化をするには限界がありました。FusionはPUNとBoltの優れたコンセプトを全て取り入れるとともに、高性能なアーキテクチャを新規に構築し、最先端の機能を標準で利用できるようにしました。次の図は、その改良点をまとめたものです。

table - pun vs bolt vs fusion
PUN vs Bolt vs Fusion

基礎概念

開発者が使用する主要なFusionコンポーネントはNetworkRunnerNetworkObjectの二つです。NetworkRunnerはFusionの核と考えることができ、ネットワークとシミュレーションを管理するNetworkRunnerがシーン内に一つ存在します。これはサーバーもクライアントも同じです。

通常のUnityのプレハブかシーン上のオブジェクトにNetworkObjectを追加することで、ランタイム時にネットワークIDが割り当てられ、ティックベースシミュレーションの同期対象の一部となります。いくつかのオプション(例えば、一部のオブジェクトをグローバルなオブジェクトとして、常にデータ転送に含める等)で調整も可能です。

ゲームオブジェクトにネットワーク上の振る舞いを追加するための基底クラスは、SimulationBehaviourNetworkBehaviourの二つがあります。

NetworkBehaviourSimulationBehaviourのサブクラス)はネットワークプロパティ(ゲームの状態を構成する要素)を追加できます。SimulationBehaviourはネットワークプロパティを持たずに、シミュレーションのステップやコールバックを制御するために使われます。

NetworkBehaviourはネットワーク間で自動的に同期するデータを保持したい場合に使用するべきです。ネットワーク上の状態値の定義は、以下のように、プロパティを作成して[Networked]を付けるだけです。

C#

[Networked] public byte life { get; set; }

[Networked]は全てのプリミティブな型(boolは例外で、1-bitで適切にシリアライズするためにNetworkBoolをかわりに使用するべきです)で動作します。また、クライアント間で安全に識別できるため、構造体や他のNetworkObject(プレハブ含む)の参照も動作します。

以下のように、ネットワークプロパティの値が変更されるたびに呼び出されるコールバックを、簡単に登録することもできます。

C#

[Networked(OnChanged = "OnTypeChanged")] public Type type { get; set; }

public static void OnTypeChanged(Changed<TheClassWhichHasTheProperty> changed)
{
  // your code here - check API docs for more details
}

さらに、コールバックがどこで呼び出されるかを制御することもできます。

  • OnChangedLocal (true/false) - trueに設定すると、プロパティを変更した端末自身でもコールバックが呼び出されます(デフォルトはfalse)
  • OnChangedRemote (true/false) - falseに設定すると、プロパティを変更した端末自身でのみコールバックが呼び出されます(デフォルトはtrue)

ビルトインコンポーネント

Fusionは、ゲームやプロトタイプを素早く開発するための、様々なビルド済みのNetworkBehaviourを提供しています。

NetworkTransformはオブジェクト(Colliderを含めることも可能)のTransformの同期状態を保ち、レンダリングは最先端のスナップショット補間によって自動的にスムーズに行われます。UnityのTransformを直接変更することで、クライアントサイド予測(どのクライアントでも)を容易に行うこともできます。

物理演算で制御されるRigidbodyには、NetworkRigidbodyが推奨です。NetworkTransformと同様の補間オプションを備え、コンフィグ設定をするだけで、FusionはUnityの物理演算(PhysX)の完全な予測・ロールバックを行うことができます。

人型キャラクターのような、プレイヤーが直接操作するオブジェクトのために、FusionにはビルトインのNetworkCharacterControllerがあります。より複雑なユースケースでは、基本的な静的クエリ(サーフェス正接、貫通の修正等)を再利用して、独自の移動やステアリング処理のコードに合わせることができます。

ティックベースのコールバック

ゲームプレイのシミュレーションコードの記述には、SimulationBehaviourのライフサイクルが重要になります。Unity標準のAwake()Start()等を使用することもできますが、ネットワーク対応したメソッドの使用を推奨します。

FusionでStart()に相当するメソッドはSpawened()と呼ばれ、特定の端末(サーバーかクライアント)でオブジェクトが最初に活性化された時に呼び出されます。Awake()はオブジェクトのライフタイムを通して変更されないもの(GetComponent<>()の呼び出しがこのカテゴリに入りますが、普段Start()で使用するものには大抵Spawned()を使用します)を、一度限りで初期化するために使用してください。

最も重要なFusionのコールバックはFixedUpdateNetwork()です。FusionのFixedUpdateNetwork()は、次のネットワークの状態を求めるロジックの実行と、リコンシリエーション中の再シミュレーションのどちらにも使用されます。

これら全てのコールバックはAPIドキュメントに詳細な説明があります。理解すべき要点は、FixedUpdateNetwork()FixedUpdate()のようにフレームレートとは独立した固定周期で実行されること、FixedUpdate()とは異なりリコンシリエーション中の再シミュレーションで同一の状態/フレーム/ティックの処理が何度も実行される場合があることです。

FusionはFixedUpdateNetwork()が呼び出される前に全てのネットワークプロパティを暗黙的にリセットするため、再シミュレーションは透過的に行われます。

NetworkTransformNetworkRigidbodyを使用しているオブジェクトなら、例えばFixedUpdateNetwork()内で以下のようなコードを安全に実行できます。

C#

transform.position += transform.forward * Runner.DeltaTime;

Runner.DeltaTimeの使い方に注意してください。これはティックレートに従ったシミュレーションを維持するために必要です。

Render()FixedUpdateNetwork()の後かつLateUpdate()の前に実行されることが保証されています。

入力

Fusionの入力処理は二つの独立したステップに分かれています。

  1. ローカルのハードウェアからの入力を収集して構造体に格納します。これはクライアントとホスト(専用サーバー除く)で、新しいティックに進むたびに行われます。入力はサーバーに送信されると同時に、(クライアント上の)ローカルで即座にクライアントサイド予測に使用されます。
  2. 入力はFixedUpdateNetwork()でゲームの状態を変更(シミュレーションを進行)するために読み取られます。これはクライアント(自身が入力権限を持つネットワークオブジェクト)でもホスト/サーバーでも実行できます。クライアントでは、リコンシリエーションのロールバックからの再シミュレーションのため、同一のティック/フレームで何度も実行される場合があります。

最初のステップは、単純にプレイヤーの行動を記録し後で使用するために保存するという一般的なUnityの入力処理メカニズムと考えることができます。二つ目のステップで、ネットワーク上の状態を更新するための入力を反映するスクリプトを分離します。

最初のステップは、FusionのOnGetInput()コールバックでポーリングされます。二つ目のステップは、通常はプレイヤーが操作するキャラクターのSimulationBehaviourなどのFixedUpdateNetwork()内で、GetInput()TryGetInput()を呼び出して処理されます。

RPC(リモートプロシージャコール)

入力の構造体と[Networked]プロパティの使用は、クライアント間でゲームの状態の同期を保つため(そして、サーバーの権限を維持するため)の信頼できる方法になりますが、それらが現実的な解決策ではない時もあります。

例えば、クライアントが入力権限を持たないオブジェクトに対して複雑なインタラクション(インベントリ内の特定の鍵を使ってロックされた扉を開くような)を実行したい時などです。

入力構造体のフィールドにどの鍵をどの扉に使うかを含めてサーバーに送信することもできますが、このアプローチではすぐに入力構造体が稀にしか使用しない値だらけで散乱してしまうでしょう。

別の問題として、一般的なFusionの入力は信頼性がないためパケットが失われることがあります。これはキャラクターを操作するような継続的な入力を行う場合はほとんど目立ちませんが、単発で一度限りのアクションを行う場合はサーバーに到達したかの確認をした方が良いでしょう。

このようなケースのために、FusionはRPC(リモートプロシージャコール)をサポートしています。

FusionはRPCのためのシンプルながら強力な構文を実装しています。SimulationBehaviourでRPCを定義するには、void型を返す一般的なC#のメソッドを宣言して[Rpc]属性を付けます。メソッドの引数には、全てのプリミティブな型(boolを除く、上記参照)や構造体、Fusionのオブジェクト(ネットワークIDを持つなら何でも、例えばNetworkObjectPlayerRefなど)の参照を取ることができます。

[Rpc]属性によってRPCがどこで呼び出されどこで実行されるかをフィルタリングすることができます。

C#

[Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)]
public void RPC_Configure(string name, Color color)
{
    playerName = name;
    playerColor = color;
}

「RPC」プレフィックスに注意してください。上記コードの記述通りにしなければならないわけではありませんが、メソッド名のプレフィックスかサフィックスには「rpc」(大文字と小文字を区別しない)を付ける必要があります。

RpcSourcesRpcTargetsに加えて、[Rpc]属性にはいくつかの追加オプションのパラメーターがあります。

  • Channel (Reliable/Unreliable) - RPCの到達保証を気にしないのであれば、Unreliableに設定します(デフォルトはReliable)
  • InvokeLocal (true/false) - ローカルでRPCを実行したくないのであれば、falseに設定します(デフォルトはtrue)
  • InvokeResim (true/false) - 再シミュレーション中にRPCを実行したいのであれば、trueに設定します(デフォルトはfalse)

RPCについては最後になりますが、RPCは明示的な状態を持たないということを覚えておいてください。たとえ全クライアントにRPCを送信したとしても、まだオンラインになっていないクライアントがRPCを受信することはなく、一度抜けて再接続したクライアントはRPCを受信したことを忘れてしまうでしょう。そのためRPCが、本当に一時的な情報(例えばチャットメッセージなど)か、実行結果が[Networked]プロパティに間接的に記録されるか、どちらかの形になっているかを常に確認すべきです。

Where to go next

Fusionを学ぶなら、「Fusion Host Mode Basics tutorial」「Fusion Shared Mode Basics tutorial」から始めることを強く推奨します。これらのチュートリアルは、Fusionを学ぶ上で必須となる基本を全て教えてくれます。

Back to top