Inetworkserialize // Inetworkserializebymemcpy im newb

using Unity.Netcode;
using Unity.Collections;

//Im trying to get void PickupNoDelete to work as a serverRpc
//when i made it a serverRpc i get error "dont know how to serialize"
//this script is attatched to the item in th game in this case a potion which is scriptable object
//it says I need to use Inetworkserialize or //Inetworkserializebymemcpy 
//only works offline without serverrpc


public class ItemPickup : NetworkBehaviour
{
public Item Item;

public void PickupNoDelete()
    {
    InventoryManager.Instance.Add(Item);
    }
}
using Unity.Netcode;
using Unity.Collections;

public class InventoryManager : NetworkBehaviour
{


    public static InventoryManager Instance;
    public List<Item> Items = new List<Item>();

    public GameObject InventoryItem;


    public InventoryItemController[] InventoryItems;

    private void Awake()

    {
        Instance = this;
    }


    public void Add(Item item)
    {
    Items.Add(item);
    }

}
using Unity.Netcode;

public class InventoryItemController : NetworkBehaviour
{

public Item item;


    public void AddItem(Item newItem)

    {
        item = newItem;
    }

}
using Unity.Netcode;

[CreateAssetMenu(fileName ="New Item",menuName ="Item/Create New Item")]

public class Item : ScriptableObject
{
    public int id;
    public string itemName;
    public int value;
    public Sprite icon;
    public ItemType itemType;

    public enum ItemType
    {
     potion
     }
}

There is a better way to handle this, but I need a bit of information on the Item class.
Do you plan on making changes to the id, itemName, value, icon, and itemType during runtime?
To make that work you would need to do something along the lines of:

public class Items : ScriptableObject
{
    public List<ItemDefine> ItemDefines;
}

// This allows you to add the struct in the inspector view
[Serializable]
// The INetworkSerializable makes it serializable
public struct ItemDefine : INetworkSerializable
{
    public int id;
    public string itemName;
    public int value;
    public Sprite icon;
    public ItemType itemType;

    public enum ItemType
    {
        potion
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref id);
        serializer.SerializeValue(ref itemName);
        serializer.SerializeValue(ref value);
        serializer.SerializeValue(ref itemType);
    }
}

The above is just pseudo code to address your original question as to how to get your current approach working.

However, if the items are going to be placed in the world and the properties never changed then you might just think about making each item type a network prefab and changing ownership when a player pick up, drops, or consumes an item:

public class PlayerInventory : NetworkBehaviour
{
    private List<Item> m_PlayerInventory = new List<Item>();
    public void PickupItem(Item item)
    {
        m_PlayerInventory.Add(item);
    }

    public void ConsumeItem(Item item)
    {
        if (item.ConsumeItem())
        {
            m_PlayerInventory.Remove(item);
        }
    }

    public void DropItem(Item item)
    {
        item.DropItem();
        m_PlayerInventory.Remove(item);
    }
}

/// <summary>
/// You could create a unique Item class per item type to handle the user's action
/// with the item. Inventory is just managing the owned Items.
/// </summary>

public class Item : NetworkBehaviour
{
    // Not sure how you plan on creating Identifiers and if you will have one unique Id per ItemType or one
    // unique Id per ItemType instance. I just assumed each instance is a unique id.
    [HideInInspector]
    public int Id;

    public ItemTypes ItemType;
    public int Value;
    public string ItemName;
    public Sprite Icon;
    public bool Consumable;
    public float DropOffset;

    // Not sure if this is a 3D or 2D object
    private Collider m_Collider;
    private MeshRenderer m_MeshRenderer;

    private NetworkVariable<bool> m_ItemIsPickedUp = new NetworkVariable<bool>();
    private NetworkVariable<Vector3> m_ItemLocation = new NetworkVariable<Vector3>();
    public enum ItemTypes
    {
        Potion,
        Sword,
        Axe,
    }

#if UNITY_EDITOR
    private void OnValidate()
    {
        // Not sure how you planned on handling the Id
        Id = GetHashCode();
    }
#endif

    private void Awake()
    {
        m_Collider = GetComponent<Collider>();
        if (m_Collider != null)
        {
            m_Collider.isTrigger = true;
        }

        m_MeshRenderer = GetComponent<MeshRenderer>();
    }

    private void OnTriggerEnter(Collider other)
    {
        // If not spawn or we are not the server (authority) then ignore trigger events
        if (!IsSpawned || !IsServer)
        {
            return;
        }

        // If the thing that collided with us has an ItemPickup behavior, then
        // change the ownership to the client that owns that thing (whether a player or the like)
        var itemPickup = other.GetComponent<PlayerInventory>();
        if (itemPickup != null)
        {
            m_ItemIsPickedUp.Value = true;
            NetworkObject.ChangeOwnership(itemPickup.OwnerClientId);
        }
    }

