スポーニング
はじめに
GameObjectがネットワーク上に存在するためには、以下の条件を満たす必要があります。
NetworkObject
コンポーネントを持っていること。Runner.Spawn()
メソッドを使って作成されること。
NetworkObject
は、Fusionがネットワーク全体で一意の識別子を割り当てるために使用されます。これにより、すべてのクライアントがそれぞれのインスタンスかを認識し、ネットワーク上の各オブジェクトの状態を正しく同期させることができます。Runner.Spawn()
メソッドは、いつ、どのようにして新しいオブジェクトのインスタンスをネットワーク全体の状態に追加するかをFusionに伝えます。
重要!
Unityに内蔵されているGameObject.Instantiate()
とGameObject.Destroy()
メソッドをネットワークオブジェクトに使用しないでください。ネットワーク状態が壊れたまま、Fusionシミュレーションループから完全に切り離されたローカルゲームオブジェクトが作成されてしまいます。
Runner.Spawn
FusionのNetworkRunner
インスタンスのRunner.Spawn()
メソッドは、UnityのGameObject.Instantiate()
メソッドを模倣しています。メソッドに与えることのできるパラメータは以下の通りです。
NetworkObject
タイプのプレハブ。- 位置
- 回転
- オブジェクトの入力権限を持つクライアントを識別するための
PlayerRef
。 - オブジェクトを他のインスタンスに複製する前に実行される、
NetworkRunner.OnBeforeSpawned
型のデリゲート。 - 予測されたスポーンである場合には、
NetworkObjectPredictionKey
型の予測キー。
プレハブのみが必須のパラメータで、他はすべて任意です。
C#
var obj = Runner.Spawn(prefab, Vector3.zero, Quaternion.identity, Runner.LocalPlayer, MyOnBeforeSpawnDelegate, key);
どのクライアントでもRunner.Spawn()
を呼び出すことができますが、ネットワークトポロジーによって結果は異なります。
- サーバーでは、Hostedまたはクライアント/サーバー型のモードでは、生成されたオブジェクトの所有権(State Authority)がサーバーに割り当てられ、オブジェクトが作成されてすぐに返されます。クライアントは、次のスナップショットの一部としてオブジェクトを受け取ります。
- Hostedモードまたはクライアント/サーバーモードのクライアントでは、デフォルトでは何も起こらず、コールはnullを返します。しかし、Fusionでは、一時的なローカルオブジェクトを作成して即座にフィードバックすることができます。これは予測スポーンと呼ばれます。
- Sharedモードでは、 State Authorityは常に呼び出し側に割り当てられ、インスタンスはすぐに返されます。他のクライアントは、いずれオブジェクトを受け取ります。
いずれの場合も、SpawnはFixedUpdateNetwork
から呼び出すべきで、ForwardとRe-simulationの両方のステージで呼び出すことができます。Fusionでは、予測されたローカル・スポーンの再シミュレーションの際に、同じオブジェクトを正確に返します(予測キーが一致していることが前提です)。一方、権威あるスポーンは共有モードかホスト上でのみ発生し、どちらも再シミュレーションを行いません。
入力権限
入力権限は、特定のクライアントの PlayerRef
をメソッドに渡すことで割り当てられます(これは任意です)。入力権限を与えられたクライアントは、オブジェクトの入力データを提供することができ、(ホストやサーバーに加えて)GetInput()
でその入力構造を照会することができます。
オブジェクトが入力を必要としない場合や、入力権限を持つクライアントがいない場合は、代わりに null
を渡すことができます。
OnBeforeSpawned
NetworkRunner.OnBeforeSpawned
パラメータには、デリゲートのシグネチャに一致するメソッドまたはラムダ式を指定できます。
C#
public delegate void OnBeforeSpawned(NetworkRunner runner, NetworkObject obj);
このデリゲートは、オブジェクトが作成された後、すべてのインスタンスで同期される前に呼び出されます。これにより、システムの他の部分がオブジェクトにアクセスできるようになる前に、呼び出し側がオブジェクトのカスタム初期化を追加で行うことができます。カスタムのネットワークプロパティを初期化するのに適した場所です。
C#
private void MySpawnFunction(){
Runner.Spawn(
_objPrefab,
Vector3.zero,
Quaternion.identity,
inputAuthority: null,
InitializeObjBeforeSpawn,
predictionKey: null
);
}
private void InitializeObjBeforeSpawn(NetworkRunner runner, NetworkObject obj)
{
var objSB = obj.GetComponent<ObjSimulationBehaviour>();
objSB.InitializeObjSettings(_currentExplosionForce);
}
また、ラムダ式を使って、より多くのパラメータを渡すことも可能です。
C#
private void MySpawnFunction(){
Runner.Spawn(
_objPrefab,
Vector3.zero,
Quaternion.identity,
inputAuthority: null,
(Runner, NO) => NO.GetComponent<MyCustomBehaviour>().Init(myInt, myParameter)
predictionKey: null
);
}
Spawned
Fusionが新しいオブジェクトを作成すると、そのオブジェクトのSpawned()
メソッドが呼び出されます。Spawned()
コールバックは、ネットワークに接続されていない変数やサブシステムをリセットするためのものです。例えば、アプリケーションがオブジェクトプーリングを使用している場合、オブジェクトがリサイクルされてデフォルトの状態ではなくなったため、Spawned()
は値をプレハブのデフォルトにリセットする必要があります。すべての NetworkBehaviour
コンポーネントは ISpawned
インターフェースを実装しており、NetworkBehaviour
から派生したすべてのコンポーネントは、このインターフェースをオーバーライドして実装することができます。ただし、SimulationBehaviour
由来のコンポーネントは、Spawned()
メソッドを呼び出すために、ISpawned
とそのメソッドを明示的に実装する必要があります。
Spawned()
は、Fusionがオブジェクトの存在を初めて認識したときに呼び出されます。これは、権威あるピアがオブジェクトを最初に生成したときのティックとは一致しませんし、必ずしも同じティックの近くではありません。このため、Networked
の状態を Spawned()
で初期化したり、RPC
を Spawned()
で送信するべきではありません。
State Authorityを持つピアでのスポーンでは、
Runner.Spawn()
の直後にSpawned()
が呼ばれます。入力権限を持つピア上の予測されたスポーンの場合、
Spawned()
はネットワークの状態が割り当てられたとき(つまり、スポーンが確認されたとき)に呼び出されます。プロキシ(ピアが入力権限も状態権限も持っていないオブジェクト)の場合、ローカルに存在しないオブジェクトのネットワーク状態をFusionが受け取ると、
Spawned()
が呼ばれます。特に、後から参加したプレイヤーはだいぶ前に生成されたオブジェクトを受け取る可能性が高く、そのオブジェクトの "birth tick "の元の状態は関係ありません。
スポーン時に状態を初期化する必要がある場合は、Runner.Spawn()
に供給されるスポーン前のNetworkRunner.OnBeforeSpawned
コールバックを使用します。
IAfterSpawned
IAfterSpawned.AfterSpawned()
は、NetworkObject
のバッチがスポーンされ、すべてのISpawned.Spawned()
コールバックが終了した後に呼び出されます。これはFusionでStart()
の代用と考えられます。NetworkBehaviour
が、スポーンの後に他のNetworkBehaviour
に値を取得したり設定したりする必要がある場合にこのコールバックを使用します。実行順序について考慮する必要はありません。
Despawn
ネットワークオブジェクトを削除するには、そのオブジェクトのState Authorityを持つピアが、Runner.Despawn()
を呼び出します。
Despawned
ISpawned
を実装したクラスに対して、Runner.Spawn()
が Spawned()
メソッドの呼び出しを引き起こすのと同様に、Despawn()
は、IDespawned()
を実装したクラスに対してIDespawned()
メソッドの呼び出しを引き起こします。
すべてのNetworkBehaviour
はIDespawned
を実装していますが、SimulationBehaviour
は明示的に追加する必要があります。
スポーン予想
Predictive Spawningは、権限を持たないクライアントが将来のオブジェクトの存在を予測し、ネットワーク上でオブジェクトの存在が成功または失敗したと確認されるまで、ローカルのプレースホルダーをシミュレートすることができます。
この機能を実現するためには2つのことが必要です。
- クライアントが一時的なローカルオブジェクトと将来の「実際の」インスタンスと一致するためには、予測キーと呼ばれる一意の識別子を作成する必要があります。これにより、スポーンが確認されたときに、ローカルオブジェクトをファーストクラスのネットワークオブジェクトにアップグレードすることができます。
- クライアントは、確定またはキャンセルされるまで、ローカルオブジェクトを非ネットワークオブジェクトとして扱う(=オブジェクトのネットワーク状態にアクセスしない)必要があります。
NetworkObjectPredictionKey
Runner.Spawn()
メソッドに渡される predictionKey
パラメータは4バイトで構成されています。アプリケーションは、以下の点を考慮すれば、自由に独自のキーを考案することができます。
- キーは、クライアントと権威あるホスト/サーバで同じでなければなりません。ランダムにしたり、ローカルデータに基づいたりすることはできません。
2.プレイヤーと現在のティックに対して一意である必要があります。 ティック(またはその下端部分)とプレーヤーIDをキーに含めるのが賢明です。
C#
var predictionKey = new NetworkObjectPredictionKey {Byte0 = (byte) Runner.Simulation.Tick, Byte1 = playerIndex};
注意: 同一ティック内に複数のネットワークオブジェクトがクライアントによって予測的に生成された場合、生成された様々なNetworkObjects
を見分けるための追加情報(例えば、共有カウンター)が必要となります。
IPredictedSpawnBehaviour
スポーンされたゲームオブジェクトが予測状態にある間は、Fusionシミュレーションの一部ではなく、ネットワーク化されたプロパティは、アクセスできる状態がまだないので機能しません。予測されたオブジェクトを実際のオブジェクトのように動作させるためには、いくつかの追加ロジックが必要です。このロジックは、IPredictedSpawnBehaviour
インターフェースを使って実装します。
C#
public interface IPredictedSpawnBehaviour {
void PredictedSpawnSpawned();
void PredictedSpawnUpdate();
void PredictedSpawnRender();
void PredictedSpawnFailed();
void PredictedSpawnSuccess();
}
Spawned, Update, Render
最初の3つのメソッドは、機能的な SimulationBehaviour
および NetworkBehaviour
コンポーネントにある Spawned()
, FixedUpdateNetwork()
および Render()
メソッドに一致します。これらのメソッドは、これらのメソッドと同じ条件で呼び出されます。ただし、これらのメソッドは、オブジェクトが「予測」された状態にあるときに、排他的かつ明示的に呼び出されるという違いがあります。
これらのメソッドの実装としては、単純にそれぞれSpawned()
、FixedUpdateNetwork()
、Render()
を呼び出すことができますが、アプリケーションはネットワークプロパティが利用できないことを考慮する必要があります。これに対する1つの解決策は、ネットワーク化されたプロパティを予測スポーンのチェックでラップし、次のように2セットの変数を維持することです。
C#
[Networked] private Vector3 _networkedVelocity { get; set; }
private Vector3 _predictedVelocity;
public Vector3 Velocity
{
get => Object.IsPredictedSpawn ? _predictedVelocity : _networkedVelocity;
set
{
if (Object.IsPredictedSpawn)
_predictedVelocity = value;
else
_networkedVelocity = value;
}
}
Failed と Success
PredictedSpawnFailed()
とPredictedSpawnSuccess
は予測に特有のもので、サーバーやホストがスポーンアクションの成功または失敗を確認したときにトリガーされます。予測が成功した場合は PredictedSpawnSuccess()
が呼ばれ、そうでない場合は PredictedSpawnFailed()
が発生します。
一時オブジェクトは完全にネットワーク化されたオブジェクトにプロモートされ、何も変更されていないかのように存在し続けるため、アプリケーションが正常なスポーンを処理する必要はほとんどありません。 ただし、予測の失敗は処理する必要があります。 最も簡単な実装は、オブジェクトを破棄することです。
予測されたオブジェクトでRunner.Dispawn()
を使用する際には、問題のオブジェクトが予測された状態であったことを示すために、追加のブールパラメータが必要です。
C#
public void PredictedSpawnFailed()
{
Runner.Despawn(Object, true);
}
Runner.Spawn()
はFusion Object Poolを介して一時オブジェクトを取得し、それを再びプールに戻す必要があるため、 Runner.Despawn()
を使用する必要があります。