Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Inetworkserialize // Inetworkserializebymemcpy im newb

Discussion in 'Netcode for GameObjects' started by PBUSHER, Dec 3, 2023.

  1. PBUSHER

    PBUSHER

    Joined:
    Feb 26, 2022
    Posts:
    1
    Code (CSharp):
    1. using Unity.Netcode;
    2. using Unity.Collections;
    3.  
    4. //Im trying to get void PickupNoDelete to work as a serverRpc
    5. //when i made it a serverRpc i get error "dont know how to serialize"
    6. //this script is attatched to the item in th game in this case a potion which is scriptable object
    7. //it says I need to use Inetworkserialize or //Inetworkserializebymemcpy
    8. //only works offline without serverrpc
    9.  
    10.  
    11. public class ItemPickup : NetworkBehaviour
    12. {
    13. public Item Item;
    14.  
    15. public void PickupNoDelete()
    16.     {
    17.     InventoryManager.Instance.Add(Item);
    18.     }
    19. }
    20.  








    Code (CSharp):
    1. using Unity.Netcode;
    2. using Unity.Collections;
    3.  
    4. public class InventoryManager : NetworkBehaviour
    5. {
    6.  
    7.  
    8.     public static InventoryManager Instance;
    9.     public List<Item> Items = new List<Item>();
    10.  
    11.     public GameObject InventoryItem;
    12.  
    13.  
    14.     public InventoryItemController[] InventoryItems;
    15.  
    16.     private void Awake()
    17.  
    18.     {
    19.         Instance = this;
    20.     }
    21.  
    22.  
    23.     public void Add(Item item)
    24.     {
    25.     Items.Add(item);
    26.     }
    27.  
    28. }






    Code (CSharp):
    1. using Unity.Netcode;
    2.  
    3. public class InventoryItemController : NetworkBehaviour
    4. {
    5.  
    6. public Item item;
    7.  
    8.  
    9.     public void AddItem(Item newItem)
    10.  
    11.     {
    12.         item = newItem;
    13.     }
    14.  
    15. }


    Code (CSharp):
    1. using Unity.Netcode;
    2.  
    3. [CreateAssetMenu(fileName ="New Item",menuName ="Item/Create New Item")]
    4.  
    5. public class Item : ScriptableObject
    6. {
    7.     public int id;
    8.     public string itemName;
    9.     public int value;
    10.     public Sprite icon;
    11.     public ItemType itemType;
    12.  
    13.     public enum ItemType
    14.     {
    15.      potion
    16.      }
    17. }
     
  2. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    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:

    Code (CSharp):
    1. public class Items : ScriptableObject
    2. {
    3.     public List<ItemDefine> ItemDefines;
    4. }
    5.  
    6. // This allows you to add the struct in the inspector view
    7. [Serializable]
    8. // The INetworkSerializable makes it serializable
    9. public struct ItemDefine : INetworkSerializable
    10. {
    11.     public int id;
    12.     public string itemName;
    13.     public int value;
    14.     public Sprite icon;
    15.     public ItemType itemType;
    16.  
    17.     public enum ItemType
    18.     {
    19.         potion
    20.     }
    21.  
    22.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    23.     {
    24.         serializer.SerializeValue(ref id);
    25.         serializer.SerializeValue(ref itemName);
    26.         serializer.SerializeValue(ref value);
    27.         serializer.SerializeValue(ref itemType);
    28.     }
    29. }
    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:
    Code (CSharp):
    1. public class PlayerInventory : NetworkBehaviour
    2. {
    3.     private List<Item> m_PlayerInventory = new List<Item>();
    4.     public void PickupItem(Item item)
    5.     {
    6.         m_PlayerInventory.Add(item);
    7.     }
    8.  
    9.     public void ConsumeItem(Item item)
    10.     {
    11.         if (item.ConsumeItem())
    12.         {
    13.             m_PlayerInventory.Remove(item);
    14.         }
    15.     }
    16.  
    17.     public void DropItem(Item item)
    18.     {
    19.         item.DropItem();
    20.         m_PlayerInventory.Remove(item);
    21.     }
    22. }
    23.  
    24. /// <summary>
    25. /// You could create a unique Item class per item type to handle the user's action
    26. /// with the item. Inventory is just managing the owned Items.
    27. /// </summary>
    28.  
    29. public class Item : NetworkBehaviour
    30. {
    31.     // Not sure how you plan on creating Identifiers and if you will have one unique Id per ItemType or one
    32.     // unique Id per ItemType instance. I just assumed each instance is a unique id.
    33.     [HideInInspector]
    34.     public int Id;
    35.  
    36.     public ItemTypes ItemType;
    37.     public int Value;
    38.     public string ItemName;
    39.     public Sprite Icon;
    40.     public bool Consumable;
    41.     public float DropOffset;
    42.  
    43.     // Not sure if this is a 3D or 2D object
    44.     private Collider m_Collider;
    45.     private MeshRenderer m_MeshRenderer;
    46.  
    47.     private NetworkVariable<bool> m_ItemIsPickedUp = new NetworkVariable<bool>();
    48.     private NetworkVariable<Vector3> m_ItemLocation = new NetworkVariable<Vector3>();
    49.     public enum ItemTypes
    50.     {
    51.         Potion,
    52.         Sword,
    53.         Axe,
    54.     }
    55.  
    56. #if UNITY_EDITOR
    57.     private void OnValidate()
    58.     {
    59.         // Not sure how you planned on handling the Id
    60.         Id = GetHashCode();
    61.     }
    62. #endif
    63.  
    64.     private void Awake()
    65.     {
    66.         m_Collider = GetComponent<Collider>();
    67.         if (m_Collider != null)
    68.         {
    69.             m_Collider.isTrigger = true;
    70.         }
    71.  
    72.         m_MeshRenderer = GetComponent<MeshRenderer>();
    73.     }
    74.  
    75.     private void OnTriggerEnter(Collider other)
    76.     {
    77.         // If not spawn or we are not the server (authority) then ignore trigger events
    78.         if (!IsSpawned || !IsServer)
    79.         {
    80.             return;
    81.         }
    82.  
    83.         // If the thing that collided with us has an ItemPickup behavior, then
    84.         // change the ownership to the client that owns that thing (whether a player or the like)
    85.         var itemPickup = other.GetComponent<PlayerInventory>();
    86.         if (itemPickup != null)
    87.         {
    88.             m_ItemIsPickedUp.Value = true;
    89.             NetworkObject.ChangeOwnership(itemPickup.OwnerClientId);
    90.         }
    91.     }
    92.  
    93.     protected override void OnOwnershipChanged(ulong previous, ulong current)
    94.     {
    95.         if (NetworkManager.LocalClientId == current)
    96.         {
    97.             NetworkManager.LocalClient.PlayerObject.GetComponent<PlayerInventory>().PickupItem(this);
    98.         }
    99.  
    100.         base.OnOwnershipChanged(previous, current);
    101.     }
    102.  
    103.     public override void OnNetworkSpawn()
    104.     {
    105.         if (!IsServer)
    106.         {
    107.             m_ItemIsPickedUp.OnValueChanged += OnItemPickedUpChanged;
    108.             m_ItemLocation.OnValueChanged += OnItemLocationChanged;
    109.         }
    110.         base.OnNetworkSpawn();
    111.     }
    112.  
    113.     public override void OnNetworkDespawn()
    114.     {
    115.         if (!IsServer)
    116.         {
    117.             m_ItemIsPickedUp.OnValueChanged -= OnItemPickedUpChanged;
    118.             m_ItemLocation.OnValueChanged -= OnItemLocationChanged;
    119.         }
    120.         base.OnNetworkSpawn();
    121.     }
    122.  
    123.     private void OnItemPickedUpChanged(bool previous, bool current)
    124.     {
    125.         OnItemIsPickedUp(current);
    126.     }
    127.  
    128.     private void OnItemLocationChanged(Vector3 previous, Vector3 current)
    129.     {
    130.         transform.position = current;
    131.     }
    132.  
    133.  
    134.     private void OnItemIsPickedUp(bool isPickedUp)
    135.     {
    136.         if (IsServer)
    137.         {
    138.             m_ItemIsPickedUp.Value = isPickedUp;
    139.         }
    140.      
    141.         if (m_Collider)
    142.         {
    143.             m_Collider.enabled = !isPickedUp;
    144.         }      
    145.         if (m_MeshRenderer)
    146.         {
    147.             m_MeshRenderer.enabled = !isPickedUp;
    148.         }
    149.      
    150.         // Add other code here to disable components on this item that you don't
    151.         // want showing up or active when in a player's inventory
    152.     }
    153.  
    154.  
    155.     public void DropItem()
    156.     {
    157.         if (!IsSpawned || OwnerClientId != NetworkManager.LocalClientId || NetworkManager.ShutdownInProgress)
    158.         {
    159.             return;
    160.         }
    161.  
    162.         if (m_ItemIsPickedUp.Value)
    163.         {
    164.             if (IsServer)
    165.             {
    166.                 OnDropItem();
    167.             }
    168.             else
    169.             {
    170.                 DropItemServerRpc();
    171.             }
    172.         }
    173.     }
    174.  
    175.     protected virtual void OnDropItem()
    176.     {
    177.         var playerOwner = NetworkManager.ConnectedClients[OwnerClientId].PlayerObject;
    178.         // Was not sure about positioning, you can make an adjustment to the DropOffset (just a way to handle positioning without needing a NetworkTransform)
    179.         // Otherwise, if you use a NetworkTransform then just set the position and remove the script associated with m_ItemLocation
    180.         m_ItemLocation.Value = playerOwner.transform.position + (playerOwner.transform.up * -DropOffset);
    181.         NetworkObject.ChangeOwnership(NetworkManager.ServerClientId);
    182.         OnItemIsPickedUp(false);
    183.     }
    184.  
    185.     [ServerRpc]
    186.     private void DropItemServerRpc(ServerRpcParams serverRpcParams = default)
    187.     {
    188.         if (serverRpcParams.Receive.SenderClientId == OwnerClientId)
    189.         {
    190.             OnDropItem();
    191.         }
    192.     }
    193.  
    194.     public bool ConsumeItem()
    195.     {
    196.         if (!IsSpawned || !Consumable || OwnerClientId != NetworkManager.LocalClientId || NetworkManager.ShutdownInProgress)
    197.         {
    198.             return false;
    199.         }
    200.  
    201.         if (m_ItemIsPickedUp.Value)
    202.         {
    203.             if (IsServer)
    204.             {
    205.                 OnConsumeItem();
    206.             }
    207.             else
    208.             {
    209.                 ConsumeItemServerRpc();
    210.             }
    211.         }
    212.  
    213.         return true;
    214.     }
    215.  
    216.     /// <summary>
    217.     /// Override this to have unique actions for consuming
    218.     /// This would be run server-side so any consume action would need
    219.     /// to be associated with the player (or the like)
    220.     /// </summary>
    221.     protected virtual void OnConsumeItem()
    222.     {
    223.         // Example pseudo code
    224.         if (ItemType == ItemTypes.Potion)
    225.         {
    226.             //
    227.         }
    228.     }
    229.  
    230.     [ServerRpc]
    231.     private void ConsumeItemServerRpc(ServerRpcParams serverRpcParams = default)
    232.     {
    233.         if (serverRpcParams.Receive.SenderClientId == OwnerClientId)
    234.         {
    235.             OnConsumeItem();
    236.         }
    237.     }
    238. }
    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).
     
  3. ZrMuLi

    ZrMuLi

    Joined:
    Jul 8, 2021
    Posts:
    4
    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)

    Code (CSharp):
    1. using System;
    2.  
    3. namespace Damage_Type
    4. {
    5.     [Serializable]
    6.     public enum DamageType
    7.     {
    8.         Physical,
    9.         Energy,
    10.         Special,
    11.         Explosive,
    12.     }
    13. }
    Code (CSharp):
    1. [System.Serializable]
    2. public struct ItemStats : INetworkSerializable
    3. {
    4.     public int damage;
    5.     public float fireRate;
    6.     public int reloadTime;
    7.     public int maxAmmo;
    8.     public int currAmmo;
    9.     public float despawnTime;
    10.  
    11.     public DamageType[] damageType;
    12.  
    13.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    14.     {
    15.         serializer.SerializeValue(ref damage);
    16.         serializer.SerializeValue(ref fireRate);
    17.         serializer.SerializeValue(ref reloadTime);
    18.         serializer.SerializeValue(ref maxAmmo);
    19.         serializer.SerializeValue(ref currAmmo);
    20.         serializer.SerializeValue(ref damageType);
    21.     }
    22. }
    Am I doing something wrong or there are better ways to this problem?

    Thank you.
     
  4. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    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:

    Code (CSharp):
    1.     [System.Serializable]
    2.     public struct ItemStats : INetworkSerializable
    3.     {
    4.         public int damage;
    5.         public float fireRate;
    6.         public int reloadTime;
    7.         public int maxAmmo;
    8.         public int currAmmo;
    9.         public float despawnTime;
    10.         private int DamageTypeSize;
    11.         public DamageType[] damageType;
    12.  
    13.         public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    14.         {
    15.             serializer.SerializeValue(ref damage);
    16.             serializer.SerializeValue(ref fireRate);
    17.             serializer.SerializeValue(ref reloadTime);
    18.             serializer.SerializeValue(ref maxAmmo);
    19.             serializer.SerializeValue(ref currAmmo);
    20.             // When writing, set the size of the DamageType array
    21.             if (serializer.IsWriter)
    22.             {
    23.                 DamageTypeSize = damageType != null ? damageType.Length : 0;
    24.             }
    25.  
    26.             // Serialize the size (Write/Read)
    27.             serializer.SerializeValue(ref DamageTypeSize);
    28.  
    29.             // If it is empty, then don't serialize
    30.             if (DamageTypeSize == 0)
    31.             {
    32.                 return;
    33.             }
    34.  
    35.             // When reading, allocate an array large enough to serialize the values into it
    36.             if (serializer.IsReader )
    37.             {
    38.                 damageType = new DamageType[DamageTypeSize];
    39.             }
    40.  
    41.             // Serialize the array (Write/Read)
    42.             serializer.SerializeValue(ref damageType);
    43.         }
    44.     }
    Let me know if this resolves your issue?
     
  5. ZrMuLi

    ZrMuLi

    Joined:
    Jul 8, 2021
    Posts:
    4
    It worked like a charm Thank you so much :D,

    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?
     
  6. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    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:

    Code (CSharp):
    1. [Flags]
    2. [Serializable]
    3. public enum DamageTypeFlags
    4. {
    5.     // Make sure the values are bit/flag relative
    6.     Physical = 0x0001,
    7.     Energy = 0x0002,
    8.     Special = 0x0004,
    9.     Explosive = 0x0008,
    10.     Plasma = 0x0010,
    11. }
    12.  
    13. [Serializable]
    14. public struct ItemStats : INetworkSerializable
    15. {
    16.     public int damage;
    17.     public float fireRate;
    18.     public int reloadTime;
    19.     public int maxAmmo;
    20.     public int currAmmo;
    21.     public float despawnTime;
    22.     public DamageTypeFlags DamageType;
    23.  
    24.     public void ApplyDamageType(DamageTypeFlags damageType)
    25.     {
    26.         DamageType |= damageType;
    27.     }
    28.  
    29.     public void RemoveDamageType(DamageTypeFlags damageType)
    30.     {
    31.         DamageType &= ~damageType;
    32.     }
    33.  
    34.     public bool HasDamageType(DamageTypeFlags damageType)
    35.     {
    36.         return DamageType.HasFlag(damageType);
    37.     }
    38.  
    39.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    40.     {
    41.         serializer.SerializeValue(ref damage);
    42.         serializer.SerializeValue(ref fireRate);
    43.         serializer.SerializeValue(ref reloadTime);
    44.         serializer.SerializeValue(ref maxAmmo);
    45.         serializer.SerializeValue(ref currAmmo);
    46.         serializer.SerializeValue(ref DamageType);
    47.     }
    48. }
    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).
     
    ZrMuLi likes this.
  7. ZrMuLi

    ZrMuLi

    Joined:
    Jul 8, 2021
    Posts:
    4
    Thank you so much, Noel
    I'm new to this and it helped me learn a lot.

    Happy holiday and be safe.
     
    NoelStephens_Unity likes this.