携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情
对自定义类型添加网络数据读写支持
如果自定义的数据类型Mirror不支持网络发送同步,那么可以通过给NetworkWriter和NetworkReader添加扩展方法来支持该类型的网络数据读写。例如,我们有一个游戏对象类型:
public class Weapon
{
int WeaponId;
GameObject prefab;
WeaponConfig config;
Texture2D icon;
public Weapon(int id)
{
WeaponId = id;
var weaponData = DataTables.GetWeaponTable().Get(id);
prefab = PrefabMgr.Instance.GetPrefab(weaponData.prafabPath);
config = weaponData.config;
icon = IconMgr.Instance.Get(config.iconId);
}
}
这个对象包含了一些Mirror不支持的数据类型,比如Texture2D。那么想在网络上传递他就要将必要的数据进行读写。对于这个类型,他有一个WeaponId属性,通过这个id可以从游戏的数据表里面查询到Weapon的相关数据,然后通过数据构造出Weapon。因此只需要在网络上传递这个id就可以:
public static class WeaponReaderWriter
{
public static void WriteWeapon(this NetworkWriter writer, Weapon weapon)
{
write.WriteInt64(weapon.WeaponId);
}
public static Weapon ReadWeapon(this NetworkReader reader)
{
return new Weapon(reader.ReadInt64());
}
}
添加以上扩展方法之后,Weapon类型就可以在远程调用和网络同步中使用,即可以在[command]和SyncVar等处使用。
这个例子的情况,是自定义类型中出现了Mirror不支持的数据类型,因此不能直接被Mirrro序列化。但如果自定义类型中的所有成员的数据类型都是Mirror支持的,但这些成员其实没必要全部通过网络传输,例如这儿的WeaponConfig,这个是一个配置数据,是游戏载入时从配置文件载入的,对于所有客户端都是一样的数据,传输这个数据其实是浪费带宽和CPU。因此这种情况,也可以对自定义类型使用自定义的网络读写。
Scriptable Object
SO往往会带有一些不能序列化的数据,且SO本身就是一种资源,对于所有客户端都一样,因此在网络上传输他们的内容也没必要。所以对于SO,也需要自定义网络读写,一般可以只传递他们的文件名,对面收到后将其载入。
网络数据对象 VS 继承和多态
看这个例子:
class Item
{
public string name;
}
class Weapon: Item
{
public int attackPoint;
}
class Armor : Item
{
public int defencePoint;
}
这是一个继承体系,比如对于Player有一个CmdEquip(Item item)方法,如果这是一个普通的C#方法,那么item可以传入一个Weapon对象或者Armor对象,使用is判断具体的对象类型,例如:
void CmdEquip(Item item)
{
if(item is Weapon weapon)
{
//This is a weapon, equip it in the hand
}
else if(item is Armor armor)
{
//This is a armor, equip it in the body
}
}
但如果CmdEquip是一个[command],Item就真的只是一个Item,如果不做任何处理,通过网络发送的就只有Item基类的数据name,并且CmdEquip中服务器获取到的对象也只是一个Item基类对象。因为Mirror在序列化时,看到Item就直接对Item序列化,并不会考虑这对象其实可能是Item的子类。并且Mirror自动生成的序列化代码也没法考虑所有可能的子类,只能就事论事,看到啥序列化啥。
另外,如果在Cmd中直接传递子类可以吗?比如这样:
[command]
void CmdEquipArmor(Armor armor)
{
//注意:这样也不行,原因下面说明
}
很可惜,即便在客户端调用CmdEquipArmor时传递的是一个真实的Armro对象,服务器获得的Armro也是不对的,他缺少来自于Item的name属性。为什么呢?因为Mirror的自动序列化只能处理他看到的类型,并不会再去查找所有可能的基类,并逐个序列化基类的成员。 那么,我们应该怎么做呢?很显然,使用自定义网络读写就可以解决,比如这样:
public static class ItemSerializer
{
const byte WEAPON = 1;
const byte ARMOR = 2;
public static void WriteItem(this NetworkWriter writer, Item item)
{
if(item is Weapon weapon)
{
writer.WriteByte(WEAPON);
writer.WriteString(weapon.name);
writer.WritePackedInt32(weapon.attackPoint);
}
else if(item is Armor armor)
{
writer.WriteByte(ARMOR);
writer.WriteString(armor.name);
writer.WritePackedInt32(armor.defencePoint);
}
}
public static void ReadItem(this NetworkReader reader)
{
byte type = reader.ReadByte();
switch(type)
{
case WEAPON:
return new Weapon
{
name = reader.ReadString();
attackPoint = reader.ReadPackedInt32();
};
break;
case ARMOR:
return new Armor
{
name = reader.ReadString();
defencePoint = reader.ReadPackedInt32();
};
break;
default:
throw new Exception($"Invalid weapon type {type}");
}
}
}