当前位置:网站首页>MLAPI系列 - 04 - 网络变量和网络序列化【网络同步】
MLAPI系列 - 04 - 网络变量和网络序列化【网络同步】
2022-07-06 04:06:00 【飞浪纪元[FWC–FE]】
MLAPI系列 - 04 - 网络变量和网络序列化【网络同步】
一、网络同步概述
Netcode的网络同步手段主要有两种:第一是RPC机制,远程调用,第二是使用网络变量。
二、 网络变量
网络变量属于Netcode的特有网络类型,封装维护一个Value,如果要封装多个字段或者数组需要自行进行封装。
三、 关于框架内置序列化
1 网络变量被定义为泛型类
NetworkVariable
,支持C#基本类型、Unity基本类型、自定义枚举。2 RPC传递消息参数,需要使用可序列化类型,支持以下可序列化类型以及继承序列化接口
INetworkSerializable
的自定义序列化类型。
1 C#基础类型
C#基础类型将由内置的序列化代码进行序列化。
这些类型包括:bool, char, sbyte, byte, short, ushort, int, uint, long, ulong, float, double, and string.
[ServerRpc]
void FooServerRpc(int somenumber, string sometext) {
/* ... */ }
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
// Client -> Server
FooServerRpc(Time.frameCount, "hello, world");
}
}
2 Unity基础类型
Unity基础类型 Color, Color32, Vector2, Vector3, Vector4, Quaternion, Ray, Ray2D类型将由内置序列化代码序列化。
[ClientRpc]
void BarClientRpc(Color somecolor) {
/* ... */ }
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
BarClientRpc(Color.red); // Server -> Client
}
}
3 枚举类型
用户定义的枚举类型将由内置序列化代码序列化(使用基础整数类型)
enum SmallEnum : byte // 0-255 限定长度
{
A,
B,
C
}
enum NormalEnum // default -> int
{
X,
Y,
Z
}
[ServerRpc]
void ConfigServerRpc(SmallEnum smallEnum, NormalEnum normalEnum)
{
/* ... */ }
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
ConfigServerRpc(SmallEnum.A, NormalEnum.X); // Client -> Server
}
}
4 数组
像 int[] 这样的数组 由内置的序列化代码序列化:
如果它们的基础类型是支持序列化的类型之一(如Vector3) 或者 它们是否实现了INetworkSerializable接口。
[ServerRpc]
void HelloServerRpc(int[] scores, Color[] colors) {
/* ... */ }
[ClientRpc]
void WorldClientRpc(MyComplexType[] values) {
/* ... */ }
5 INetworkSerializable接口
INetworkSerializable
接口可用于定义自定义的可序列化类型。
struct MyComplexStruct : INetworkSerializable
{
public Vector3 Position;
public Quaternion Rotation;
// INetworkSerializable
void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Position);
serializer.SerializeValue(ref Rotation);
}
// ~INetworkSerializable
}
//实现类型INetworkSerializable,并被NetworkSerializer, RPCs和NetworkVariable支持
[ServerRpc]
void MyServerRpc(MyComplexStruct myStruct) {
/* ... */ }
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
MyServerRpc(
new MyComplexStruct
{
Position = transform.position,
Rotation = transform.rotation
}); // Client -> Server
}
}
5.1 嵌套序列类型
嵌套序列类型将被null除非您按照以下方法之一初始化:
- 调用前手动SerializeValue如果serializer.IsReader(或者类似的东西)
- 在默认构造函数中初始化
这是故意的。在正确初始化之前,您可能会看到这些值为null。序列化程序没有反序列化它们,因此null值在序列化之前被简单地应用。
5.2 条件序列化
因为对结构的序列化有更多的控制,所以可以在运行时实现条件序列化。
示例:数组
public struct MyCustomStruct : INetworkSerializable
{
public int[] Array;
void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
// Length
int length = 0;
if (!serializer.IsReader)
{
length = Array.Length;
}
serializer.SerializeValue(ref length);
// Array
if (serializer.IsReader)
{
Array = new int[length];
}
for (int n = 0; n < length; ++n)
{
serializer.SerializeValue(ref Array[n]);
}
}
}
读取:
序列化(反序列化)length从流回来
迭代Array成员n=length倍 将值序列化(反序列化)回数组[n]流中的元素写入:
序列化长度=数组 写入流的长度
迭代数组成员n =长度倍 序列化数组中的值[n]元素添加到流中
这BufferSerializer.IsReader标志在这里被用来确定是否设置length值,然后我们使用它来确定是否创建一个新的int[]实例与length要设置的大小Array在从流中读取值之前。
还有一个等价但相反的BufferSerializer.IsWriting
示例:移动
public struct MyMoveStruct : INetworkSerializable
{
public Vector3 Position;
public Quaternion Rotation;
public bool SyncVelocity;
public Vector3 LinearVelocity;
public Vector3 AngularVelocity;
void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
// Position & Rotation
serializer.SerializeValue(ref Position);
serializer.SerializeValue(ref Rotation);
// LinearVelocity & AngularVelocity
serializer.SerializeValue(ref SyncVelocity);
if (SyncVelocity)
{
serializer.SerializeValue(ref LinearVelocity);
serializer.SerializeValue(ref AngularVelocity);
}
}
}
读取:
序列化(反序列化)Position从流回来
序列化(反序列化)Rotation从流回来
序列化(反序列化)SyncVelocity从流回来
检查是否SyncVelocity设置为true,如果是这样:
序列化(反序列化)LinearVelocity从流回来
序列化(反序列化)AngularVelocity从流回来
写入:
加载Position到流里
加载Rotation到流里
加载SyncVelocity到流里
检查是否SyncVelocity设置为true,如果是这样:
加载LinearVelocity到流里
加载AngularVelocity到流里
不同于排列上例中,我们没有使用BufferSerializer.IsReader标志来更改序列化逻辑,但要更改序列化标志本身的值。如果SyncVelocity标志设置为真,则LinearVelocity和AngularVelocity将被序列化到流中
当SyncVelocity标志设置为false,我们会离开LinearVelocity和AngularVelocity使用默认值。
5.3 递归嵌套序列化
可以用递归序列化嵌套成员INetworkSerializable层次树中的接口。
示例:
public struct MyStructA : INetworkSerializable
{
public Vector3 Position;
public Quaternion Rotation;
void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Position);
serializer.SerializeValue(ref Rotation);
}
}
public struct MyStructB : INetworkSerializable
{
public int SomeNumber;
public string SomeText;
public MyStructA StructA;
void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref SomeNumber);
serializer.SerializeValue(ref SomeText);
StructA.NetworkSerialize(serializer);
}
}
如果我们要单独加载MyStructA,它会使用NetworkSerializer加载Position和Rotation入流,.
然而,如果我们要序列化MyStructB,它会序列化SomeNumber和SomeText到流中,然后序列化StructA通过Call -
MyStructA void
NetworkSerialize(NetworkSerializer)方法,该方法序列化Position和Rotation入同一流。
注意
从技术上讲,数量没有硬性限制INetworkSerializable可以沿着树层次结构序列化的字段。在实践中,考虑内存和带宽边界以获得最佳性能。
您可以在递归嵌套序列化场景中有条件地序列化,并利用这两种功能。
6 自定义序列化
使用时RPCs,NetworkVariable或任何其他需要序列化的游戏对象网络代码(Netcode)相关任务。Netcode使用默认的序列化管道,如下所示:
Custom Types => Built In Types => INetworkSerializable
也就是说,当Netcode第一次获得一个类型时,它将检查用户已经为序列化注册的任何自定义类型,之后它将检查它是否是一个内置类型,如Vector3、float等。这些是默认处理的。如果不是,它将检查该类型是否继承INetworkSerializable如果有,它将调用它的write方法。
默认情况下,任何满足unmanaged通用约束可以自动序列化为RPC参数。这包括所有基本类型(bool、byte、int、float、enum等)以及只包含这些基本类型的任何结构。
通过这个流程,您可以覆盖全部的序列化全部类型,甚至内置在类型中,并且使用提供的API,它甚至可以用您自己没有定义的类型来完成,那些在第三方墙后面的类型,例如。网络类型。
若要注册自定义类型或重写已处理的类型,需要为FastBufferReader.ReadValueSafe()和FastBufferWriter.WriteValueSafe():
//告诉Netcode将来如何序列化和反序列化Url。
//类名在这里无关紧要。
public static class SerializationExtensions
{
public static void ReadValueSafe(this FastBufferReader reader, out Url value)
{
reader.ReadValueSafe(out string val);
value = new Url(val);
}
public static void WriteValueSafe(this FastBufferWriter writer, in Url value)
{
writer.WriteValueSafe(instance.Value);
}
}
RPC
的代码生成将直接通过FastBufferWriter
和FastBufferReader
自动获取并使用这些函数。
您还可以选择使用相同的方法来添加对BufferSerializer<TReaderWriter>.SerializeValue()
,如果你愿意,这将使这种类型在INetworkSerializable
类型:
//类名在这里无关紧要。
public static class SerializationExtensions
{
public static void SerializeValue<TReaderWriter>(this BufferSerializer<TReaderWriter> serializer, ref Url value) where TReaderWriter: IReaderWriter
{
if (serializer.IsReader)
{
value = new Url();
}
serializer.SerializeValue(ref value.Value);
}
}
7 网络对象和网络行为
GameObjects
, NetworkObjects
和 NetworkBehaviour
不是可序列化的类型,因此不能在 RPC
或者 NetworkVariables
默认情况下。
有两个方便的wrappers
【包装器】可用于发送对NetworkObject
或者一个NetworkBehaviour
通过RPC
或NetworkVariables
.
7.1 NetworkObjectReference 网络对象引用类型
NetworkObjectReference
可用于序列化对NetworkObject
。它只能在已经生成的上使用NetworkObjects
.
下面是一个使用NetworkObject
发送目标的参考NetworkObject
通过RPC
:
public class Weapon : NetworkBehaviour
{
public void ShootTarget(GameObject target)
{
var targetObject = target.GetComponent<NetworkObject>();
ShootTargetServerRpc(targetObject);
}
[ServerRpc]
public void ShootTargetServerRpc(NetworkObjectReference target)
{
if (target.TryGet(out NetworkObject targetObject))
{
// deal damage or something to target object.
}
else
{
// Target not found on server, likely because it already has been destroyed/despawned.
}
}
}
7.2 Implicit Operators 隐式运算
还有从/到转换的隐式运算符NetworkObject
/GameObject
这可以用来简化代码。例如,上述示例也可以用以下方式编写:
NetworkObjectReference -> NetworkObject
GameObject -> NetworkObjectReference
public class Weapon : NetworkBehaviour
{
public void ShootTarget(GameObject target)
{
ShootTargetServerRpc(target);
}
[ServerRpc]
public void ShootTargetServerRpc(NetworkObjectReference target)
{
NetworkObject targetObject = target;
}
}
注意: 如果找不到引用,到
NetworkObject
/GameObject
的隐式转换将导致Null。
7.3 NetworkBehaviourReference 网络行为参考
NetworkBehaviourReference
工作方式类似于NetworkObjectReference
而是用来指代特定的NetworkBehaviour
衍生的上的组件NetworkObject
.
public class Health : NetworkBehaviour
{
public NetworkVariable<int> Health = new NetworkVariable<int>();
}
public class Weapon : NetworkBehaviour
{
public void ShootTarget(GameObject target)
{
var health = target.GetComponent<Health>();
ShootTargetServerRpc(health, 10);
}
[ServerRpc]
public void ShootTargetServerRpc(NetworkBehaviourReference health, int damage)
{
if (health.TryGet(out Health healthComponent))
{
healthComponent.Health.Value -= damage;
}
}
}
7.4 网络对象引用、网络行为引用的工作原理
NetworkObjectReference
和 NetworkBehaviourReference
是方便的wrappers
包装器,它序列化NetworkObject当发送时,在接收端检索相应的 用那个id。NetworkBehaviourReference
发送一个附加索引,用于查找正确的NetworkBehaviour
在哪个NetworkObject
上.
它们都实现INetworkSerializable
接口。
四、 网络变量使用
网络变量泛型类 NetworkVariable
,支持C#基本类型和Unity基本类型。
1 bool 类型的网络变量使用
当NetworkVariable<T>
的 Value
更改, OnValueChanged
将带有新旧两个值的参数进行回调,回调函数应对值变化而不断轮询最新的值。
public class Door : NetworkBehaviour
{
public NetworkVariable<bool> State = new NetworkVariable<bool>();
public override void OnNetworkSpawn()
{
State.OnValueChanged += OnStateChanged;
}
public override void OnNetworkDespawn()
{
State.OnValueChanged -= OnStateChanged;
}
public void OnStateChanged(bool previous, bool current)
{
// note: `State.Value` will be equal to `current` here
if (State.Value)
{
// door is open:
// - rotate door transform
// - play animations, sound etc.
}
else
{
// door is closed:
// - rotate door transform
// - play animations, sound etc.
}
}
[ServerRpc(RequireOwnership = false)]
public void ToggleServerRpc()
{
// this will cause a replication over the network
// and ultimately invoke `OnValueChanged` on receivers
State.Value = !State.Value;
}
}
2 网络变量读写权限
默认情况下,NetworkVariable<T>
只能由服务器写并且可以被任何人读取。这些权限可以改变通过构造函数更改。
// A snippet from the Netcode SDK
public abstract class NetworkVariableBase
{
// ...
public const NetworkVariableReadPermission DefaultReadPerm =
NetworkVariableReadPermission.Everyone;
public const NetworkVariableWritePermission DefaultWritePerm =
NetworkVariableWritePermission.Server;
// ...
}
public class NetworkVariable<T> : NetworkVariableBase
{
// ...
public NetworkVariable(T value = default,
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
{
// ...
}
// ...
}
2.1 Read
public enum NetworkVariableReadPermission
{
Everyone,
Owner
}
Everyone → All clients and server will get value updates.
Owner → Only server and the owner client will get value updates.
2.2 Write
public enum NetworkVariableWritePermission
{
Server,
Owner
}
Server → Only the server can write to the value.
Owner → Only the owner client can write to the value, server can’t write to the value.
3. 示例代码整合
public class Cube : NetworkBehaviour
{
// everyone can read, only owner can write
public NetworkVariable<Vector3> NetPosition = new NetworkVariable<Vector3>(
default,
NetworkVariableBase.DefaultReadPerm, // Everyone
NetworkVariableWritePermission.Owner);
private void FixedUpdate()
{
// owner writes, others read & apply
if (IsOwner)
{
NetPosition.Value = transform.position;
}
else
{
transform.position = NetPosition.Value;
}
}
// everyone can read, only server can write
public NetworkVariable<Color> NetColor = new NetworkVariable<Color>(
default,
NetworkVariableBase.DefaultReadPerm, // Everyone
NetworkVariableWritePermission.Server);
public override void OnNetworkSpawn()
{
NetColor.OnValueChanged += OnColorChanged;
}
public override void OnNetworkDespawn()
{
NetColor.OnValueChanged -= OnColorChanged;
}
public void OnColorChanged(Color previous, Color current)
{
// update materials etc.
}
[ServerRpc(RequireOwnership = false)]
public void ChangeColorServerRpc()
{
NetColor.Value = Random.ColorHSV();
}
}
五、 MLAPI pre-10版本的一些变化
0.0 升级说明
最近Netcode升级了pre-10版本,官方修复了一些bug,将博主提供的自pre-6版本的自定义泛型网络变量编译导致编辑器崩溃的bug修复了,另外还有一些其他变化。
1 修复数组泛型崩溃
Fixed NetworkAnimator issue where it was not always disposing the NativeArray that is allocated when spawned. (#1946)
2 ClientNetworkTransform 基类函数名 OnIsServerAuthoritatitive 发生变化
修改前:
//覆盖此值并返回false以跟随所有者的权限,否则,默认为服务器权限
protected override bool OnIsServerAuthoritatitive()
{
return false;
}
修改后:
//覆盖此值并返回false以跟随所有者的权限,否则,默认为服务器权限
protected override bool OnIsServerAuthoritative()
{
return false;
}
3 基类变化:NetworkVariableSerialization修改为NetworkVariableBase
升级后,部分继承NetworkVariableSerialization
的自定义网络变量脚本可能会出现报错。
报错原因是使用了过度阶段使用的基类NetworkVariableSerialization
可能准备弃用。
3.1 报错原因
1 版本升级前,属于过度脚本,与网络变量一样,继承了网络变量基类
NetworkVariableBase
,重写ReadDelta
等基类方法可以使用封装好的Read
和Write
静态方法
1.1 使用起来比较方便
----------------------------------------------------------------------------------------【pre-9】
2 版本升级后,移除了基类、Read
和Write
静态方法等
2.1 自定义网络变量转回到使用网络变量基类NetworkVariableBase
即可
----------------------------------------------------------------------------------------【pre-10】
3.2 脚本修改示例
改动前:
Read(reader, out Value.Array[i]);
改动后:
reader.ReadValueSafe(out Value.Array[i]);
边栏推荐
- IDEA编译JSP页面生成的class文件路径
- [001] [stm32] how to download STM32 original factory data
- Record an excel xxE vulnerability
- [Zhao Yuqiang] deploy kubernetes cluster with binary package
- Oracle ORA error message
- MySql数据库root账户无法远程登陆解决办法
- Leetcode32 longest valid bracket (dynamic programming difficult problem)
- 10个 Istio 流量管理 最常用的例子,你知道几个?
- [matlab] - draw a five-star red flag
- pd. to_ numeric
猜你喜欢
Interface idempotency
MySql数据库root账户无法远程登陆解决办法
Database, relational database and NoSQL non relational database
Alibaba testers use UI automated testing to achieve element positioning
Record an excel xxE vulnerability
Ipv4中的A 、B、C类网络及子网掩码
WPF效果第一百九十一篇之框选ListBox
ESP32(基于Arduino)连接EMQX的Mqtt服务器上传信息与命令控制
记一次excel XXE漏洞
cookie,session,Token 这些你都知道吗?
随机推荐
Facebook等大廠超十億用戶數據遭泄露,早該關注DID了
KS008基于SSM的新闻发布系统
Error 1045 (28000): access denied for user 'root' @ 'localhost' (using password: no/yes
Database, relational database and NoSQL non relational database
【FPGA教程案例12】基于vivado核的复数乘法器设计与实现
Basic knowledge of binary tree, BFC, DFS
P2648 make money
C (XXIX) C listbox CheckedListBox Imagelist
[FPGA tutorial case 12] design and implementation of complex multiplier based on vivado core
MySql數據庫root賬戶無法遠程登陸解决辦法
Blue Bridge Cup - Castle formula
Thread sleep, thread sleep application scenarios
Esp32 (based on Arduino) connects the mqtt server of emqx to upload information and command control
Exchange bottles (graph theory + thinking)
Le compte racine de la base de données MySQL ne peut pas se connecter à distance à la solution
asp. Core is compatible with both JWT authentication and cookies authentication
[practice] mathematics in lottery
JVM的手术刀式剖析——一文带你窥探JVM的秘密
【可调延时网络】基于FPGA的可调延时网络系统verilog开发
Cf603e pastoral oddities [CDQ divide and conquer, revocable and search set]