tanaka's Programming Memo

プログラミングについてのメモ。

Unity5.1 ネットワークシステムの状態同期

公式サイトが和訳されました → Unity - マニュアル: ステートの同期



前へ | 次へ

UNet Unity5.1からの新しいネットワークシステム
Unity5.1のネットワークマニュアル斜め読み(2)
UNet NetworkManagerの利用
Unity5.1 ネットワークシステムのオブジェクト生成
Unity5.1 ネットワークシステムの状態同期
Unity5.1 ネットワークシステムのRemote Actions
Unity5.1 ネットワークシステム PlayerObjects
Unity5.1 ネットワークシステム Object Visibility
Unity5.1 ネットワークシステム Network Messages
Unity5.1 ネットワークシステム Matchmaker
Unity5.1 ネットワークシステム Scene Objects
Unity5.1 ネットワークシステム:シングルプレイヤーゲームを多人数に対応させる
Unity5.1 ネットワークシステム Multiplayer Lobby
Unity5.1 ネットワークシステム Network Clients and Servers
Unity5.1 ネットワークリファレンス概要

公式サイトの以下のページの斜め読みメモです。
Unity - マニュアル: ステートの同期

状態同期(State Synchronization)

状態同期はサーバーからリモートクライアントに対して実行されます。シーンをサーバーと共有すると、ローカルクライアントは状態同期の変数の値を保持しなくなります。ローカルクライアントと同期するデータの変数はローカルとサーバーのどちらにも存在します。それをサーバーと一致させるための状態同期の処理がクライアントで呼ばれます。

データの向きは一方向であり、リモートクライアントからサーバーに向けては同期されません。これは後述するCommandsの振る舞いです。

SyncVars

SyncVarsはサーバーからクライアントにデータを同期させるためのNetworkBehaviourスクリプトのメンバー変数です。オブジェクトがspawnされるかゲーム中に新しいプレイヤーが参加して増えると、それらに対して、表示中のネットワークオブジェクトの持つすべてのSyncVarの最新の状態が送信されます。SyncVarのメンバー変数は[SyncVar]属性を使って定義します。

以下、公式サイトから抜粋。

class Player : NetworkBehaviour
{

    [SyncVar]
    int health;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        health -= amount;
    }
}

