5 - プロパティの変更
概要
ネットワークプロパティを定義する際、Fusionは提供されたget
スタブとset
スタブのカスタムコードを入れ替え、ネトワークステートにアクセスします。つまり、アプリケーションはプロパティの値の変更を処理するのにこのメソッドを使用できず、また個別のsetterメソッドの作成はローカルでのみ可能になります。
この問題を解消するために、Fusionでは[Networked]
属性に引数を提供しています。これは、静的コールバックメソッドを指定してプロパティが変わるごとに起動させるのに使用することができます。
これは、ステートの変化に応じてローカルのビジュアルエフェクトを生成したり、ゲームプレイロジックに直接影響を与えないその他のタスクを実行するのに便利です。プロパティの変更が再シミュレーションによって何度も行われたり(正確には予測で1回、予測が不正確であった場合はさらにもう1回で計2回)、ネットワークの状態が送信されるよりも早く2つの値の間を行き来する場合には、プロパティの変更が完全にスキップされる(またはパケットが落ちる)ことがあるので、これは重要な注意点です。
OnChanged
コールバックは、UnityのUpdate()
サイクルの一環として(厳密には、Update
の前に呼び出されます)起動しますが、シミュレーションの一環ではなく、単にあるUnityフレームから次のフレームへの変更に反応しています。これは、値の簡単な切り替えが検出されないもう一つの理由となります。1つのフレーム内で複数のスナップショットが処理されていて、最後のスナップショットがネットワークプロパティを元の値に返す場合、変更は検出されません。
RPC等ではなく変更コールバックを使用する主なメリットは、RPCはゲームが予期せぬステートになると遅延する可能性があるのに対し、コールバックは適切なティックで実行されるという点です。
変更リスナを追加する
プロパティの変更したコールバックは[Networked]
属性自体のOnChanged
パラエータで定義され、Changed<T>
タイプのシングルパラメータを受け入れることで静的なメソッドの名前をとります。T
というのは中に含まれている挙動のタイプのことです。
提供されたコールバックは静的メソッドである必要があります。静的メソッドであれば、Fusionはオブジェクトのインスタンスごとにデリゲートを割り当てるのではなく、タイプごとに単一のデリゲートを持つことができます。
この例では、ボールが発射されたときにキューブを白い色にし、段々と青色にしていくことがゴールです。
このエフェクトを引き起こすには、ホストがネットワーク変数でシングルビットをトグルします。1つのティックでスポーンできるボールは1つのみであるので、新しいスポーンはそれぞれビットの値を前のティックとは異なるものに変更してOnChanged
コールバックを呼び出します。
コードを追加する前に、この設計はうまくいかない 可能性がある という点にご留意ください。既に既述したように、特にフリップフロップ計ではない場合、変更が検出されない場合があるからです。こちらに対応できるようにするため、NetworkBool
をbyte
またはint
で置き換え、各起動時にかわりにバンプさせます。結局、ビジュアルエフェクトをどれだけ優先させるか、もしくはどれだけ帯域幅を消費させるかの問題となります。
これらを踏まえて、Player
プラスを開き新しいプロパティとコールバックのシンプルな実装を追加します。以下を参照してください。:
[Networked(OnChanged = nameof(OnBallSpawned))]
public NetworkBool spawned { get; set; }
public static void OnBallSpawned(Changed<Player> changed)
{
changed.Behaviour.material.color = Color.white;
}
これは明らかにPlayer
に、cube meshの色の変更に使用するためのマテリアルプロパティがあることを前提としています。そのまま進み、新しいプロパティを追加しましょう :
private Material _material;
Material material
{
get
{
if(_material==null)
_material = GetComponentInChildren<MeshRenderer>().material;
return _material;
}
}
色はライナーインターポレーションとしてRender()
で、現在の色から青にアップデートされるはずです。これがUpdate()
ではなくRender()``FixedUpdateNetwork()
の後に実行されることが保証されているのと、Runner.DeltaTime
ではなくTime.deltaTime
が使用されるからです。 Time.deltaTime
を使用する理由は、これがUnityのレンダリングループで実行されていてFusionシミュレーションの一部ではないからです。
public override void Render()
{
material.color = Color.Lerp(material.color, Color.blue, Time.deltaTime );
}
残りのすべては、Runner.Spawn()
を呼び出した後にspawned
プロパティをトグルして、コールバックをトリガーするためのものです:
...
Runner.Spawn(_prefabBall, transform.position+_forward, Quaternion.LookRotation(_forward));
spawned = !spawned;
...
Spawn()
が呼び出される個所が2つあることにご留意ください。その両方の箇所でspawned
をトグルします。
理由
Q: 単純に、スポーンの呼び出し時に即座に色を設定すればいいのではないでしょうか?
そうすると、ホストや入力権限のあるクライアントであれば、プレイヤーのインプットに応じて予測するため効果があるのですが、プロキシには有効ではありません。
Q: 色が、単純にローカルでRender()
内の全てのクライアントに適用されるネットワークプロパティだとしたらどうでしょうか?OnChangedハンドラの必要は確実になくなるのではないでしょうか。
確かにうまくいきますが、ホストでアニメーション化する必要があり不必要なトラフィクを生み出すことになります。一般的に、ビジュアルエフェクトはステートオーソリティによってトリガーされ、各クライアント上でそれぞれ実行されるべきです。火花が飛んでさえいれば、その火花がどちらかの方向に飛んでいるかどうかは誰も気にしません。
前のステートにアクセスする
このシンプルな例では変更したプロパティの実際の値には触れませんでしたが、コールバックに与えられたChanged<T>
パラメータにはソースビヘイビアへのレファレンスがあることに留意しておくべきです。ここが変更が行われた点で、アプリケーションがそのティック中のビヘイビアの全てのネットワークプロパティにアクセスできるようになったところだからです。
わかりづらかったかもしれませんが、ビヘイビアの前のステートを読み込み、アプリケーションへ効果的に前のティックから全てのネットワークプロパティのステートを提供するメソッドもあります:
var newValue = changed.Behaviour.someNetworkedProperty;
changed.LoadOld();
var oldValue = changed.Behaviour.someNetworkedProperty;
同様に、アプリケーションはLoadNew()
で再度新しいステートを読み込むことができます。ただし、ステートのリセットについて心配する必要はありません。ステートはこの特定のコールバックのコンテキストにおいてのみ関係があるからです。