状态同步
状态同步是从服务器到远程客户端完成的。本地客户端没有它的串行化数据,因为它与服务器共享场景。串行到本地客户端的任何数据都将是冗余的。然而,在本地客户端上调用数据与SyncVar挂钩。
数据未从远程客户端同步到服务器。这是指令的工作。
SyncVars
SyncVars是从服务器同步到客户端的NetworkBehaviour脚本的成员变量。当一个对象被产生或者一个新玩家加入正在进行的游戏时,它们将接收到视域中所有网络物体的SyncVars的最新状态。在调用OnStartClient()之前,SyncVars的状态应用于客户端上的对象,因此该对象的状态保证在OnStartClient()内是最新的。SyncVars可以是基本数据类型,如整数,字符串和浮点数。它们也可以是Unity类型,例如Vector3和用户定义的结构,但是对于SyncVars的更新作为单片更新发送,而不是在结构中的字段更改时的增量更改。在单个NetworkBehaviour脚本中最多可以有32个SyncVars,这包括SyncLists。
当SyncVar的值更改时,服务器会自动发送SycnVar更新。没有必要对SyncVars执行任何手动清理字段。
同步列表
SyncLists类似于SyncVars,但它们是值列表,而不是单个值。SyncList内容包括在具有SyncVar状态的初始状态更新中。SyncLists不需要SyncVar属性,它们是特定的类。对于基本类型,有内置的SyncList类型:
• SyncListString
• SyncListFloat
• SyncListInt
• SyncListUInt
• SyncListBool
还有SyncListStruct,可以用于用户定义的结构列表。使用的结构SyncListStruct派生类可以包含基本类型,数组和常见Unity类型的成员。它们不能包含复杂类或通用容器。
SyncLists具有名为Callback的SyncListChanged委托,允许在列表内容更改时通知客户端。将使用发生的操作类型和操作所针对的项目的索引来调用此代理。
自定义串行化函数
通常,使用SyncVars足以让脚本将其状态串行化到客户端,但在某些情况下需要更复杂的串行化代码。开发人员可以利用NetworkBehaviour上用于SyncVar串行化的虚拟函数来执行自己的自定义串行化。这些功能是:
public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
public virtual void OnDeSerialize(NetworkReader reader, bool initialState);
initialState标志用于区分对象第一次串行化和可以发送增量更新时。第一次将对象发送到客户端时,它必须包括完整状态快照,但后续更新可以通过仅包括增量更改来节省带宽。请注意,当initialState为true时,不调用SyncVar钩子函数,仅用于增量更新。
如果一个类有SyncVars,那么这些函数的实现会自动添加到类中。因此,具有SyncVars的类也不能具有自定义串行化函数。
OnSerialize函数应返回true时指示发送更新。如果它返回true,那么该脚本的脏位被设置为零,如果它返回false,那么脏位不改变。这允许对脚本的多个更改随时间累积,并在系统就绪时发送,而不是每个帧。
串行化流
具有NetworkIdentity组件的游戏对象可以具有源自NetworkBehaviour的多个脚本。串行化这些对象的流程是:
在服务器上:
• 每个NetworkBehaviour有一个脏面具。此掩码在OnSerialize中作为syncVarDirtyBits可用
• NetworkBehaviour脚本中的每个SyncVar在脏掩码中分配一位。
• 更改SyncVars的值会使该SyncVar的位在脏掩码中设置
• 或者,调用SetDirtyBit()直接写入脏掩码
• NetworkIdentity对象在服务器上作为其更新循环的一部分被检查
• 如果NetworkIdentity上的任何NetworkBehavours是脏的,则为该对象创建一个UpdateVars包
• 通过在对象上的每个NetworkBehavail上调用OnSerialize来填充UpdateVars数据包
• 没有脏的NetworkBehavours向它们的脏位写入一个零
• 脏的NetworkBehavours写它们的脏掩码,然后更改SyncVars的值
• 如果OnSerialize对于NetworkBehaviour返回true,则脏掩码将针对该NetworkBehaviour重置,因此它将不再发送,直到它的值更改。
• UpdateVars数据包发送到正在观察对象的就绪客户端
在客户端:
• 接收对象的UpdateVars分组
• 对对象上的每个NetworkBehaviour脚本调用OnDeserialize函数
• 对象上的每个NetworkBehaviour脚本读取脏掩码。
• 如果NetworkBehaviour的脏掩码为零,OnDeserialize函数将不再读取返回
• 如果脏掩码为非零值,则OnDeserialize函数读取与所设置的脏位相对应的SyncVars的值
• 如果有SyncVar钩子函数,那些被从流读取的值调用。
如果NetworkBehaviour有一个基类也有串行化函数,基类函数也应该被调用。
注意,为对象状态更新创建的UpdateVar分组可以在被发送到客户端之前聚合在缓冲器中,因此单个传输层分组可以包含多个对象的更新。
远程操作
网络系统具有在网络上执行动作的方法。这些类型的操作有时称为远程过程调用。在网络系统中有两种类型的远程过程调用:Commands - 从客户端调用并在服务器上运行; 和ClientRpc调用 - 它们在服务器上调用并在客户端上运行。
下图显示了远程操作采取的方向:
Commands
命令从客户端上的Player对象发送到服务器上的Player对象。为了安全起见,命令只能从你的玩家对象发送,所以你不能控制其他玩家的对象。要将某个函数转换为命令,请向其中添加[Command]定制属性,并添加“Cmd”前缀。此函数现在将在服务器上在客户端上调用时运行。任何参数将自动使用命令传递到服务器。
命令函数必须具有前缀“Cmd”。当读取调用命令的代码时,这是一个提示 - 这个函数是特殊的,不象普通函数那样在本地调用。
小心从客户端每帧发送命令!这可能会导致大量的网络流量。
默认情况下,命令在通道0(默认可靠通道)上发送。因此,默认情况下所有命令都可靠地发送到服务器。这可以使用[Command]自定义属性的“Channel”参数进行自定义。此参数应为一个整数,表示通道号。
通道1也默认设置为不可靠通道,因此要使用此参数,请在Command属性中使用值1作为参数,如下所示:
[Command(channel=1)]
从Unity 5.2版开始,可以从具有客户端权限的非玩家对象发送命令。这些对象必须已使用NetworkServer.SpawnWithClientAuthority衍生,或者使用NetworkIdentity.AssignClientAuthority设置权限。从这些对象发送的命令在对象的服务器实例上运行,而不是在客户端的关联Player对象上运行。
ClientRpc调用
ClientRpc调用从服务器上的对象发送到客户端上的对象。它们可以从具有已生成的NetworkIdentity的任何服务器对象发送。由于服务器具有权限,因此服务器对象在发送这些调用时不存在安全问题。要将函数变成ClientRpc调用,请向其中添加[ClientRpc]定制属性,并添加“Rpc”前缀。此函数现在将在客户端上在服务器上调用时运行。任何参数将自动传递给ClientRpc调用的客户端..
ClientRpc函数必须具有前缀“Rpc”。当读取调用方法的代码时,这是一个提示 - 这个函数是特殊的,不象普通函数那样在本地调用。
当使用LocalClient作为主机运行游戏时,ClientRpc调用将在LocalClient上调用 - 即使它与服务器处于相同的进程。因此,LocalClients和RemoteClients的行为对ClientRpc调用是相同的。