请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
 

State Synchronization

 
 
State Synchronization is done from the Server to Remote Clients. The local client does not have data serialized to it, since it shares the scene with the server. Any data serialized to a local client would be redundant. SyncVar hooks however are called on local clients. 状态同步是由从服务器到客户端的。本地客户端没有序列化的数据,因为它和服务器共享同一个场景。任何为本地客户端序列化的数据都是冗余的。然而,SyncVar钩子函数会被本地客户端调用。
 
Data is not synchronized from remote clients to the server. This is job of Commands. 数据不会从客户端向服务器同步,从客户端向服务端上的数据操作,叫做命令(Commands)。
 
SyncVars
 
SyncVars are member variables of NetworkBehaviour scripts that are synchronized from the server to clients. When an object is spawned, or a new player joins a game in progress, they are sent the latest state of all SyncVars on networked objects that are visible to them. Member variables are made into SyncVars by using the [SyncVar] custom attribute: 同步化变量(SyncVars)是继承自NetworkBehaviour类的类成员变量,他们会被服务端同步到客户端上。当一个物体被spawn出来之后,或者一个新的玩家途中加入游戏后,该玩家会接收到他的视野内所有object的同步化变量。类成员变量通过[SyncVar]标签被配置成同步化变量:如下代码所示:
 
class Player : NetworkBehaviour
{
    [SyncVar]
    int health;

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

        health -= amount;
    }
}
 
The state of SyncVars is applied to objects on clients before OnStartClient() is called, so the state of the object is guaranteed to be up-to-date inside OnStartClient(). 在客户端的OnStartClient函数被调用之前,同步化变量的状态在就被应用到object上了,所以在OnStartClient函数中,object的状态已经是最新的数据
 
SyncVars can be basic types such as integers, strings and floats. They can also be Unity types such as Vector3 and user-defined structs, but updates for struct SyncVars are sent as monolithic updates, not incremental changes if fields within a struct change. There can be up to 32 SyncVars on a single NetworkBehaviour script - this includes SyncLists. 同步化变量可以是基础类型,如整数,字符串和浮点数。也可以是Unity内置数据类型,如Vector3和用户自定义的结构体,但是对结构体类型的同步化变量,如果只有几个字段的数值有变化,整个结构体都会被发送。每个NetworkBehaviour脚本可以有最多32个同步化变量,包括同步化列表(见下面的解释)。
 
SycnVar updates are sent automatically by the server when the value of a SyncVar changes. There is no need to perform any manual dirtying of fields for SyncVars. 当同步化变量有变化时,服务端会自动发送它们的最新数据。不需要手工为同步化变量设置任何的脏数据标志位。
 
SyncLists
 
SyncLists are like SyncVars but they are lists of values instead of individual values. SyncList contents are included in initial state updates with SyncVar state. SyncLists do not require the SyncVar attributes, they are specific classes. There are built-in SyncList types for basic types: 同步化列表类似于同步化变量,但是它们是一些值的列表。而不是单个值。同步化列表和同步化变量都包含在初始的状态更新里。同步化列表不需要[SyncVar]属性标识,他们是特殊的类。内建的基础类型属性列表有:
 
SyncListString
SyncListFloat
SyncListInt
SyncListUInt
SyncListBool
 
There is also SyncListStruct which can be used for lists of user-defined structs. The struct used SyncListStruct derived class can contain members of basic types, arrays, and common Unity types. They cannot contain complex classes or generic containers. 还有个SyncListStruct类,可以用来让用户自定义的结构体。从SyncListStruct派生出的结构体类,可以包含基础类型,数组和通用Unity类型的成员变量,但是不能包含复杂的类和通用容器。
 
SyncLists have a SyncListChanged delegate named Callback that allows clients to be notified when the contents of the list change. This delegate is called with the type of operation that occurred, and th eindex of the item that the operation was for. 同步化列表有一个叫做SyncListChanged的回调函数,可以使客户端能接收到列表中的数据改动的通知。这个回调函数被调用时,会被通知到操作类型,和修改的变量索引
 