    protected override void OnOwnershipChanged(ulong previous, ulong current)
    {
        if (NetworkManager.LocalClientId == current)
        {
            NetworkManager.LocalClient.PlayerObject.GetComponent<PlayerInventory>().PickupItem(this);
        }

        base.OnOwnershipChanged(previous, current);
    }

    public override void OnNetworkSpawn()
    {
        if (!IsServer)
        {
            m_ItemIsPickedUp.OnValueChanged += OnItemPickedUpChanged;
            m_ItemLocation.OnValueChanged += OnItemLocationChanged;
        }
        base.OnNetworkSpawn();
    }

    public override void OnNetworkDespawn()
    {
        if (!IsServer)
        {
            m_ItemIsPickedUp.OnValueChanged -= OnItemPickedUpChanged;
            m_ItemLocation.OnValueChanged -= OnItemLocationChanged;
        }
        base.OnNetworkSpawn();
    }

    private void OnItemPickedUpChanged(bool previous, bool current)
    {
        OnItemIsPickedUp(current);
    }

    private void OnItemLocationChanged(Vector3 previous, Vector3 current)
    {
        transform.position = current;
    }


    private void OnItemIsPickedUp(bool isPickedUp)
    {
        if (IsServer)
        {
            m_ItemIsPickedUp.Value = isPickedUp;
        }
      
        if (m_Collider)
        {
            m_Collider.enabled = !isPickedUp;
        }      
        if (m_MeshRenderer)
        {
            m_MeshRenderer.enabled = !isPickedUp;
        }
      
        // Add other code here to disable components on this item that you don't
        // want showing up or active when in a player's inventory
    }


    public void DropItem()
    {
        if (!IsSpawned || OwnerClientId != NetworkManager.LocalClientId || NetworkManager.ShutdownInProgress)
        {
            return;
        }

        if (m_ItemIsPickedUp.Value)
        {
            if (IsServer)
            {
                OnDropItem();
            }
            else
            {
                DropItemServerRpc();
            }
        }
    }

    protected virtual void OnDropItem()
    {
        var playerOwner = NetworkManager.ConnectedClients[OwnerClientId].PlayerObject;
        // Was not sure about positioning, you can make an adjustment to the DropOffset (just a way to handle positioning without needing a NetworkTransform)
        // Otherwise, if you use a NetworkTransform then just set the position and remove the script associated with m_ItemLocation
        m_ItemLocation.Value = playerOwner.transform.position + (playerOwner.transform.up * -DropOffset);
        NetworkObject.ChangeOwnership(NetworkManager.ServerClientId);
        OnItemIsPickedUp(false);
    }

    [ServerRpc]
    private void DropItemServerRpc(ServerRpcParams serverRpcParams = default)
    {
        if (serverRpcParams.Receive.SenderClientId == OwnerClientId)
        {
            OnDropItem();
        }
    }

    public bool ConsumeItem()
    {
        if (!IsSpawned || !Consumable || OwnerClientId != NetworkManager.LocalClientId || NetworkManager.ShutdownInProgress)
        {
            return false;
        }

        if (m_ItemIsPickedUp.Value)
        {
            if (IsServer)
            {
                OnConsumeItem();
            }
            else
            {
                ConsumeItemServerRpc();
            }
        }

        return true;
    }

    /// <summary>
    /// Override this to have unique actions for consuming
    /// This would be run server-side so any consume action would need
    /// to be associated with the player (or the like)
    /// </summary>
    protected virtual void OnConsumeItem()
    {
        // Example pseudo code
        if (ItemType == ItemTypes.Potion)
        {
            //
        }
    }

    [ServerRpc]
    private void ConsumeItemServerRpc(ServerRpcParams serverRpcParams = default)
    {
        if (serverRpcParams.Receive.SenderClientId == OwnerClientId)
        {
            OnConsumeItem();
        }
    }
}

The above is just a real quick implementation and could be better refined… but gives you the idea of a different approach (it is all pseudo code and not tested but should be close to functional if not functional).

Hi, I met a similar problem I do the NetworkSerialize on my custom struct
and inside there is an array of enum
so when I try to test the game when the client connects to the host
it’s said

NullReferenceException: Object reference not set to an instance of an object
Unity.Netcode.BufferSerializerWriter.SerializeValue[T] (T[ ]& value, Unity.Netcode.FastBufferWriter+ForEnums unused)

