当前位置:网站首页>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]);
边栏推荐
- Record an excel xxE vulnerability
- 潘多拉 IOT 开发板学习(HAL 库)—— 实验9 PWM输出实验(学习笔记)
- C#(二十七)之C#窗体应用
- Yyds dry goods inventory web components series (VII) -- life cycle of custom components
- Plus d'un milliard d'utilisateurs de grandes entreprises comme Facebook ont été compromis, il est temps de se concentrer sur le did
- C mouse event and keyboard event of C (XXVIII)
- Ethernet port &arm & MOS &push-pull open drain &up and down &high and low sides &time domain and frequency domain Fourier
- 【可调延时网络】基于FPGA的可调延时网络系统verilog开发
- WPF效果第一百九十一篇之框选ListBox
- Web components series (VII) -- life cycle of custom components
猜你喜欢
Blue Bridge Cup - Castle formula
ESP32(基于Arduino)连接EMQX的Mqtt服务器上传信息与命令控制
Custom event of C (31)
Basic knowledge of binary tree, BFC, DFS
DM8 backup set deletion
Path of class file generated by idea compiling JSP page
[introduction to Django] 11 web page associated MySQL single field table (add, modify, delete)
How many of the 10 most common examples of istio traffic management do you know?
Cf464e the classic problem [shortest path, chairman tree]
C#(二十七)之C#窗体应用
随机推荐
Codeforces Round #770 (Div. 2) B. Fortune Telling
Ybtoj coloring plan [tree chain dissection, segment tree, tarjan]
关于进程、线程、协程、同步、异步、阻塞、非阻塞、并发、并行、串行的理解
How to execute an SQL statement in MySQL
Custom event of C (31)
How to modify field constraints (type, default, null, etc.) in a table
C form application of C (27)
Detailed explanation of serialization and deserialization
Développement d'un module d'élimination des bavardages à clé basé sur la FPGA
Global and Chinese markets for medical gas manifolds 2022-2028: Research Report on technology, participants, trends, market size and share
[Key shake elimination] development of key shake elimination module based on FPGA
【按鍵消抖】基於FPGA的按鍵消抖模塊開發
Facebook等大廠超十億用戶數據遭泄露,早該關注DID了
/usr/bin/gzip: 1: ELF: not found/usr/bin/gzip: 3: : not found/usr/bin/gzip: 4: Syntax error:
颠覆你的认知?get和post请求的本质
Class A, B, C networks and subnet masks in IPv4
Blue Bridge Cup - day of week
Security xxE vulnerability recurrence (XXe Lab)
Global and Chinese markets for endoscopic drying storage cabinets 2022-2028: Research Report on technology, participants, trends, market size and share
About some basic DP -- those things about coins (the basic introduction of DP)