public class MyScript : NetworkBehaviour
{
    public struct Buf
    {
        public int id;
        public string name;
        public float timer;
    };
            
    public class TestBufs : SyncListStruct<Buf> {}
    TestBufs m_bufs = new TestBufs();
    
    void BufChanged(Operation op, int itemIndex)
    {
        Debug.Log("buf changed:" + op);
    }
    
    void Start()
    {
        m_bufs.Callback = BufChanged;
    }
}
 
Often the use of SyncVars is enough for scripts to serialize their state to clients, but some cases require more complex serialization code. The virtual functions on NetworkBehaviour that are used for SyncVar serialization can be implmented by developers to perform their own custom serialization. These functions are: 通常在脚本中使用同步化变量就够了,但是有些情况下也需要更复杂的序列化代码。NetworkBehaviour中的虚函数允许开发者定制自己的序列化函数,这些函数有
 
public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
public virtual void OnDeSerialize(NetworkReader reader, bool initialState);
 
The initialState flag is useful to differentiate between the first time an object is serialized and when incremental updates can be sent. The first time an object is sent to a client, it must include a full state snapshot, but subsequent updates can save on bandwidth by including only incremental changes. Note that SyncVar hook fucntion are not called when initialState is true, only for incremental updates. 参数initalState可以用来标识是当前的操作是第一次序列化数据,还是只发送增量的数据。如果是第一次发送给客户端,必须要包含所有状态的数据,后续的更新只需要包含增量的修改
 
If a class has SyncVars, then implementations of these functions are added automatically to the class. So a class that has SyncVars cannot also have custom serialization functions.. 如果一个类里面声明了同步化变量,这些函数的实现会自动被加到类里面,因此一个有同步化变量的类不能定义自己的序列化函数。
 
The OnSerialize function should return true to indicate that an update should be sent. If it returns true, then the dirty bits for that script are set to zero, if it returns false then the dirty bits are not changed. This allows multiple changes to a script to be accumulated over time and sent when the system is ready, instead of every frame. OnSerialize函数应该返回true来表示示有更新需要发送,如果它返回了true,这个类的所有脏标志位都会被清除,如果它返回false,则脏标志位不会被修改。这可以允许将多次改动合并在一起发送,而不需要每一帧都发送。
 
Serialization Flow
 
Game objects with the NetworkIdentity component can have multiple scripts derived from NetworkBehaviour. The flow for serializing these objects is:
On the server:
具有NetworkIdentity组件的game object可以带有多个从NetworkBehaviour派生出来的脚本,这些物体的序列化流程为:
在服务器上:
 
1 Each NetworkBehaviour has a dirty mask. This mask is available inside OnSerialize as syncVarDirtyBits

2 Each SyncVar in a NetworkBehaviour script is assigned a bit in the dirty mask.

3 Changing the value of SyncVars causes the bit for that SyncVar to be set in the dirty mask
Alternatively, calling SetDirtyBit() writes directly to the dirty mask

4 NetworkIdentity objects are checked on the server as part of it’s update loop

5 If any NetworkBehaviours on a NetworkIdentity are dirty, then an UpdateVars packet is created for that object

6 The UpdateVars packet is populated by calling OnSerialize on each NetworkBehaviour on the object

7 NetworkBehaviours that are NOT dirty write a zero to the packet for their dirty bits

8 NetworkBehaviours that are dirty write their dirty mask, then the values for the SyncVars that have changed

9 If OnSerialize returns true for a NetworkBehaviour, the dirty mask is reset for that NetworkBehaviour, so it will not send again until it’s value changes.

10 The UpdateVars packet is sent to ready clients that are observing the object
1 每个NetworkBehaviour上都有一个脏数据掩码,这个掩码可以在OnSerialize函数中通过syncVarDirtyBits访问到

2 NetworkBehavious中的每个同步化变量被指定了脏数据掩码中的一位

3 对同步化变量的修改会使对应的脏数据位被设置,或者可以通过调用SetDirtyBit函数直接修改脏数据标志位

