在使用PUN2开发多人游戏时,会存在通过RPC和Event等发送自定义类型(例如用户定义的结构或类)的情况。但是,PUN2不允许将这些自定义类型直接发送到网络。相反,PUN2 提供了自定义类型注册方法作为解决方案。
为了使用PUN2的自定义类型注册方法,自定义类型必须序列化为字节数组。在许多情况下,字节序列化是使用二进制格式化程序执行的。然而,Binary Formatter 会产生大量额外字节,这会损害网络密集型游戏的流量,并给无法使用免费 WIFI 连接的移动用户带来较差的体验。另外Binary Formatter无法序列化Unity的Vector2、Vector3、Quaternion等。
该存档提供了通过脚本自定义类型注册方法,并比较了与其他序列化方法的差异。
给定的结构将使用二进制格式化程序进行序列化,并且大小将由 Marshal 进行测量。请注意,不包括 Vector3、四元数等 Unity 对象,因为这些对象无法通过 Binary Formatter 进行序列化。
public struct TestStruct
{
public int int_1 ;
public int int_2 ;
public string string_1 ;
public float float_1 ;
public float float_2 ;
}
TestStruct testStruct = new TestStruct
{
int_1 = 30 ,
int_2 = 71 ,
string_1 = " ABC가나다 " ,
float_1 = 0.162f ,
float_2 = 62f ,
} ;
其结构及测量尺寸系列化如下:
public void BinaryFormatterSerialize ( TestStruct testStruct )
{
byte [ ] bytes ;
MemoryStream memoryStream = new MemoryStream ( ) ;
BinaryFormatter binaryFormatter = new BinaryFormatter ( ) ;
binaryFormatter . Serialize ( memoryStream , testStruct ) ;
memoryStream . Close ( ) ;
bytes = memoryStream . ToArray ( ) ;
Debug . Log ( string . Format ( " Bianary Formatter Serialized Size : {0} bytes " , bytes . Length ) ) ;
}
public void CheckSize ( TestStruct testStruct )
{
Debug . Log ( string . Format ( " Original Size : {0} bytes " , Marshal . SizeOf ( testStruct ) ) ) ;
Debug . Log ( JsonUtility . ToJson ( testStruct , true ) ) ;
}
结果如下:
大小(字节) | |
---|---|
原来的 | 24字节 |
二进制格式化程序 | 199 字节 |
给定结构的理论大小为 24 字节。当使用 Binary Formatter 序列化给定结构时,大小为 199 字节,比理论大小大 8 倍。当序列化并将其发送到网络时,这可能会导致流量开销。
上面给定的结构将使用 JsonUtility 序列化并转换为字节。序列化结构如下:
public void JsonSerialize ( TestStruct testStruct )
{
byte [ ] bytes = Encoding . UTF8 . GetBytes ( JsonUtility . ToJson ( testStruct ) ) ;
Debug . Log ( string . Format ( " JsonUtility Serialized Size : {0} bytes " , bytes . Length ) ) ;
}
结果如下:
大小(字节) | |
---|---|
原来的 | 24字节 |
JsonUtility & 字节转换 | 94字节 |
当给定的结构体用 JsonUtility 序列化并转换为字节时,大小为 94 字节,大约是理论大小的 4 倍。可以通过缩短变量名称来减小此大小。例如,当如下所示更改变量名称时,结果如下。
public struct TestStruct
{
public int a ;
public int b ;
public string c ;
public float d ;
public float e ;
}
TestStruct testStruct = new TestStruct
{
a = 30 ,
b = 71 ,
c = " ABC가나다 " ,
d = 0.162f ,
e = 62f ,
} ;
大小(字节) | |
---|---|
原来的 | 24字节 |
JsonUtility & 字节转换 | 67字节 |
字节大小从 94 字节减少到 67 字节。但是,它仍然大于理论大小 24 字节。
该存档引入了自定义序列化器,它可以序列化自定义类型,例如用户定义的结构或类。该串行器可以提供接近理论大小的大小。可序列化的类型和大小如下:
类型 | 大小(字节) |
---|---|
字节 | 1字节 |
字节[] | 4 + (1 * 长度) 字节 |
布尔值 | 1字节 |
布尔[] | 4 + (1 * 长度) 字节 |
整数 | 4字节 |
整数[] | 4 + (4 * 长度) 字节 |
漂浮 | 4字节 |
漂浮[] | 4 + (4 * 长度) 字节 |
向量2 | 8字节 |
向量2[] | 4 + (8 * 长度) 字节 |
矢量3 | 12字节 |
矢量3[] | 4 + (12 * 长度) 字节 |
四元数 | 16字节 |
四元数[] | 4 + (16 * 长度) 字节 |
细绳 | 4 + α(UTF8 编码)字节 |
细绳[] | 4 + ((4 + α) * 长度) 字节 |
首先,声明使用上面的MSLIMA.Serializer。
using MSLIMA.Serializer;
然后,假设结构如下:
public struct TestStruct
{
public int int_1 ;
public int int_2 ;
public float float_1 ;
public bool bool_1 ;
public string string_1 ;
public Vector3 vector3_1 ;
public Vector3 vector3_2 ;
public Quaternion quaternion_1 ;
}
TestStruct testStruct = new TestStruct
{
int_1 = 30 ,
int_2 = 71 ,
float_1 = 0.162f ,
bool_1 = true ,
string_1 = " ABC가나다 " ,
vector3_1 = new Vector3 ( - 23f , 62f , 26f ) ,
vector3_2 = new Vector3 ( 1f , 7f , - 15f ) ,
quaternion_1 = Quaternion . Euler ( 35f , 0f , 15f )
} ;
首先,在自定义类型内部创建名称为“Serialize”和“Deserialize”的静态方法。 “Serialize”方法有一个参数,类型为object,返回类型为byte[]。 “Deserialize”方法有一个参数,类型为byte[],返回类型为对象。
请注意,方法名称、参数和返回类型必须与描述的相同。此外,这些方法必须是静态的。
public static byte [ ] Serialize ( object customObject )
{
}
public static object Deserialize ( byte [ ] bytes )
{
}
其次,将 customObject 转换为自定义类型,在“Serialize”方法中声明字节数组。
public static byte [ ] Serialize ( object customObject )
{
TestStruct o = ( TestStruct ) customObject ;
byte [ ] bytes = new byte [ 0 ] ;
}
现在,使用 Serializer 的方法序列化所需的字段并最终返回字节。请注意,字节数组是通过 ref 关键字传递的。
public static byte [ ] Serialize ( object customObject )
{
.. .
Serializer . Serialize ( o . int_1 , ref bytes ) ;
Serializer . Serialize ( o . int_2 , ref bytes ) ;
Serializer . Serialize ( o . float_1 , ref bytes ) ;
Serializer . Serialize ( o . bool_1 , ref bytes ) ;
Serializer . Serialize ( o . string_1 , ref bytes ) ;
Serializer . Serialize ( o . vector3_1 , ref bytes ) ;
Serializer . Serialize ( o . vector3_2 , ref bytes ) ;
Serializer . Serialize ( o . quaternion_1 , ref bytes ) ;
return bytes ;
}
第三,在“Deserialize”方法中创建新的自定义类型,并声明 int 类型的偏移量变量,并将其初始化为 0。
public static object Deserialize ( byte [ ] bytes )
{
TestStruct o = new TestStruct ( ) ;
int offset = 0 ;
}
现在,使用 Serializer 的 deserialize 方法来反序列化上面序列化的字段。偏移量通过 ref 关键字传递并返回上面创建的自定义类型。
注意,反序列化的顺序必须与序列化的顺序相同。
public static object Deserialize ( byte [ ] bytes )
{
.. .
o . int_1 = Serializer . DeserializeInt ( bytes , ref offset ) ;
o . int_2 = Serializer . DeserializeInt ( bytes , ref offset ) ;
o . float_1 = Serializer . DeserializeInt ( bytes , ref offset ) ;
o . bool_1 = Serializer . DeserializeBool ( bytes , ref offset ) ;
o . string_1 = Serializer . DeserializeString ( bytes , ref offset ) ;
o . vector3_1 = Serializer . DeserializeVector3 ( bytes , ref offset ) ;
o . vector3_2 = Serializer . DeserializeVector3 ( bytes , ref offset ) ;
o . quaternion_1 = Serializer . DeserializeQuaternion ( bytes , ref offset ) ;
return o ;
}
最后,自定义类型应注册到 PUN2。调用一次下面描述的方法来注册自定义类型。如果需要注册多个自定义类型,则字节码必须不同。只需更改方法中参数的字母即可实现。
Serializer . RegisterCustomType < TestStruct > ( ( byte ) 'A' ) ;
给定结构序列化的结果如下:
大小(字节) | |
---|---|
原来的 | 64字节 |
自定义序列化器 | 69字节 |
理论大小为 64 字节,实际序列化大小为 69 字节。 5 个字节的差异是由字符串引起的,字符串的大小可以根据长度而变化。结果是可以接受的。
自定义序列化程序提供的大小比 Binary Formatter 或 JsonUtility 序列化更小。但是,存在一些限制,即为每个应该序列化且不支持嵌套类型的自定义类型编写所有序列化方法可能会很不方便。尽管如此,如果用原始类型序列化简单的自定义类型并经常将其发送到网络,则此自定义序列化程序会有所帮助。
添加此注册表的范围。
"scopedRegistries" : [
{
"name" : " MS-LIMA " ,
"url" : " https://package.openupm.com " ,
"scopes" : [
" com.ms-lima "
]
}
]