using System;

namespace Damage_Type
{
    [Serializable]
    public enum DamageType
    {
        Physical,
        Energy,
        Special,
        Explosive,
    }
}
[System.Serializable]
public struct ItemStats : INetworkSerializable
{
    public int damage;
    public float fireRate;
    public int reloadTime;
    public int maxAmmo;
    public int currAmmo;
    public float despawnTime;

    public DamageType[] damageType;

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref damage);
        serializer.SerializeValue(ref fireRate);
        serializer.SerializeValue(ref reloadTime);
        serializer.SerializeValue(ref maxAmmo);
        serializer.SerializeValue(ref currAmmo);
        serializer.SerializeValue(ref damageType);
    }
}

Am I doing something wrong or there are better ways to this problem?

Thank you.

ZrMuLi,
When using structs you need to keep in mind that a struct being serialized is being newly instantiated on the reader relative side. Since you are using the array of enum values, I might suggest making this adjustment:

    [System.Serializable]
    public struct ItemStats : INetworkSerializable
    {
        public int damage;
        public float fireRate;
        public int reloadTime;
        public int maxAmmo;
        public int currAmmo;
        public float despawnTime;
        private int DamageTypeSize;
        public DamageType[] damageType;

        public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
        {
            serializer.SerializeValue(ref damage);
            serializer.SerializeValue(ref fireRate);
            serializer.SerializeValue(ref reloadTime);
            serializer.SerializeValue(ref maxAmmo);
            serializer.SerializeValue(ref currAmmo);
            // When writing, set the size of the DamageType array
            if (serializer.IsWriter)
            {
                DamageTypeSize = damageType != null ? damageType.Length : 0;
            }

            // Serialize the size (Write/Read)
            serializer.SerializeValue(ref DamageTypeSize);

            // If it is empty, then don't serialize
            if (DamageTypeSize == 0)
            {
                return;
            }

            // When reading, allocate an array large enough to serialize the values into it
            if (serializer.IsReader )
            {
                damageType = new DamageType[DamageTypeSize];
            }

            // Serialize the array (Write/Read)
            serializer.SerializeValue(ref damageType);
        }
    }

Let me know if this resolves your issue?

It worked like a charm Thank you so much :smile:,

but I have a question when you said
“Since you are using the array of enum values, I might suggest making this adjustment:”
Are there any better options you want to suggest?

Heh... well better is relative to what you need it for and whether you need to have multiple damage types per item or not. If not, then just making it a DamageType (i.e. not an array) would be fine.
If you do need to have multiple damage types, then adding the Flags attribute above the enum declaration and just setting the bit values would save you the memory allocation of the array and reduce the overall size of that data, when serializing, considerably.

As an example, you could do something like this:

[Flags]
[Serializable]
public enum DamageTypeFlags
{
    // Make sure the values are bit/flag relative
    Physical = 0x0001,
    Energy = 0x0002,
    Special = 0x0004,
    Explosive = 0x0008,
    Plasma = 0x0010,
}

[Serializable]
public struct ItemStats : INetworkSerializable
{
    public int damage;
    public float fireRate;
    public int reloadTime;
    public int maxAmmo;
    public int currAmmo;
    public float despawnTime;
    public DamageTypeFlags DamageType;

    public void ApplyDamageType(DamageTypeFlags damageType)
    {
        DamageType |= damageType;
    }

    public void RemoveDamageType(DamageTypeFlags damageType)
    {
        DamageType &= ~damageType;
    }

    public bool HasDamageType(DamageTypeFlags damageType)
    {
        return DamageType.HasFlag(damageType);
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref damage);
        serializer.SerializeValue(ref fireRate);
        serializer.SerializeValue(ref reloadTime);
        serializer.SerializeValue(ref maxAmmo);
        serializer.SerializeValue(ref currAmmo);
        serializer.SerializeValue(ref DamageType);
    }
}

I added those helper methods to just give you an idea how you can modify and check for the damage type.
As well, the unity editor's inspector view also already knows how to handle enum flags.

But it really depends upon what your overall needs are that should dictate what that property type is...if it is just to have different damage types applied to an item, then you might find the enum flag approach easier (serialization wise) and that it consumes much less bandwidth since it only consumes one 32 bit integer (4 bytes) vs the array approach which consumes one integer per damage type (i.e. your original damage types would consume 16 bytes if you had all enum types added to the array).

1 Like

Thank you so much, Noel
I’m new to this and it helped me learn a lot.

Happy holiday and be safe.

1 Like

Thank you so much for the help! :)