携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
同步 Synchronization
同步是网络游戏的核心,一同玩游戏的玩家可以看到彼此以及游戏世界中的共享对象,都是通过同步机制。
同步的方向
Mirror中,同步是从服务端向客户端进行,即将服务器上的对象状态复制给客户端上的对象拷贝。从技术上说,只有远程客户端需要同步。本地客户端和服务器是共享场景的,所以并不需要同步,不过SyncVar hooks仍然会在本地客户端上调用。数据不会从客户端向服务器同步,但是客户端可以使用commands主动要求服务器改变状态。
同步变量 SyncVars
SyncVars是继承NetworkBehaviour的类拥有的属性,Mirror会自动从服务器向客户端同步他们。当一个新的游戏对象被生成,或者一个新的player加入游戏时,他们可见的所有网络对象上的所有SyncVars的最新状态会被发送给他们。使用[SyncVar]这个自定义属性去指定脚本中的哪些变量需要同步。SyncVars可以使用在Mirror支持的所有数据类型中,在单个NetworkBehaviour脚本中,最多可以使用64个SyncVars,包含SyncLists中的。SyncVars的同步是自动的,当SyncVars的值变化时,服务器自动发送SyncVar。注意,在Inspector中改变SyncVar的值不会触发更新。[SyncVar]属性可以使用hook参数来设置一个回调方法,当客户端上的SyncVar值改变时被调用。
OnStartClient
当OnStartClient被调用时,所有SyncVars的状态已经同步给客户端上的游戏对象。所以在OnStartClient回调内部,对象的状态已经是最新的了。
[SyncVar]使用示例
public class Enemy : NetworkBehaviour
{
[SyncVar]
public int hp = 100;
}
public class PlayerController : NetworkBehaviour
{
void Update()
{
if(isLocalPlayer)
{
if(Input.GetKeyDown(KeyCode.J))
{
GameObject target = GetTarget();
CmdShoot(target);
}
}
}
[Command]
public void CmdShoot(GameObject enemy)
{
enemy.GetComponent<Enemy>().hp -= 5;
}
}
在这个例子中,local player按下了开火键,通过某个方法获得当前的攻击目标,然后调用CmdShoot,这是一个Command方法,是在服务器上调用的,因此是服务器上这个敌人的hp被扣减。由于hp这个变量通过[SyncVar]属性修饰,因此这个敌人的hp会自动同步到所有客户端。
子类中使用[SyncVar]
当继承自NetworkBehaviour的脚本又有子类时,在子类中仍然可以使用[SyncVar]属性。比如下面的这个例子:
class Pet: NetworkBehaviour
{
[SyncVar]
string name;
}
class Cat: Weapon
{
[SyncVar]
public Color32 color;
}
将Cat脚本添加到prefab之后,会自动同步name和color。但需要注意的是,Cat和Pet必须在同一个程序集中。如果他们在不同的程序集中,Cat中不能直接改变name,需要在Pet中增加一个方法去修改name,然后在Cat中调用。
SyncVar Hooks
当使用[SyncVar]同步变量时,可以指定一个hook参数,设置一个回调函数,当客户端上的变量值改变时调用。
- Hook方法必须具有两个和被同步的变量相同类型的参数,一个是old value,另一个是new value。
- Hook方法是属性更新时自动调用的。
- 只有值被同步后才会调用Hook,在Inspector中改变值不会调用Hook
- Hook方法可以是一个虚方法,可以在子类中override
Hook示例
public class PlayerController : NetworkBehaviour
{
[SyncVar(hook = nameof(SetPlayerColor))]
Color playerColor = Color.black;
Material cachedMaterial;
public override void OnStartServer()
{
base.OnStartServer();
playerColor = Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f);
}
void SetPlayerColor(Color oldColor, Color newColor)
{
if(cachedMaterial == null)
{
cachedMaterial = GetComponent().material;
}
cachedMaterial.color = newColor;
}
void OnDestroy()
{
Destroy(cachedMaterial);
}
}
这个例子中,当玩家连入时(OnStartServer),会被赋予一个随机颜色,PlayerColor变量通过[SyncVar]同步给所有玩家,使用hook = nameof(SetPlayerColor)指定一个钩子函数,但PlayerColor在客户端上获取同步后的值时调用,这个方法内部会更新材质颜色。
Hook调用顺序
如果多个SyncVar被同时在服务器上设置,他们的hook调用的顺序是更加这几个SyncVar变量定义的顺序。