4 服务器的每个Update调用都会检查他的NetworkIdentity组件

5 如果有标记为脏的NetworkBehaviour,就会为那个物体创建一个更新数据包

6 每个NetworkBehaviour组件的OnSerialize函数都被调用,来构建这个更新数据包

7 没有脏数据位设置的NetworkBehaviour在数据包中添加0标志

8 有脏数据位设置的NetworkBehavious写入他们的脏数据和有改动的同步化变量的值

9 如果一个NetworkBehavious的OnSerialize函数返回了True,那么他的脏标志位被重置,因此直到下一次数据修改之前不会被再次发送

10 更新数据包被发送到能看见这个物体的所有客户端
 
On the client: 在客户端:
 
1 an UpdateVars packet is received for an object

2 The OnDeserialize function is called for each NetworkBehaviour script on the object

3 Each NetworkBehaviour script on the object reads a dirty mask.

4 If the dirty mask for a NetworkBehaviour is zero, the OnDeserialize functions returns without reading any more

5 If the dirty mask is non-zero value, then the OnDeserialize function reads the values for the SyncVars that correspond to the dirty bits that are set

6 If there are SyncVar hook functions, those are invoked with the value read from the stream.
1 接收到一个物体的更新数据包

2 每个NetworkBehavious组件的OnDeserialize函数被调用

3 这个物体上的每个NetworkBehavious组件读取脏数据标识

4 如果关联到这个NetworkBehaviour组件的脏数据位是0,OnDeserialize函数直接返回;

5 如果脏数据标志不是0,OnDeserialize函数继续读取后续的同步化变量

6 如果有同步化变量的钩子函数,调用钩子函数
 
So for this script: 所有对于以下的代码:
 
public class data : NetworkBehaviour
{
    [SyncVar]
    public int int1 = 66;

    [SyncVar]
    public int int2 = 23487;

    [SyncVar]
    public string MyString = "esfdsagsdfgsdgdsfg";
}
 
The generated OnSerialize function is something like: 所产生的序列化函数OnSerialize,如下代码所示:
 
public override bool OnSerialize(NetworkWriter writer, bool forceAll)
{
    if (forceAll)
    {
        // the first time an object is sent to a client, send all the data (and no dirty bits)
        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)
        {
            // write dirty bits if this is the first SyncVar written
            writer.WritePackedUInt32(base.get_syncVarDirtyBits());
            wroteSyncVar = true;
        }
        writer.WritePackedUInt32((uint)this.int1);
    }
    if ((base.get_syncVarDirtyBits() & 2u) != 0u)
    {
        if (!wroteSyncVar)
        {
            // write dirty bits if this is the first SyncVar written
            writer.WritePackedUInt32(base.get_syncVarDirtyBits());
            wroteSyncVar = true;
        }
        writer.WritePackedUInt32((uint)this.int2);
    }
    if ((base.get_syncVarDirtyBits() & 4u) != 0u)
    {
        if (!wroteSyncVar)
        {
            // write dirty bits if this is the first SyncVar written
            writer.WritePackedUInt32(base.get_syncVarDirtyBits());
            wroteSyncVar = true;
        }
        writer.Write(this.MyString);
    }

    if (!wroteSyncVar)
    {
        // write zero dirty bits if no SyncVars were written
        writer.WritePackedUInt32(0);
    }
    return wroteSyncVar;
}
 
And the OnDeserialize function is something like: 反序列化函数将如下:
 
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();
    }
}
 
If a NetworkBehaviour has a base class that also has serialization functions, the base class functions should also be called. 如果这个NetworkBehaviour的基类也有一个序列化函数,基类的序列化函数也将被调用。
 
Note that the UpdateVar packets created for object state updates may be aggregated in buffers before being sent to the client, so a single transport layer packet may contain updates for multiple objects. 要注意的是,这些更新的数据包可能会在缓冲区中合并,所以一个传输层数据包可能包含多个object的更新数据包。