当前位置:网站首页>Mlapi series - 04 - network variables and network serialization [network synchronization]

Mlapi series - 04 - network variables and network serialization [network synchronization]

2022-07-06 04:08:00 Feilang era [fwc – fe]

One 、 Overview of network synchronization

Netcode There are two main means of network synchronization : The first is the RPC Mechanism , The remote invocation , The second is to use network variables .

Two 、 Network variables

Network variables belong to Netcode Unique network type , Package maintenance one Value, If you want to encapsulate multiple fields or arrays, you need to encapsulate them yourself .

3、 ... and 、 About framework built-in serialization

  • 1 Network variables are defined as generic classes NetworkVariable, Support C# Basic types 、Unity Basic types 、 Custom enumeration .

  • 2 RPC Pass message parameters , You need to use serializable types , The following serializable types and inherited serialization interfaces are supported INetworkSerializable Custom serialization type of .

1 C# The base type

C# The base type will be serialized by the built-in serialization code .
These types include :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 The base type

Unity The base type Color, Color32, Vector2, Vector3, Vector4, Quaternion, Ray, Ray2D The type will be serialized by the built-in serialization code .

[ClientRpc]
void BarClientRpc(Color somecolor) {
     /* ... */ }

void Update()
{
    
    if (Input.GetKeyDown(KeyCode.P))
    {
    
        BarClientRpc(Color.red); // Server -> Client
    }
}

3 Enumeration type

User defined enumeration types will be serialized by built-in serialization code ( Use the base integer type )

enum SmallEnum : byte // 0-255  Limited length 
{
    
    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 Array

image int[] Such an array Serialized by built-in serialization code :

If their base type is one of the types that support serialization ( Such as Vector3) perhaps Whether they realize INetworkSerializable Interface .

[ServerRpc]
void HelloServerRpc(int[] scores, Color[] colors) {
     /* ... */ }

[ClientRpc]
void WorldClientRpc(MyComplexType[] values) {
     /* ... */ }

5 INetworkSerializable Interface

INetworkSerializable Interface can be used to define custom serializable types .

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
}

// Implementation types INetworkSerializable, And be NetworkSerializer, RPCs and NetworkVariable Support 

[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 Nested sequence type

Nested sequence types will be null Unless you initialize in one of the following ways :

  • Manually before calling SerializeValue If serializer.IsReader( Or something like that )
  • Initialize in the default constructor

This is intentional . Before initializing correctly , You may see that these values are null. The serializer did not deserialize them , therefore null Values are simply applied before serialization .

5.2 Conditional serialization

Because there is more control over the serialization of structures , So you can implement conditional serialization at run time .

Example : Array

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]);
        }
    }
}

Read :

serialize ( Deserialization )length Flow back from
iteration Array member n=length times Serialize the value ( Deserialization ) Back to array [n] Elements in the stream

write in :

Serialization length = Array Length of the write stream
Iterate over array members n = Length times Serialize the values in the array [n] Element is added to the stream
this BufferSerializer.IsReader Flags are used here to determine whether to set length value , Then we use it to determine whether to create a new int[] Examples and length Size to set Array Before reading the value from the stream .
There is another equivalent but opposite BufferSerializer.IsWriting

Example : Move

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);
        }
    }
}

Read :

serialize ( Deserialization )Position Flow back from
serialize ( Deserialization )Rotation Flow back from
serialize ( Deserialization )SyncVelocity Flow back from
Check whether the SyncVelocity Set to true, If so :
serialize ( Deserialization )LinearVelocity Flow back from
serialize ( Deserialization )AngularVelocity Flow back from

write in :

load Position Into the stream
load Rotation Into the stream
load SyncVelocity Into the stream
Check whether the SyncVelocity Set to true, If so :
load LinearVelocity Into the stream
load AngularVelocity Into the stream
Different from the arrangement in the above example , We didn't use BufferSerializer.IsReader Flag to change the serialization logic , But change the value of the serialization flag itself .

If SyncVelocity Flag set to true , be LinearVelocity and AngularVelocity Will be serialized into the stream
When SyncVelocity Flag set to false, We will leave LinearVelocity and AngularVelocity Use the default value .

5.3 Recursive nested serialization

Nested members can be serialized recursively INetworkSerializable Interfaces in the hierarchical tree .

Example :

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);
    }
}

If we want to load separately MyStructA, It will use NetworkSerializer load Position and Rotation Inflow ,.

However , If we want to serialize MyStructB, It serializes SomeNumber and SomeText Into the stream , Then serialize StructA adopt Call -
MyStructA void
NetworkSerialize(NetworkSerializer) Method , This method serializes Position and Rotation Enter the same first-class .

Be careful
Technically speaking , There is no hard limit to the number INetworkSerializable Fields that can be serialized along the tree hierarchy . In practice , Consider memory and bandwidth boundaries for best performance .

You can conditionally serialize in a recursive nested serialization scenario , And use these two functions .

6 Custom Serialization

When using RPCs,NetworkVariable Or any other game object network code that needs to be serialized (Netcode) Related tasks .Netcode Use the default serialization pipeline , As shown below :

Custom Types => Built In Types => INetworkSerializable

in other words , When Netcode The first time you get a type , It will check any custom types that the user has registered for serialization , Then it will check whether it is a built-in type , Such as Vector3、float etc. . These are handled by default . If not , It will check whether the type inherits INetworkSerializable If there is , It will call its write Method .

