Version: 2021.2
言語: 日本語
状態同期
ネットワークでの可視性

高度な状態同期

重要: UNet は非推奨のソリューションになり、現在、新しい Multiplayer とネットワーキングソリューション (Netcode for GameObjects) が開発中です。詳細は、GameObjects Web サイトの Unity Netcode を参照してください。

大抵の場合は、ゲームスクリプトがその状態をクライアント向けにシリアライズするには SyncVars を使用するだけで十分です。ただし、より複雑なシリアル化のコードが必要な場合もあります。このページは、Unity の通常の 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 を返します。True が返されるとそのスクリプト用のダーティビットは 0 に設定されます。もし、False が返されるとダーティビットは変更されません。これを利用すると、変更を毎フレーム送信するのではなく、時間の経過とともに蓄積した複数のスクリプトへの変更を、システムの準備が整ったときに送信することが可能になります。

シリアル化のルール

NetworkIdentity コンポーネントを持ったゲームオブジェクトは NetworkBehaviour から派生するスクリプトを複数持つことができます。オブジェクトのシリアライズの流れは以下の通りです。

サーバー上

  • NetworkBehaviour にダーティマスクがあります。このマスクは OnSerialize の中でsyncVarDirtyBits として使用可能です。

  • NetworkBehaviour スクリプト内のそれぞれの SyncVar にダーティマスクの中のビットが 1 つ割り当てられます。

  • SyncVar の値が変更されると、その SyncVar のビットがダーティマスクで設定されます。

  • または、SetDirtyBit() の呼び出しによって、ビットが直接ダーティマスクに書き込まれます。

  • NetworkIdentity ゲームオブジェクトが、その更新ループの 1 部としてサーバーで確認されます。

  • NetworkIdentityNetworkBehaviours がダーティな場合、そのゲームオブジェクトのために UpdateVars パケットが作成されます。

  • OnSerialize を呼び出すことによって、UpdateVars パケットがゲームオブジェクトの各 NetworkBehaviour に加えられます。

  • ダーティでない NetworkBehaviour は、そのダーティビットのパケットに 0 を記述します。

  • ダーティな NetworkBehaviour はそのダーティマスクに書き込み、その後、変更された SyncVars の値を書き込みます。

  • OnSerializeNetworkBehaviour に対して True を返すと、その NetworkBehaviour ダーティマスクはリセットされます。そのため、その値が変更されるまではパケットは再度送信されません。

  • UpdateVars パケットが、ゲームオブジェクトを監視しているクライアントで準備の整ったものに送信されます。

クライアント上

  • オブジェクトが UpdateVars packet を受け取ります。

  • OnDeserialize 関数がゲームオブジェクトの各 NetworkBehaviour スクリプトで呼び出されます。

  • 各オブジェクトの NetworkBehaviourスクリプトがダーティマスクを読み込みます。

  • NetworkBehaviour のダーティマスクが 0 の場合は、OnDeserialize 関数は、それ以上読み込むことなくリターンします。

  • ダーティマスクが 0 以外の値のときは、OnDeserialize 関数は、設定されたダーティビットに対応する SyncVar の値を読み込みます。

  • SyncVar フック関数がある場合、ストリームから読み込まれた値で実行されます。

したがって、このスクリプトの場合


public class data : NetworkBehaviour
{

    [SyncVar]
    public int int1 = 66;

    [SyncVar]
    public int int2 = 23487;

    [SyncVar]
    public string MyString = "Example string";
}

以下のコード例は、生成された OnSerialize 関数を示しています。


public override bool OnSerialize(NetworkWriter writer, bool forceAll)
{
    if (forceAll)
    {
        // 初めてオブジェクトをクライアントに送信。すべてのデータを送ります (ダーティビットは含みません)
        writer.WritePackedUInt32((uint)this.int1);
        writer.WritePackedUInt32((uint)this.int2);
        writer.Write(this.MyString);
        return true;
    }
    bool wroteSyncVar = false;
    if ((base.get_syncVarDirtyBits() & 1u) != 0u)
    {
        if (!wroteSyncVar)
        {
            //これが、初めての SyncVar の書き込みである場合は、ダーティビットを書き込みます
            writer.WritePackedUInt32(base.get_syncVarDirtyBits());
            wroteSyncVar = true;
        }
        writer.WritePackedUInt32((uint)this.int1);
    }
    if ((base.get_syncVarDirtyBits() & 2u) != 0u)
    {
        if (!wroteSyncVar)
        {
            // これが、初めての SyncVar の書き込みである場合は、ダーティビットを書き込みます
            writer.WritePackedUInt32(base.get_syncVarDirtyBits());
            wroteSyncVar = true;
        }
        writer.WritePackedUInt32((uint)this.int2);
    }
    if ((base.get_syncVarDirtyBits() & 4u) != 0u)
    {
        if (!wroteSyncVar)
        {
            // これが、初めての SyncVar の書き込みである場合は、ダーティビットを書き込みます
            writer.WritePackedUInt32(base.get_syncVarDirtyBits());
            wroteSyncVar = true;
        }
        writer.Write(this.MyString);
    }

    if (!wroteSyncVar)
    {
        //  SyncVar の書き込みがない場合は、ダーティビットに 0 を書き込みます
        writer.WritePackedUInt32(0);
    }
    return wroteSyncVar;
}

以下のコード例は OnDeserialize 関数を示しています。


public override void OnDeserialize(NetworkReader reader, bool initialState)
{
    if (initialState)
    {
        this.int1 = (int)reader.ReadPackedUInt32();
        this.int2 = (int)reader.ReadPackedUInt32();
        this.MyString = reader.ReadString();
        return;
    }
    int num = (int)reader.ReadPackedUInt32();
    if ((num & 1) != 0)
    {
        this.int1 = (int)reader.ReadPackedUInt32();
    }
    if ((num & 2) != 0)
    {
        this.int2 = (int)reader.ReadPackedUInt32();
    }
    if ((num & 4) != 0)
    {
        this.MyString = reader.ReadString();
    }
}

もし NetworkBehaviour に基本クラスがあり、それがシリアル化関数も持っている場合、その基本クラスの関数も呼び出されます。

オブジェクトの状態更新用に作成されたUpdateVar パケットはクライアントに送信される前にバッファーに集められるため、1 つのトランスポート層パケットに複数のオブジェクトの更新が含まれることがあります。

状態同期
ネットワークでの可視性