訳注:上記では、int型のhealthという変数が状態同期する変数となり、サーバー上で値が変(更されるとクライアントに送信されます。

SyncVarの状態はクライアントのOnStartClient()が呼び出される前に設定されます。そのためOnStartClient()内でのオブジェクトの状態は有効な値になっていることが保証されます。

以下のような基本型やUnityで定義されている型がSyncVarsにできます。

  • int型
  • string型
  • float型
  • Vector3
  • ユーザー定義の構造体

ただし構造体に対するSyncVarの更新は、構造体全体を一括して更新する仕組みになっていて、構造体の個別の要素の変化だけを更新することはしません。

SyncVarの変更があると、サーバーが自動的に変更されたSyncVarのみ更新を実行します。手動で変数の変更を監視して管理する必要はありません。

SyncLists

SyncListはSyncVarを個別の値ではなくリストにしたものです。SyncListのデータはSyncVarの状態による初期値の更新を含みます。SyncListはSyncVar属性は必要とせずクラスで指定します。SyncListのための組み込みクラスを以下に挙げます。

  • SyncListString
  • SyncListFloat
  • SyncListInt
  • SyncListUInt
  • SyncListBool

ユーザー定義の構造体をリストにするためのSyncListStructクラスもあります。

データ通信関数のカスタマイズ(Custom Serialization Functions)

SyncVarの基本機能だけでクライアントへの状態同期が完了する場合も多いですが、より複雑なデータ通信が必要となる場合もあります。NetworkBehaviourに定義されているSyncVarのデータ通信のための仮装関数を上書きすることで、開発者は独自のデータ通信を行うことができます。

それらの仮装関数は以下のものです。

  • public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
  • public virtual void OnDeSerialize(NetworkReader reader, bool initialState);

initialStateフラグは、オブジェクトの初回送信と、データの更新による送信を見分けるのに利用します。初めてクライアントにデータを送信する場合はすべてのデータを送信する必要があります。一方、更新処理では変更のあった変数のみを送信することで通信量を抑制できます。なお、SyncVarのフック関数はinitialStateがtrueの初期化時には呼ばれず、更新処理時にのみ呼ばれます。

もしクラスがSyncVarを持っていた場合、これらの関数の実装は自動的に追加されます。そのため、SyncVarを持っているクラスではカスタマイズしたデータ送信関数を一緒に持たせることはできません。

OnSerialize関数からtrueを返した場合、更新(update)が送られたことを示します。trueが返された場合、データの変更を表すダーティビットが0にクリアされます。falseが返された場合、ダーティビットはそのままにします。これにより、毎フレームでデータを送信することをせず、システムが準備できるまでの間の複数の変更を溜め込んでまとめて送信するようなことができます。

送信の流れ(Serialization Flow)

NetworkIdentityを追加したゲームオブジェクトは様々なスクリプトをNetworkBehaviourから得ることができます。これらのオブジェクトのデータ送信の流れは以下の通りです。

サーバー上

()内は訳の補足です。

  • NetworkBehaviourごとに(変更したSyncVarを判定するための)ダーティマスクが追加される。このマスクはOnSerialize()関数内でsyncVarDirtyBitsを使って参照できる
  • NetworkBehaviourスクリプト内のSyncVarごとに、ダーティマスクのビットが割り当てられる
  • SyncVarの値が変更されると、その値に対応するダーティマスクのビットが(1に)セットされる
  • SetDirtyBit()を利用して直接ダーティマスクを設定することもできる
  • サーバー上の更新ループの中で、NetworkIdentityオブジェクトがチェックされる
  • NetworkIdentity上のNetworkBehaviourのダーティビットが設定されていた場合、そのオブジェクト用に(状態を更新するための)UpdateVarパケットが生成される
  • OnSerialize()関数が呼ばれ、オブジェクト上の各NetworkBehaviour上にUpdateVarが用意される
  • 値が変更されていないNetworkBehaviourはダーティマスクのパケットに0を書き込む
  • 値が変更されたNetworkBehaviourはダーティマスクをパケットに書き込み、SyncVarの値を変更する
  • OnSerialize()からtrueが返されたらそのNetworkBehaviourのダーティマスクを0にリセットする。これにより、値が更新されない限り、次回はデータは送信されない
  • UpdateVarパケットがオブジェクトを持っているクライアントへ送信される

クライアント上

  • オブジェクトへのUpdateVarパケットを受信する
  • オブジェクトのNetworkBehaviourが設定されているスクリプトごとのOnDeserialize関数が呼ばれる
  • オブジェクト上のNetworkBehaviourごとにダーティマスクを読み込む
  • ダーティマスクが0のとき、OnDeserialize()関数は何も読み込まずに終わる
  • ダーティマスクが0以外の時、OnDeserialize()関数はダーティマスクに設定されたビットに対応するSyncVarの値を読み込んで設定する
  • SyncVarフック関数が設定されていたら(カスタムのデータ受信関数)、ストリームから値を読み込む処理が実行される

(上記の例をコードにして示したものがUnity - マニュアル: ステートの同期の最後の方にあります。最初にSyncVarを定義しているスクリプト例。次にそのスクリプトに対して自動追加されるOnSerialize()の例。3つ目に同様のOnDeserialize()の例。)

もしNetworkBehaviourがデータ送信のための関数を持ったベースクラスを持っていた場合、ベースクラスのデータ送信も呼ばれる必要があります。

オブジェクトの状態のために作られるUpdateVarパケットは、クライアントに送信される前にバッファ内に収集される可能性があり、通信層が1つのパケットは複数のオブジェクトのための更新情報を含んでいる可能性があります。


前へ | 次へ