Unity Mirror联网游戏开发(7) 同步篇二

888 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

Mirror的同步容器

上篇中,我们使用[SyncVar]这个自定义属性,为单个变量添加同步功能。但有时候,我们会在脚本中使用容器来容纳一组对象,容器中的对象我们也需要同步功能,为此Mirror提供了同步容器,包括SyncList , SyncDictionary, SyncHashSetSyncSortedSet

SyncList

  • SyncList是一个泛型容器,其内部的数据会自动同步。
  • SyncList是类似于C# List那样的基于数组的列表。
  • SyncList可以包含任意Mirror支持的类型。关于Mirror支持的类型,会单独总结一篇。
  • SyncList必须是readonly的,这意味着该变量在构造函数之后不能被修改,因此SyncList只能在定义时赋值或者在所属对象的构造函数中赋值。
  • SyncList是和其他SyncVars一起序列化的,SyncList中的变量和其他SyncVars变量被合并到一条消息中。

使用示例

public struct Item
{
    public string name;
    public int amount;
}

public class Player : NetworkBehaviour
{
    public readonly SyncList<Item> inventory = new SyncList<Item>();
    public int coins = 100;
    
    [Command]
    public void CmdPurchase(string itemName)
    {
        if(coins > 10)
        {
            coins -= 10;
            Item item = new Item
            {
                name = "Sword",
                amout = 3;
            };
            
            inventory.Add(item);
        }
    }
}

这个例子中,当player执行CmdPurchase时,服务器上,该player获得了一个Item,添加到 inventory这个SyncList中,在下次同步时,所有客户端将会知道这件事。

SyncList回调

使用 SyncList.Callback这个event注册SyncList改变时的回调函数,通常在Start,OnClientStartOnServerStart中进行注册。如果注册时,SyncList中已经有数据,你不会得到初始数据添加的回调,只有这之后再有数据更新时才会回调。因此如果想得到已有数据的更新回调,需要手动调用回调函数。

SyncList回调的示例

接着上面的Player的例子,有一个SyncList为inventory,给它注册一个回调。

class Player : NetworkBehaviour
{
    public override void OnStartClient()
    {
        //Callback是个event,因此使用 += 注册回调函数
        inventory.Callback += OnInventoryUpdated;
        
        //处理SyncList中的已有数据,手动调用回调
        for(int i=0; i < inventory.Count; i++)
        {
            OnInventoryUpdated(SyncList<Item>.Operation.OP_ADD, i, new Item(), inventory[index]);
        }        
    }
    
    void OnInventoryUpdated(SyncList<Item>.Operation op, int index, Item oldItem, Item newItem)
    {
        switch(op)
        {
            case SyncList<Item>.Opeartion.OP_ADD:
                //Add操作,index是add的索引位置,newItem是add的Item
                break;
            case SyncList<Item>.Operation.OP_INSERT:
                //Insert操作,index是插入的位置,newItem是插入的Item
                break;
            case SyncList<Item>.Operation.OP_REMOVEAT:
                //删除操作,index是删除的位置,oldItem是被删除的Item
                break;
            case SyncList<Item>.Operation.OP_SET:
                //设置操作,index是修改的Item的索引,oldItem是修改前的Item值,newItem是修改后的新Item值
                break;
            case SyncList<Item>.Operation.OP_CLEAR:
                //清除操作,list被清空
                break;
        }
    }
}

SyncDictionary

同步字典容器,当服务器上该容器内容改变时,改动会同步到各个客户端,回调会调用。只有增量被传输。 基本用法和SyncList类似,一个例子:

public struct Item
{
    public string name;
    public int hitPoints;
}

public class Player : NetworkBehaviour
{
    public readonly SyncDictionary<string, Item> Equipment = new SyncDictionary<string, Item>();
    public override void OnStartServer()
    {
        Equipment.Add("head", new Item { name = "Helment", hitPoints = 20});
        Equipment.Add("body", new Item { name = "Armor", hitPoints = 50});        
    }
    
    public override void OnStartClient()
    {
        Equipment.Callback += OnEquipmentChange;
        
        //处理初始数据的回调
        foreach(KeyValuePair<string, Item> kvp in Equipment)
        {
            OnEquipmentChange(SyncDictionary<string, Item>.Operation.OP_ADD, kvp.Key, kvp.Value);
        }
    }
    
    void OnEquipmentChange(SyncDictionary<string, Item>.Operation op, string key, Item item)
    {
        switch(op)
        {
            case SyncIDictionary<string, Item>.Operation.OP_ADD:
                //添加Item                
                break;
            case SyncIDictionary<string, Item>.Operation.OP_SET:            
                //修改Item
                break;
            case SyncIDictionary<string, Item>.Operation.OP_REMOVE:
                //删除Item                
                break;
            case SyncIDictionary<string, Item>.Operation.OP_CLEAR:
                //清除字典
                break;
        }
    }
    
}

SyncDictionary内部数据结构

SyncDictionary默认使用Dictionary存储数据,如果需要使用其他的IDictionary实现,比如SortedListSortedDictionary,需要使用SyncIDictionary,然后将字典容器的实例传入。例如:

public class Player : NetworkBehaviour
{
    public readonly SyncIDictionary Equipment = new SyncIDictionary(new SortedList());
}

SyncHashSet

支持同步的哈希集合,定义方法示例:

public readonly SyncHashSet<string> buffs = new SyncHashSet<string>();

增加回调:

public override void OnStartClient()
{
    //在OnStartClient内部注册,将添加客户端的委托
    //如果想在服务器上调用委托,需要在 OnStartServer中注册
    buffs.Callback += OnBuffsChanged;
    
    //同样要处理已有数据
    foreach(string buff in buffs)
    {
        OnBuffsChanged(SyncSet<string>.Operation.OP_ADD, buff);
    }    
}

void OnBuffsChanged(SyncSet<string>.Operation op, string buff)
{
    //op包括 OP_ADD, OP_REMOVE, OP_CLEAR
}

SyncSortedSet

SyncSortedSet是类似于C# SortedSet的集合,具有自动同步功能。和SyncHashSets不同,SyncSortedSet在插入元素时会进行排序。 定义方法示例:

public readonly SyncSortedSet<string> buffs = new SyncSortedSet<string>();

使用回调:

public overrid void OnStartClient()
{
    buffs.Callback += OnBuffsChanged;
    foreach(string buff in buffs)
        OnBuffsChanged(SyncSortedSet<string>.Operation.OP_ADD, buff);
}

void OnBuffsChanged(SyncSortedSet<string>.Operation op, string buff)
{
    //op包括 OP_ADD, OP_REMOVE, OP_CLEAR
}