Search Unity

Question Change The Player's Outfit Mesh

Discussion in 'Netcode for GameObjects' started by mmertbbulut, Jan 28, 2024.

  1. mmertbbulut

    mmertbbulut

    Joined:
    Nov 25, 2021
    Posts:
    13
    Hello everyone,

    I'm working on an online game, and I've sorted out the lobby and relay systems. I've also implemented the local costume change, and it works fine locally. However, things are not going as expected when we go online. When I connect with two different clients, on Player 1's screen, both characters wear Player 1's costume, and on Player 2's screen, both wear Player 2's costume. No matter how much I tried with ServerRpc and ClientRpc, I couldn't figure it out. Thanks in advance for your help. Here my code.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using Unity.Netcode;
    5. using UnityEngine;
    6.  
    7. public class Clothes : NetworkBehaviour
    8. {
    9.     [SerializeField] private Gender gender;
    10.     public bool isCostumeSellected;
    11.     private Dictionary<string, SkinnedMeshRenderer> Outfits = new Dictionary<string, SkinnedMeshRenderer>();
    12.  
    13.  
    14.     [SerializeField] private SkinnedMeshRenderer OutfitTshirtmesh;
    15.     [SerializeField] private SkinnedMeshRenderer OutfitHatmesh;
    16.     [SerializeField] private SkinnedMeshRenderer OutfitBeardmesh;
    17.     [SerializeField] private SkinnedMeshRenderer OutfitHairmesh;
    18.     [SerializeField] private SkinnedMeshRenderer OutfitPantsmesh;
    19.     [SerializeField] private SkinnedMeshRenderer OutfitCostumeTopMesh;
    20.     [SerializeField] private SkinnedMeshRenderer OutfitCostumeBottomMesh;
    21.     [SerializeField] private SkinnedMeshRenderer OutfitCostumeShoesMesh;
    22.     [SerializeField] private SkinnedMeshRenderer OutfitGlassesmesh;
    23.     [SerializeField] private SkinnedMeshRenderer OutfitMaskmesh;
    24.     [SerializeField] private SkinnedMeshRenderer OutfitShoesmesh;
    25.  
    26.     private void Awake()
    27.     {
    28.  
    29.         InitilizeSkinnedMesh();
    30.  
    31.         if (GameManager.Instance.playerData.gender != gender)
    32.         {
    33.             transform.gameObject.SetActive(false);
    34.         }
    35.     }
    36.     private void Start()
    37.     {
    38.         // this.isCostumeSellected = gender == Gender.Famele && GameManager.Instance.IsFameleIsCostume();
    39.         //this.isCostumeSellected = Gender.Male == gender && GameManager.Instance.IsMaleIsCostume();
    40.  
    41.         GameManager.Instance.IsCostumeSellected();
    42.         ChangeMeshToSellectedMesh();
    43.     }
    44.  
    45.     private void InitilizeSkinnedMesh()
    46.     {
    47.         if (OutfitTshirtmesh != null) Outfits.Add(OutfitType.TShirt.ToString(), OutfitTshirtmesh);
    48.         if (OutfitHatmesh != null) Outfits.Add(OutfitType.Hat.ToString(), OutfitHatmesh);
    49.         if (OutfitPantsmesh != null) Outfits.Add(OutfitType.Pants.ToString(), OutfitPantsmesh);
    50.         if (OutfitMaskmesh != null) Outfits.Add(OutfitType.Mask.ToString(), OutfitMaskmesh);
    51.         if (OutfitShoesmesh != null) Outfits.Add(OutfitType.Shoes.ToString(), OutfitShoesmesh);
    52.         if (OutfitHairmesh != null) Outfits.Add(OutfitType.Hair.ToString(), OutfitHairmesh);
    53.         if (OutfitBeardmesh != null) Outfits.Add(OutfitType.Beard.ToString(), OutfitBeardmesh);
    54.         if (OutfitCostumeTopMesh != null) Outfits.Add(OutfitType.Costume.ToString(), OutfitCostumeTopMesh);
    55.         if (OutfitGlassesmesh != null) Outfits.Add(OutfitType.Eyeglasses.ToString(), OutfitGlassesmesh);
    56.     }
    57.  
    58.     public void ChangeMeshToSellectedMesh()
    59.     {
    60.         List<CustomizationSO> maleList = ScriptibleObjectManager.instance.ReturnMaleList();
    61.         List<CustomizationSO> fameleList = ScriptibleObjectManager.instance.ReturnFameleList();
    62.         List<CustomizationSO> combineList = maleList.Concat(fameleList).ToList();
    63.  
    64.         for (int i = 0; i < combineList.Count; i++)
    65.         {
    66.             foreach (var mesh in Outfits)
    67.             {
    68.  
    69.                 if (mesh.Key == OutfitType.Costume.ToString() && combineList[i].gender == gender && isCostumeSellected)
    70.                 {
    71.                     ChangeMeshEnableForCostume(false);
    72.  
    73.                     mesh.Value.sharedMesh = combineList[i].itemMesh;
    74.                     mesh.Value.material = combineList[i].itemMaterial;
    75.                     OutfitCostumeBottomMesh.sharedMesh = combineList[i].itemMeshCustomeBottom;
    76.                     OutfitCostumeBottomMesh.material = combineList[i].itemCustomeBottomMaterial;
    77.  
    78.                     OutfitCostumeShoesMesh.sharedMesh = combineList[i].itemMeshCustomeShoes;
    79.                     OutfitCostumeShoesMesh.material = combineList[i].itemCustomeShoesMaterial;
    80.                 }
    81.                 else if (mesh.Key == combineList[i].outfitType.ToString() && mesh.Key != OutfitType.Costume.ToString() && combineList[i].gender == gender)
    82.                 {
    83.                     mesh.Value.sharedMesh = combineList[i].itemMesh;
    84.                     mesh.Value.material = combineList[i].itemMaterial;
    85.                 }
    86.             }
    87.         }
    88.     }
     
  2. mo1ok

    mo1ok

    Joined:
    Mar 26, 2016
    Posts:
    5
    Hey mmertbbulut,

    What's probably happening here is that the code to equip the outfit is running on both player objects when the game is instantiated.

    There's two ways to solve this:

    1. Wrap the initializing code around a network check to make sure it only runs for appropriate player.

    Something like:

    ```
    if (GetComponent<NetworkObject>().IsLocalPlayer) {
    // only run equip code if the player object is the person currently playing the game
    }
    ```

    However, you might run into issues where the outfit looks fine for yourself, but perhaps the other player is naked because it doesn't know what oufit they are supposed to be wearing.

    So what I would recommend instead is

    2. Storing outfit information as a Network Variable that maps to your outfit dictionary.
    Code (CSharp):
    1.  
    2. private NetworkVariable<string> currentlyEquippedPants = new NetworkVariable<string>("jeans");
    3.  
    4. [ServerRpc]
    5. equipOutfitServerRpc(string outfitName)
    6. {
    7.   currentlyEquippedPants .Value = outfitName; // string of the outfit name
    8.   equipOutfitClientRpc();
    9. }
    10.  
    11. [ClientRpc]
    12. equipOutfitClientRpc()
    13. {
    14.   // take string from network value, use that to do normal init
    15.   OutfitPantsmesh = Outfits[currentlyEquippedPants .Value]
    16.   // do initialization
    17. }
    18.  

    With network variables, all state will be kept track on the server. You don't need to do any work to synchronize client/server variables. However, when you update the network variable, you must do it through a server RPC.


     
  3. mmertbbulut

    mmertbbulut

    Joined:
    Nov 25, 2021
    Posts:
    13

    Hello mo1ok,

    I'll try out the suggestion you wrote as soon as possible and get back to you. Thanks for your effort and information.
     
  4. mmertbbulut

    mmertbbulut

    Joined:
    Nov 25, 2021
    Posts:
    13
    Hey again,

    That's not work for me. Im getting error about Serialization Unity.Mesh

    Code (CSharp):
    1. ArgumentException: Serialization has not been generated for type UnityEngine.Mesh. This can be addressed by adding a [GenerateSerializationForGenericParameterAttribute] to your generic class that serializes this value (if you are using one), adding [GenerateSerializationForTypeAttribute(typeof(UnityEngine.Mesh)] to the class or method that is attempting to serialize it, or creating a field on a NetworkBehaviour of type NetworkVariable. If this error continues to appear after doing one of those things and this is a type you can change, then either implement INetworkSerializable or mark it as serializable by memcpy by adding INetworkSerializeByMemcpy to its interface list to enable automatic serialization generation. If not, assign serialization code to UserNetworkVariableSerialization.WriteValue, UserNetworkVariableSerialization.ReadValue, and UserNetworkVariableSerialization.DuplicateValue, or if it's serializable by memcpy (contains no pointers), wrap it in ForceNetworkSerializeByMemcpy`1.
    2. Unity.Netcode.FallbackSerializer`1[T].ThrowArgumentError () (at
    Anyone can help me about that ?
     
  5. Nyphur

    Nyphur

    Joined:
    Jan 29, 2016
    Posts:
    98
    Sounds like you've tried to send the actual mesh as a parameter in the RPC method. You can't do that, you can only send simple types like an int or string. The best approach is to have an array of meshes and then send the index of the mesh you want as an int.
     
  6. mmertbbulut

    mmertbbulut

    Joined:
    Nov 25, 2021
    Posts:
    13
    I have a problem now. When I apply the code below, meaning when I get the Mesh of the SkinnedMeshRenderer corresponding to the Tshirt from the Outfits dictionary (Top_Skin_3), there is no change, and I cannot achieve the desired result.



    Code (CSharp):
    1.     [ServerRpc]
    2.     public void EquipOutfitServerRpc()
    3.     {
    4.         EquipOutfitClientRpc();
    5.     }
    6.  
    7.     [ClientRpc]
    8.     public void EquipOutfitClientRpc()
    9.     {
    10.         OutfitTshirtmesh.sharedMesh = OutfitTshirtmesh.sharedMesh;
    11.     }
    Even if I modify the code and assign the Mesh of the SkinnedMeshRenderer corresponding to the Tshirt from the Outfits dictionary (Top_Skin_3) to a newly opened Mesh variable named testMesh, and then apply it as below, there is still no change, and I cannot achieve the desired result.




    Code (CSharp):
    1.   public override void OnNetworkSpawn()
    2.     {
    3.  
    4.         TestMesh = OutfitTshirtmesh.sharedMesh;
    5.  
    6.         NetworkManager.Singleton.OnClientConnectedCallback += LoadingComplate;
    7.     }
    Code (CSharp):
    1.     [ServerRpc]
    2.     public void EquipOutfitServerRpc()
    3.     {
    4.         EquipOutfitClientRpc();
    5.     }
    6.  
    7.     [ClientRpc]
    8.     public void EquipOutfitClientRpc()
    9.     {
    10.  
    11.         OutfitTshirtmesh.sharedMesh = TestMesh;
    12.     }
    However, if I manually assign the mesh (Top_Skin_3) to the testMesh variable from the inspector and then run the code, I get exactly the result I want. What could be the reason for this, and how can I overcome it?



    [ServerRpc]
    public void EquipOutfitServerRpc()
    {
    EquipOutfitClientRpc();
    }

    [ClientRpc]
    public void EquipOutfitClientRpc()
    {

    OutfitTshirtmesh.sharedMesh = TestMesh;
    }
     
  7. mmertbbulut

    mmertbbulut

    Joined:
    Nov 25, 2021
    Posts:
    13
    Still I have same problem please someone can help me
     
  8. Nyphur

    Nyphur

    Joined:
    Jan 29, 2016
    Posts:
    98
    " OutfitTshirtmesh.sharedMesh = OutfitTshirtmesh.sharedMesh;"

    This code does nothing, you're setting a variable equal to itself. Your RPCs aren't sending the name or index of the mesh you're looking for, and they aren't looking it up.
     
  9. mmertbbulut

    mmertbbulut

    Joined:
    Nov 25, 2021
    Posts:
    13
    I did your way but runtime mesh wont change.
     
  10. Nyphur

    Nyphur

    Joined:
    Jan 29, 2016
    Posts:
    98
    Nobody can help you if you don't post your code.