docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Custom NetworkVariables

    In addition to the standard NetworkVariables available in Netcode for GameObjects, you can also create custom NetworkVariables for advanced implementations. The NetworkVariable and NetworkList classes were created as NetworkVariableBase class implementation examples. While the NetworkVariable<T> class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, you can create your own custom implementation.

    Note

    Read the Serialization overview page to understand how Netcode for GameObjects handles serialization.

    To create your own NetworkVariableBase-derived container, you should:

    1. Create a class deriving from NetworkVariableBase.
    2. Override the following methods:
      • void WriteField(FastBufferWriter writer)
      • void ReadField(FastBufferReader reader)
      • void WriteDelta(FastBufferWriter writer)
      • void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
    3. Depending on your custom NetworkVariableBase container, you can look at NetworkVariable<T> or NetworkList to see how those two examples were implemented.

    NetworkVariable serialization

    The way you read and write NetworkVariables changes depending on the type you use.

    • Known, non-generic types can use FastBufferWriter and FastBufferReader to serialize the NetworkVariable value.
    • Integer types: This type gives you the option to use BytePacker and ByteUnpacker to compress the NetworkVariable value. This process can save bandwidth but adds CPU processing time.
    • Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods:
      • Use GenerateSerializationForTypeAttribute to serialize hard-coded types.
      • Use GenerateSerializationForGenericParameterAttribute to serialize generic types.
    • Types implementing INetworkSerializable can also be serialized using FastBufferWriter and FastBufferReader.

    Read more about custom serialization for more approaches to customizing serialization.

    Serialize a hard-coded type

    The following code example uses GenerateSerializationForTypeAttribute to generate serialization for a specific hard-coded type:

    [GenerateSerializationForType(typeof(Foo))]
    public class MyNetworkVariableTypeUsingFoo : NetworkVariableBase {}
    

    You can call a type that you know the name of with the FastBufferReader or FastBufferWriter methods. These methods don't work for generic types because the name of the type is unknown.

    Serialize a generic type

    The following code example uses GenerateSerializationForGenericParameterAttribute to generate serialization for a specific generic parameter in your NetworkVariable type:

    [GenerateSerializationForGenericParameter(0)]
    public class MyNetworkVariableType<T> : NetworkVariableBase {}
    

    This attribute accepts an integer that indicates which parameter in the type to generate serialization for. This value is 0-indexed, which means that the first type is 0, the second type is 1, and so on.

    The following code example places the attribute more than once on one class to generate serialization for multiple types, in this case,TFirstType and `TSecondType:

    [GenerateSerializationForGenericParameter(0)]
    [GenerateSerializationForGenericParameter(1)]
    public class MyNetworkVariableType<TFirstType, TSecondType> : NetworkVariableBase {}
    

    The GenerateSerializationForGenericParameterAttribute and GenerateSerializationForTypeAttribute attributes make Unity's code generation create the following methods:

    NetworkVariableSerialization<T>.Write(FastBufferWriter writer, ref T value);
    NetworkVariableSerialization<T>.Read(FastBufferWriter writer, ref T value);
    NetworkVariableSerialization<T>.Duplicate(in T value, ref T duplicatedValue);
    NetworkVariableSerialization<T>.AreEqual(in T a, in T b);
    

    For dynamically-allocated types with a value that isn't null (for example, managed types and collections like NativeArray and NativeList) call Read to read the value in the existing object and write data into it directly (in-place). This avoids more allocations.

    You can use AreEqual to determine if a value is different from the value that Duplicate cached. This avoids sending the same value multiple times. You can also use the previous value that Duplicate cached to calculate deltas to use in ReadDelta and WriteDelta.

    The type you use must be serializable according to the supported types list. Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. Unity's code generator assumes that all NetworkVariable types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for.

    Note

    These attributes won't generate delta serialization. If you would like to customize how deltas are sent when a custom value has partially changed, refer to UserNetworkVariableSerialization.

    Custom NetworkVariableBase example

    This example shows a custom NetworkVariable type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom NetworkVariable type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation:

    // Using MyCustomNetworkVariable within a NetworkBehaviour
    internal class TestMyCustomNetworkVariable : NetworkBehaviour
    {
        public MyCustomNetworkVariable CustomNetworkVariable = new MyCustomNetworkVariable();
        public MyCustomGenericNetworkVariable<int> CustomGenericNetworkVariable = new MyCustomGenericNetworkVariable<int>();
    
        public override void OnNetworkSpawn()
        {
            if (IsServer)
            {
                for (int i = 0; i < 4; i++)
                {
                    var someData = new SomeData
                    {
                        SomeFloatData = i,
                        SomeIntData = i
                    };
                    someData.SomeListOfValues.Add((ulong)i + 1000000);
                    someData.SomeListOfValues.Add((ulong)i + 2000000);
                    someData.SomeListOfValues.Add((ulong)i + 3000000);
                    CustomNetworkVariable.SomeDataToSynchronize.Add(someData);
                    CustomNetworkVariable.SetDirty(true);
    
                    CustomGenericNetworkVariable.SomeDataToSynchronize.Add(i);
                    CustomGenericNetworkVariable.SetDirty(true);
                }
            }
        }
    }
    
    /// <summary>
    /// Bare minimum example of NetworkVariableBase derived class
    /// </summary>
    [Serializable]
    public class MyCustomNetworkVariable : NetworkVariableBase
    {
        /// <summary>
        /// Managed list of class instances
        /// </summary>
        internal List<SomeData> SomeDataToSynchronize = new List<SomeData>();
    
        /// <summary>
        /// Writes the complete state of the variable to the writer
        /// </summary>
        /// <param name="writer">The stream to write the state to</param>
        public override void WriteField(FastBufferWriter writer)
        {
            // Serialize the data we need to synchronize
            writer.WriteValueSafe(SomeDataToSynchronize.Count);
            foreach (var dataEntry in SomeDataToSynchronize)
            {
                writer.WriteValueSafe(dataEntry.SomeIntData);
                writer.WriteValueSafe(dataEntry.SomeFloatData);
                writer.WriteValueSafe(dataEntry.SomeListOfValues.Count);
                foreach (var valueItem in dataEntry.SomeListOfValues)
                {
                    writer.WriteValueSafe(valueItem);
                }
            }
        }
    
        /// <summary>
        /// Reads the complete state from the reader and applies it
        /// </summary>
        /// <param name="reader">The stream to read the state from</param>
        public override void ReadField(FastBufferReader reader)
        {
            // De-Serialize the data being synchronized
            var itemsToUpdate = 0;
            reader.ReadValueSafe(out itemsToUpdate);
            SomeDataToSynchronize.Clear();
            for (int i = 0; i < itemsToUpdate; i++)
            {
                var newEntry = new SomeData();
                reader.ReadValueSafe(out newEntry.SomeIntData);
                reader.ReadValueSafe(out newEntry.SomeFloatData);
                var itemsCount = 0;
                var tempValue = (ulong)0;
                reader.ReadValueSafe(out itemsCount);
                newEntry.SomeListOfValues.Clear();
                for (int j = 0; j < itemsCount; j++)
                {
                    reader.ReadValueSafe(out tempValue);
                    newEntry.SomeListOfValues.Add(tempValue);
                }
    
                SomeDataToSynchronize.Add(newEntry);
            }
        }
    
        /// <summary>
        /// Used to write partial updates rather than synchronizing the full state on every change.
        /// </summary>
        /// <param name="writer">The stream to write the state to</param>
        public override void WriteDelta(FastBufferWriter writer)
        {
            // Not implemented for this example, instead we can write the field
            WriteField(writer);
        }
    
        /// <summary>
        /// Used to read partial updates rather than synchronizing the full state on every change.
        /// </summary>
        /// <inheritdoc/>
        public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
        {
            // Not implemented for this example, instead we can read the field
            ReadField(reader);
        }
    
    }
    
    /// <summary>
    /// Bare minimum example of generic NetworkVariableBase derived class
    /// </summary>
    /// <typeparam name="T">Generic type marker</typeparam>
    [Serializable]
    [GenerateSerializationForGenericParameter(0)]
    public class MyCustomGenericNetworkVariable<T> : NetworkVariableBase
    {
        /// <summary>Managed list of class instances</summary>
        public List<T> SomeDataToSynchronize = new List<T>();
    
        /// <summary>
        /// Writes the complete state of the variable to the writer
        /// </summary>
        /// <param name="writer">The stream to write the state to</param>
        public override void WriteField(FastBufferWriter writer)
        {
            // Serialize the data we need to synchronize
            writer.WriteValueSafe(SomeDataToSynchronize.Count);
            for (var i = 0; i < SomeDataToSynchronize.Count; ++i)
            {
                var dataEntry = SomeDataToSynchronize[i];
                // NetworkVariableSerialization<T> is used for serializing generic types
                NetworkVariableSerialization<T>.Write(writer, ref dataEntry);
            }
        }
    
        /// <summary>
        /// Reads the complete state from the reader and applies it
        /// </summary>
        /// <param name="reader">The stream to read the state from</param>
        public override void ReadField(FastBufferReader reader)
        {
            // De-Serialize the data being synchronized
            var itemsToUpdate = 0;
            reader.ReadValueSafe(out itemsToUpdate);
            SomeDataToSynchronize.Clear();
            for (int i = 0; i < itemsToUpdate; i++)
            {
                T newEntry = default;
                // NetworkVariableSerialization<T> is used for serializing generic types
                NetworkVariableSerialization<T>.Read(reader, ref newEntry);
                SomeDataToSynchronize.Add(newEntry);
            }
        }
    
        /// <summary>
        /// Used to write partial updates rather than synchronizing the full state on every change.
        /// </summary>
        /// <param name="writer">The stream to write the state to</param>
        public override void WriteDelta(FastBufferWriter writer)
        {
            // Not implemented for this example, instead we can write the field
            WriteField(writer);
        }
    
        /// <summary>
        /// Used to read partial updates rather than synchronizing the full state on every change.
        /// </summary>
        /// <inheritdoc/>
        public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
        {
            // Not implemented for this example, instead we can read the field
            ReadField(reader);
        }
    }
    
    [Serializable]
    internal class SomeData
    {
        public int SomeIntData = default;
        public float SomeFloatData = default;
        public List<ulong> SomeListOfValues = new List<ulong>();
    }
    

    While the above example isn't the recommended way to synchronize a list where the number or order of elements in the list often changes, it's an example of how you can define your own rules using NetworkVariableBase.

    You can test the code above by:

    • Using the above code with a project that includes Netcode for GameObjects.
    • Adding the TestMyCustomNetworkVariable component to an in-scene placed NetworkObject.
    • Creating a stand alone build and running that as a host or server.
    • Running the same scene within the Editor and connecting as a client.
      • Once connected, you can then select the GameObject with the attached NetworkObject and TestMyCustomNetworkVariable components so it appears in the inspector view. There you can verify the TestMyCustomNetworkVariable.CustomNetworkVariable property was synchronized with the client (like in the screenshot below): ScreenShot
    Note

    You can't nest NetworkVariables inside other NetworkVariable classes. This is because Netcode for GameObjects performs a code generation step to define serialization callbacks for each type it finds in a NetworkVariable. The code generation step looks for variables as fields of NetworkBehaviour types; it misses any NetworkVariables declared anywhere else. Instead of nesting NetworkVariables inside other NetworkVariable classes, declare NetworkVariable or NetworkList properties within the same NetworkBehaviour within which you have declared your custom NetworkVariableBase implementation.

    In This Article
    Back to top
    Copyright © 2026 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)