5 - プロパティの変更
概要
ここでは、ネットワークプロパティを使用してネットワーク上でデータを同期する方法を説明します。
ネットワークプロパティ
NetworkObject
にNetworkTransform
コンポーネントを追加するとtransform
を同期できますが、それ以外の状態(スクリプトの変数など)は同期されません。この状態をネットワーク上で同期するには、[Networked]
属性を付けたネットワークプロパティが必要です。ネットワークプロパティによって、状態権限者のオブジェクトの状態がその他のクライアント全員に同期されます。
状態権限を持たないクライアントがネットワークプロパティを変更しても、変更はネットワーク上で同期されません。その変更はローカル上の予測として適用され、状態権限者の変更によって上書きされます。ネットワークプロパティの値を全てのクライアント間で同期したい場合は、状態権限者がネットワークプロパティを変更する必要があることに注意してください。
シンプルな例として、ネットワークプロパティでプレイヤーの色を同期してみましょう。新規スクリプトを作成し、名前をPlayerColor
にします。スクリプトには、ネットワークプロパティと、オブジェクトのMaterial
の参照を追加します。
この例の目標は、ボールを発射した際にプレイヤーを白色にした後に青色にフェードさせることです。
ネットワークプロパティの宣言
色変更のエフェクトを発生させるために、ホストはネットワーク上でbool
値を切り替えます。1ティック内でボールは二つ以上スポーンしないようになっているため、ボールをスポーンするたびにbool
値を切り替えることで、その変化を検出することができます。
この仕様はうまくいかない可能性があることに気を付けてください。値が非常に頻繁に変更される場合には、その変更が検出されないことがあります。この問題を緩和する方法の一つは、bool
をbyte
かint
に置き換えて、呼び出しのたびに値をインクリメントすることです。つまるところ、ビジュアルエフェクトと帯域幅消費量のどちらを優先するかのトレードオフになります。
Player
クラスを開き、新規のネットワークプロパティを追加してください。
C#
[Networked]
public bool spawnedProjectile { get; set; }
ネットワークプロパティを定義すると、Fusionはget
とset
スタブをネットワーク上の状態にアクセスするためのコードに置き換えます。つまりアプリケーションでは、これらのメソッドをプロパティの値を変更するために使用したり、ローカル上でのみ動作するようなセッターメソッドを作成したりすることはできません。
上記の問題はChangeDetector
で変更検出することで解決できます。以下のように、スクリプトに新規でChangeDetector
を追加し、Spawn
メソッド内で初期化してください。
C#
private ChangeDetector _changeDetector;
public override void Spawned()
{
_changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);
}
また、マテリアルを参照する_material
フィールドを追加し、Awake
で値を設定してください。
C#
public Material _material;
private void Awake()
{
_material = GetComponentInChildren<MeshRenderer>().material;
}
さらに、以下のようなRender
メソッドを追加してください。
C#
public override void Render()
{
foreach (var change in _changeDetector.DetectChanges(this))
{
switch (change)
{
case nameof(spawnedProjectile):
_material.color = Color.white;
break;
}
}
}
このコードは、全てのネットワークプロパティの変更を反復します。変更は前回の呼び出しからの差分になるため、ここでは前回のRender
呼び出し時との差分になります。全てのクライアントで、値の変更が検出されるとMaterial
の色が更新されます。
これは、状態の変化に応じてローカル上でビジュアルエフェクトを生成したり、ゲームプレイロジックに直接影響を与えない処理を実行したりするのに便利です。重要な注意点ですが、プロパティの変更は再シミュレーション時に何度も(正確には、予測で一回、予測が不正確だった場合にもう一回、計二回)行われます。また、ネットワーク上の状態が送信されるよりも早く値が変更されて元の値に戻ったり、パケットがドロップしたりした場合は、プロパティの変更が完全にスキップされることがあります。
RPC等ではなく変更検出APIを使用する主なメリットは、RPCは状態が変更された後に実行される可能性があるのに対して、変更検出APIは状態が変更されたティック内で直ちに実行できる点です。
色の更新
色は、Render()
で現在の色から青色に線形補間されます。これがUpdate()
ではなくRender()
で行われているのは、Render()
がFixedUpdateNetwork()
の後に実行されることが保証されているためです。また、Runner.DeltaTime
ではなくTime.deltaTime
が使用されているのは、Render()
がFusionのシミュレーションの一部ではなくUnityのライフサイクル内で実行されるためです。
以下のコードをRender
メソッドの最終行に追加してください。
C#
_material.color = Color.Lerp(_material.color, Color.blue, Time.deltaTime);
最後に、Runner.Spawn()
を呼び出した後にspawnedProjectile
プロパティを切り替えて、変更検出APIを呼び出します。
C#
Runner.Spawn(_prefabBall, transform.position+_forward, Quaternion.LookRotation(_forward));
spawnedProjectile = !spawnedProjectile;
Spawn()
は二か所で呼び出されているので、その両方でspawnedProjectile
を切り替えてください。
よくある質問
Q: 単純にスポーン時に色を初期化してはいけないのでしょうか?
ホストや入力権限のあるクライアントでは正しく動作しますが、プロキシ(クライアントが入力権限も状態権限も持たないオブジェクト)ではうまく動作しないためです。
Q: 色をネットワークプロパティにして、単純にRender()
でそのプロパティの値を更新すれば、変更を検出する必要はないのではないでしょうか?
確かに動作はしますが、値を頻繁に更新することで不要な通信が大量に発生します。一般的に、ビジュアルエフェクトは状態権限を持つプレイヤーによって引き起こされた後は、各クライアント上で独立して実行すべきです。派手なエフェクトを出すことは重要ですが、そのエフェクトの火花が飛ぶ方向まで正確に同期する必要はありません。
Back to top