Unity Mirror联网游戏开发(6) 同步篇一

1,506 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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变量定义的顺序。