By default , Any satisfaction unmanaged Generic constraints can be automatically serialized as RPC Parameters . This includes all basic types (bool、byte、int、float、enum etc. ) And any structure that contains only these basic types .

Through this process , You can override all serialization types , Even built into types , And use the provided API, It can even be done with types that you don't define yourself , Those types behind the third-party wall , for example . Network type .

To register a custom type or override a processed type , Need to be for FastBufferReader.ReadValueSafe() and FastBufferWriter.WriteValueSafe():

// tell Netcode How to serialize and deserialize in the future Url.
// The class name doesn't matter here .
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 The code generation of will be directly through FastBufferWriter and FastBufferReader Get and use these functions automatically .

You can also choose to use the same method to add pairs BufferSerializer<TReaderWriter>.SerializeValue(), If you will , This will make this type in INetworkSerializable type :

// The class name doesn't matter here .
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 Network objects and network behavior

GameObjects, NetworkObjects and NetworkBehaviour Not a serializable type , So it can't be in RPC perhaps NetworkVariables By default .

There are two convenient wrappers【 Wrappers 】 Can be used to send pairs NetworkObject Or a NetworkBehaviour adopt RPC or NetworkVariables.

7.1 NetworkObjectReference Network object reference type

NetworkObjectReference Can be used to serialize pairs NetworkObject. It can only be used on already generated NetworkObjects.

Here is a use NetworkObject Reference of sending target NetworkObject adopt 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 Implicit operation

And from / Implicit operator to transform NetworkObject/GameObject This can be used to simplify code . for example , The above example can also be written in the following way :

NetworkObjectReference -> NetworkObject
GameObject -> NetworkObjectReference
public class Weapon : NetworkBehaviour
{
    
    public void ShootTarget(GameObject target)
    {
    
        ShootTargetServerRpc(target);
    }

    [ServerRpc]
    public void ShootTargetServerRpc(NetworkObjectReference target)
    {
    
        NetworkObject targetObject = target;
    }
}

Be careful : If the reference is not found , To NetworkObject / GameObject The implicit conversion of will result in Null.

7.3 NetworkBehaviourReference Network behavior reference

NetworkBehaviourReference Work in a way similar to NetworkObjectReference It is used to refer to specific NetworkBehaviour Derived components on 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 Network object reference 、 How network behavior references work

NetworkObjectReference and NetworkBehaviourReference It's convenient wrappers Wrappers , It serializes NetworkObject When sending , Retrieve the corresponding Use that id.NetworkBehaviourReference Send an additional index , Used to find the correct NetworkBehaviour In which NetworkObject On .

They all achieve INetworkSerializable Interface .


Four 、 Network variables use

Network variable generic class NetworkVariable, Support C# Basic types and Unity Basic types .

1 bool Network variables of type use

When NetworkVariable<T> Of Value change , OnValueChanged Callback the parameter with old and new values , The callback function should constantly poll for the latest value in response to value changes .

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 Read and write permissions of network variables

By default ,NetworkVariable<T> It can only be written by the server and can be read by anyone . These permissions can be changed through the constructor .

// 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. Sample code integration

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();
    }
}

5、 ... and 、 MLAPI pre-10 Some changes in the version

0.0 Upgrade instructions

lately Netcode The upgrade pre-10 edition , The official repaired some bug, Will the blogger provide from pre-6 Version of the custom generic network variable compilation caused the editor to crash bug Repair the , There are other changes .

 Insert picture description here
 Insert picture description here

1 Fix array generic crash

Fixed NetworkAnimator issue where it was not always disposing the NativeArray that is allocated when spawned. (#1946)

 Insert picture description here

2 ClientNetworkTransform Base class function name OnIsServerAuthoritatitive change

 Before the change :
// Overwrite this value and return false To follow the owner's permissions , otherwise , The default is server permission 
        protected override bool OnIsServerAuthoritatitive()
        {
    
            return false;
        }
 After modification :
// Overwrite this value and return false To follow the owner's permissions , otherwise , The default is server permission 
        protected override bool OnIsServerAuthoritative()
        {
    
            return false;
        }

3 Base class changes :NetworkVariableSerialization It is amended as follows NetworkVariableBase

After upgrading , Partial inheritance NetworkVariableSerialization Of Custom network variable script There may be an error .
The reason for the error is that the base class used in the transition phase is used NetworkVariableSerialization May be ready to abandon .

3.1 Error reason

1 Before version upgrade , It belongs to excessive script , Same as network variables , Inherits the network variable base class NetworkVariableBase, rewrite ReadDelta And other base class methods can use encapsulated Read and Write Static methods
1.1 Easy to use
----------------------------------------------------------------------------------------【pre-9】 Insert picture description here
2 After version upgrade , Removed base class 、Read and Write Static method, etc
2.1 The custom network variable returns to the base class of the network variable NetworkVariableBase that will do
----------------------------------------------------------------------------------------【pre-10】 Insert picture description here

3.2 Example of script modification

 Before modification :
 Read(reader, out Value.Array[i]);
 
 After the changes :
 reader.ReadValueSafe(out Value.Array[i]);
原网站

版权声明
本文为[Feilang era [fwc – fe]]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/187/202207060405419184.html