クイックスタート
クイックスタート
新しいUnityプロジェクトを作成し、Fusion SDKをインポートしました。次にマルチプレイヤーゲームの作成に取り掛かりましょう。あるいは、既存のシングルプレイヤー・プロトタイプをお持ちの方もいらっしゃるでしょう。では、何から始めればよいのでしょう?
お勧めは、サンプルの一つから始めることです。これらのサンプルは、マルチプレイヤーゲームのベスト・プラクティスやデザイン・パターンをわかりやすく実装しており、その多くはプロジェクトにそのままコピーすることができます。
コーディングを始める前に、最も重要なコンポーネントについて説明します。このドキュメントにはそれらの役割を記載していますので、実際のサンプルコードを見たときに、何を探すべきかわかります。
基本
Fusionで使用する主要なコンポーネントは、「NetworkRunner」と「NetworkObject」の2つです。NetworkRunner
は、Fusionの中核ともいえるもので、シーンに1つだけ配置し、ネットワークとシミュレーションの両方を管理します。これはサーバーとクライアントの両方で行われます。
通常のUnityプレハブやシーンオブジェクトにNetworkObject
を追加すると、実行時にネットワークIDが割り当てられ、同期されたティックベースのシミュレーションの一部となります。また、いくつかのオプションを設定することもできます(例えば、この特定のオブジェクトを常にデータ転送の一部にする - グローバルオブジェクト)。
他の2つのベースビヘイビアを継承して、ゲームオブジェクトに実際のネットワークを追加することができます。SimulationBehaviour
とNetworkBehaviour
です。
ネットワークプロパティ(ゲームの状態を構成する)は、NetworkBehaviour
(SimulationBehaviour
の特殊なサブクラス)に追加されますが、後者はネットワークプロパティを持たずに、シミュレーションステップやコールバックを制御するために使用できます。
NetworkBehaviour
は、ネットワーク上で自動的に同期されるデータを保持するために使用します。ネットワーク状態の値を定義するには、単純にプロパティを作成し、次のように [Networked]
としてマークします。
[Networked] public byte life { get; set; }
[Networked]
は、すべてのプリミティブ型で動作します (ただし、bool
については、単一のビットとして適切にシリアル化されるため、代わりにNetworkBool
を使用する必要があります)。また、構造体や他の NetworkObject
への参照(プレハブも含む)についても、すべてのクライアントで安全に識別できるため、機能します。
重要: Fusionは、空のget/setスタブを、実際のネットワーク状態データにアクセスするコードに置き換えるので、これらは空にしておきます。
また、ネットワーク上のプロパティの値が変化するたびにトリガーされるコールバックを登録することも簡単にできます。
[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
は、オブジェクトのトランスフォームを同期させます(コライダを含むこともできます)。レンダリングは、最先端のスナップショット補間により、自動的に滑らかさを保ちます。また、UnityのTransform
のデータを直接変更することで、(任意のゲームクライアント上で)クライアントサイドの完全な予測を行うことも簡単にできます。
物理的に制御されたリジッドボディには、NetworkRigidbody
がお勧めです。同じ補間オプションが使えますし、Fusionでは設定オプションを設定するだけで、PhysXで直接、完全な予測/ロールバックを行うことができます。
ヒューマノイドキャラクターのように、プレイヤーが直接操作するオブジェクトについては、Fusionに内蔵されているNetworkCharacterController
を使用します。より複雑なユースケースでは、基本的な静的クエリ(表面の接線、貫通補正などを行う)を再利用して、ステアリングや移動のためのカスタムコードとマッチさせることが可能です。
ティックベースのコールバック
ゲームプレイのシミュレーションコードを書くには、SimulationBehaviour
のライフサイクルが重要になります。Unityに内蔵されているAwake()
やStart()
などを使うこともできますが、一般的には、ネットワークセーフな対応を意識することをお勧めします。
FusionのStart()
に相当するものはSpawned()
と呼ばれ、特定のマシン(サーバーやクライアント)上でそのオブジェクトが初めて命を吹き込まれたときにトリガーされます。オブジェクトの寿命が尽きるまで変更されないものを一度だけ初期化するには、Awake()
を使います。GetComponent<>()
の呼び出しで行われることが多いですが、通常はStart()
を使うべきところをSpawned()
を使います。
最も重要なFusionコールバックは FixedUpdateNetwork()
です。 FixedUpdateNetwork()
は、Fusionが次のネットワーク状態のロジックを実行するためと、調整中に再シミュレーションするための両方に使用するものです。
これらのコールバックについては、APIドキュメントに詳細が記載されています。重要なのは、FixedUpdateNetwork()
は、FixedUpdate()
と同様に、レンダリングレートとは無関係に、一定の間隔で呼び出されるということです。また、FixedUpdate()
とは異なり、サーバーから送られてきたアップデートを調整するために再シミュレーションが発生した場合、同じ状態/フレーム/ティックに対して何度も呼び出すことができます。
ネットワークプロパティについては、再シミュレーション は透過的です。なぜなら、Fusionは、FixedUpdateNetwork()
を呼び出す前に、すべてのネットワークステートプロパティを暗黙のうちにリセットするからです。
例えば、FixedUpdateNetwork()
の中でこのような処理を行っても、対象となるオブジェクトがNetworkTransform
やその他の組み込みコンポーネントを使用している限り、全く問題ありません。
transform.position += transform.forward * Runner.DeltaTime;
Runner.DeltaTime
を使用していることに注目してください。これは、ネットワークのティックレートに合わせてシミュレーションを維持するために必要です。
Render()
は、FixedUpdateNetwork()
のすべての呼び出しの後、LateUpdate()
の前に実行されることが保証されています。
入力
Fusionでは、入力処理を2つのステップに分けています。
- ローカルのハードウェアからInputを収集して、構造体に配置します。これは、クライアントとホスト(専用サーバーではない)でのみ、新しいティックごとに1回ずつ行われます。この構造体はサーバーに送信されますが、ローカルではクライアント側の即時予測にも使用されます(クライアントの場合)。
FixedUpdateNetwork()
からこの入力を読み込んで、ゲームの状態を変更する(シミュレーションを進める)ことができます。これは、クライアント(入力権限のあるネットワークオブジェクトから)とホスト/サーバーの両方で行うことができます。クライアントでは、同一ティック/フレームで複数回実行して、復元ロールバック時に再シミュレーションを行うことができます。
最初のステップは、プレイヤーが何をしているかを単純に記録し、後で使えるように保存する、通常のUnityの入力処理メカニズムと考えることができます。一方、2つ目のステップは、その入力を適用してネットワーク状態を変更するための独立したスクリプトです。
最初のステップは、FusionのOnGetInput()
コールバックでポーリングされ、次のステップは、FixedUpdateNetwork()
の中からGetInput()
またはTryGetInput()
を呼び出すことで処理されます。
リモートプロシージャコール
ネットワーククライアント間でゲームの状態を同期させる(サーバーの権限を維持する)には、Input構造体と[Networked]
プロパティを使用するのが一般的ですが、それが最も実用的な解決策ではない場合もあります。
例えば、クライアントが入力権限を持たないオブジェクトに対して、複雑なインタラクションを行いたい場合です。
Input構造体にいくつかのフィールドを追加して、どのドアにどの鍵を使用するかをサーバーに伝えることもできますが、この方法ではInput構造体の中に多くのクラッタがすぐにできてしまいます。
また、通常のFusion Inputは信頼性が低く、パケットが失われることがあります。キャラクターを動かすような継続的な入力であれば、この問題はほとんど気になりませんが、1回限りのアクションであれば、確実にサーバーに到達させたいと思うかもしれません。
このような場合のために、FusionはRPC(Remote Procedure Calls)を使用しています。
Fusionには、シンプルで強力なRPCの構文が実装されています。任意のSimulationBehaviour
に対してRPCを定義するには、リターンタイプ ;void
の通常のC#メソッドを宣言し、[Rpc]
属性を付けます。プリミティブなパラメータ(bool
を除く、上記参照)のほか、構造体やFusionオブジェクトへの参照(NetworkObject
やPlayerRef
など、ネットワークのアイデンティティを持つもの)を取る可能性があります。
[Rpc]
属性では、どこから呼び出されるか、どこで実行されるかをフィルタリングすることができます。
[Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)]
public void RPC_Configure(string name, Color color)
{
playerName = name;
playerColor = color;
}
また、「RPC」というプレフィックスの使用にも注意してください。この正確な表記を使用する必要はありませんが、メソッド名の前または後に「rpc」を付ける必要があります(大文字と小文字は区別されません)。
sourceとtargetのプロパティに加えて、[Rpc]
属性にはいくつかの追加オプションのパラメータがあります。
Channel
(Reliable/Unreliable) - RPCの送信中の損失を気にしない場合は、Unreliableに設定します(デフォルトはReliableです)。InvokeLocal
(true/false) - ローカルクライアントでRPCを起動したくない場合は、falseに設定します(デフォルトはtrueInvokeResim
(true/false) - 再シミュレーション時にもRPCを起動させたい場合はtrueに設定します(デフォルトはfalse)
RPCに関する最後の注意点として、RPCには明示的な状態がないことを念頭に置いておく必要があります。たとえ「すべてのクライアント」に送信したとしても、まだオンラインになっていないクライアントはそれを受信しませんし、一度退室して戻ってきたクライアントは忘れてしまいます。このため、RPCの状態が本当に一時的なものであるか(例:チャットメッセージ)、またはその効果が間接的に[Networked]
プロパティに記録されているかを常に確認する必要があります。