Search Unity

Resolved Player hierarchical networkobjects

Discussion in 'Netcode for GameObjects' started by randany, Dec 3, 2021.

  1. randany

    randany

    Joined:
    Apr 20, 2021
    Posts:
    5
    Hello!
    I'm trying to have a player with a structure much similar to the one shown here
    https://docs-multiplayer.unity3d.com/docs/advanced-topics/networkobject-parenting.

    upload_2021-12-3_14-16-2.png

    Just changing the player prefab to this hierarchy gives the follow warning and error:
    [Netcode] NetworkPrefab "Player" has child NetworkObject(s) but they will not be spawned across the network (unsupported NetworkPrefab setup).
    [Netcode] NetworkPrefab hash was not found! In-Scene placed NetworkObject soft synchronization failure for Hash: 2021925118!
    I did read that parented networksobjects can't be instantiated at runtime and must be parented manually but that raises the question. If I must create own networkobjects for the hands and then parent them to the player to achieve this structure? Or is there another better way around this?
     
  2. luke-unity

    luke-unity

    Joined:
    Sep 30, 2020
    Posts:
    306
    For runtime spawned objects and player objects you would have to spawn each NetworkObject in the hierarchy separately and reparent them. So for your player object you could have a script which spawns two additional hand objects when the player gets spawned and reparents them under the player.

    Objects which are already in the scene support nested hierarchies of NetworkObjects.
     
    choandepzai321 and randany like this.
  3. DA-PaoPao

    DA-PaoPao

    Joined:
    Aug 13, 2021
    Posts:
    10
    I am struggling with the same problem.
    In my case the hands are a bone of the main player object, so I cant spawn them separately.
    Also how would I reparent the hand objects under the arms, because the arms are not NetworkObjects and therefore parenting to them would not be allowed.
    Thanks in advance.
     
  4. randany

    randany

    Joined:
    Apr 20, 2021
    Posts:
    5
    The way I did it was to create a new empty networkobject "Hand" with only a transform and a networkobject. This object I then instantiated and spawned as a child of respective hand bone via script. Finally I could parent any weapon or alike to the new Hand networkobject. This all is done during runtime. Hope it helped :)
     
  5. DA-PaoPao

    DA-PaoPao

    Joined:
    Aug 13, 2021
    Posts:
    10
    Hi randany, thanks for your post.
    I tried this, but how do you parent the "Hand" object to the bone transform of the hand.
    When I try it gives me:
    InvalidParentException: Invalid parenting, NetworkObject moved under a non-NetworkObject parent.
    :(
     
  6. randany

    randany

    Joined:
    Apr 20, 2021
    Posts:
    5
    upload_2021-12-11_15-13-45.png

    This is the parenting I did on Awake in a script attached to the player. It's probably not ideal but it worked for my case.
     
  7. DA-PaoPao

    DA-PaoPao

    Joined:
    Aug 13, 2021
    Posts:
    10
    thanks, that worked :)
     
  8. DA-PaoPao

    DA-PaoPao

    Joined:
    Aug 13, 2021
    Posts:
    10
    On second thought, it doesn't seem to work correctly after all. :(
    I don't know if you also have this. But if I run as host everything is fine and the hand objects are attached to the actual hand bone, but if I run as client (another build is running as host), the hand objects are not attached to the actual player objects. See the image below. 2 player objects and the hand objects are attached to the root.

    upload_2021-12-11_18-48-43.png

    This is probably because the NetworkObjects have not been properly Parented using TrySetParent(GameObject, Boolean).

     
  9. randany

    randany

    Joined:
    Apr 20, 2021
    Posts:
    5
    Yes you are right, I seem to have the same issue. I'll try the TrySetParent function. If I come up with a solution I will let you know.
     
  10. DA-PaoPao

    DA-PaoPao

    Joined:
    Aug 13, 2021
    Posts:
    10
    TrySetParent won't work, because the hand is not a NetworkObject, and you'll get InvalidParentException: Invalid parenting, NetworkObject moved under a non-NetworkObject parent. :(
     
  11. Bimaokai

    Bimaokai

    Joined:
    Jul 21, 2017
    Posts:
    19
    any news to that? I have a egg/chicken problem. My Structure has bones like yours.
    When i want to add a NetworkObject on the bone it tells me the following
    Code (CSharp):
    1. Invalid parenting, NetworkObject moved under a non-NetworkObject parent
    When i try to instantiate a bone NetworkObject to the bone and even when i add it to the NetworkManager it tells me the following:
    Code (CSharp):
    1. NetworkPrefab could not be found. Is the prefab registered with NetworkManager?
    I am really confused how to do this porperly.
     
  12. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    I'm having the same problem. I'd like to parent networkobjects to my players' hands and back during gameplay, but then these need to be networkobjects themselves. But if they are, I have to spawn the relevant bones separately when spawning my players, and I get the same error for non-server clients as Bimaokai.

    Given that this is a very common use case that is even used as an example on the Unity docs (see OP), there has to be a way to do this – but how?
     
  13. Nocazone

    Nocazone

    Joined:
    Dec 7, 2021
    Posts:
    20
    Kind of bit my tooth out on this and I found some kind of workaround. See the last post of this:
    Help Wanted - Reparent player to networkObject - Unity Forum

    I could not instantiate all child objects in my player dynamically because they are imported from blender and I don't want to tear that apart.
     
  14. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    I had a look at that post; am I correct in understanding that you don't actually parent a networkobject to your player's hand, but instead parent a non-networkobject that is on a networked list? Because if that's the case it unfortunately doesn't apply to a case like mine, where the picked-up item has to be networked itself.
     
  15. Nocazone

    Nocazone

    Joined:
    Dec 7, 2021
    Posts:
    20
    I wanted the same like you, parenting a NetworkObject (Weapon) to the hand of the player which is on top root a NetworkObject itself. I got always the problem, that the parent should also be a NetworkObject Spawned by the Server, and that is not applicable as i do not instanitate all of the bone transforms dynamically.
    But I am sorry that is not a valuable option for you. If you have a solution for the parenting problem just let me know, would be cool to do it right.
     
  16. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    @luke-unity Is there any news on how to achieve a networkobject with a child networkobject attached to it, like the documentation example? This is really a bottleneck in my project right now and I'd love a workaround to get to this state somehow.
     
  17. MaxYari

    MaxYari

    Joined:
    Feb 20, 2018
    Posts:
    13
    Same, this is really confusing and an obvious use case, how in the world are we suppose to do it?

    Update: Oh actually @Nocazone post that he linked is helpfull.
    [DOESNT WORK] @CosmoM are you _sure_ that you need you weapon (i assume) object to have NetworkObject on it? Afaik it will be networked (in a sense that it will be synced and you can use networktransform on it and such) since your parent object has NetworkObject on it and that's enough to make all children networkable.
    In case you need a NetworkObject on your weapon before parenting, perhaps you can just removed NetworkObject component through ServerRpc before parenting, or if that will not work
    - use copy of this object without NetworkObject attached and figure some way to cross-reference them (like store a reference to non-NetworkObject prefab inside of your NetworkObject prefab).
    Some of that should work.

    Update 2: So i've tried most of what i described above (what is now marked in gray) and it doesn't really work, for some reason even if one removes network object from game object - it's not possible to instantly parent it - there's some delay before server will alow it, it must be possible to somehow catch this delay but I figured a bit better and less hacky solution, in essence:

    1. Keep you player as is, with deep nested weapon sockets and all
    2. Upon player spawn create network object sockets and parent them directly to your network object player (not deep nested).
    3. Now on client side on these network object sockets use a script that will follow position and rotation of deep nested sockets
    4. Parent your network object weapons to these new network object sockets. This way we'll avoid "can't parent to non network object" since all of the network objects be parented directly to network objects
    5. Profit.
     
    Last edited: Apr 19, 2022
  18. crossfirealt22

    crossfirealt22

    Joined:
    Sep 24, 2017
    Posts:
    2
    @luke-unity
    Is there any news on how to achieve a networkobject with a child networkobject attached to it, like the documentation example?
     
  19. mrstruijk

    mrstruijk

    Joined:
    Jan 24, 2018
    Posts:
    52
  20. JohnPoliakov

    JohnPoliakov

    Joined:
    May 20, 2021
    Posts:
    10
  21. CreativeChris

    CreativeChris

    Unity Technologies

    Joined:
    Jun 7, 2010
    Posts:
    457
    The docs have been updated with more examples and information regarding expected behaviour. Can you clarify what the issue is and then, please log an issue.
     
  22. Safemilk

    Safemilk

    Joined:
    Dec 14, 2013
    Posts:
    42
    I feel like I'm missing something here... whenever I spawn something it auto de parents, so then re parenting is impossible because the next thing up the chain doesn't have a network object on it.

    Is the solution really to upwards recursively do this till I hit the root? That seems pretty wild.

    I've read the docs and I'm just blanking on this. Anyone make any progress on this?

    Failing that, has there been any further thinking around this workflow and how NGO might try to support it?

    Or are we all literally just... missing some obvious point.

    I use free floating bones for attach points for a multitude of reasons and benefits and I'd love to continue using this workflow in a networked game.
     
    firebird721 and Nyphur like this.
  23. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    Hi @Safemilk , have you already had a look at the examples in the official ClientDriven sample? It shows how to parent/unparent objects.
    If not, you can download it here .
     
    Mj-Kkaya likes this.
  24. Nyphur

    Nyphur

    Joined:
    Jan 29, 2016
    Posts:
    98
    I think there may be some misunderstanding of the problem people are having in this thread (and several other threads I found on the same topic). The ClientDriven sample doesn't contain an example of this parenting problem, it has picked up NetworkObjects that get parented to the player's root and float above their heads in a fixed local offset. Real games don't do this, they would parent an object to a hand transform that's deep in the skeleton hierarchy of a spawned player, which is what the OP was trying to do.

    • You can't use this technique to pick up a NetworkObject because the hand transform itself isn't a NetworkObject
    • You can't just make the hand transform a NetworkObject because all NetworkObjects must be parented to other NetworkObjects.
    • You can't just add NetworkObject components to the whole hierarchy chain from the root to the hand because Unity doesn't spawn the child NetworkObjects from a prefab (such as the player).
    • You can't recursively add the NetworkObject components to the hierarchy chain at runtime because by design NetworkObject spawning is all server-side and will only spawn prefabs that are registered with the NetworkManager. So this will work on the server, but the clients will never get the NetworkObjects added.
    Essentially the NetworkObject spawn system is designed to only spawn prefabs, but we can't have nested NetworkObjects in prefabs and we can't set it up at runtime. That means the only place you can ever parent a NetworkObject to on a spawned-in player is the root object, so the use case of a player picking up a NetworkObject from the game world and holding it in a hand isn't actually possible with NGO. We have to fake it.

    That's all fine, but even the complex examples in the documentation don't cover this use case and it's an extremely common one. The ClientDriven sample also side-steps these problems rather than implementing solutions -- things like the picked up items hovering in a fixed position above the player's head and the fact they're so large that you don't really notice the position discrepancy from the server having authority over their transforms. It makes it seem like the sample is a good representation of an interactive item pickup system, but really BossRoom is the better sample to look at for that.

    I did solve this, will post my solution in a second.
     
    tonyomendoza likes this.
  25. Nyphur

    Nyphur

    Joined:
    Jan 29, 2016
    Posts:
    98
    Yeah, after trying a lot of different approaches I finally solved it by basically faking it. We have to work within the constraints of NGO so we can only parent the picked up item to the player's root, so from there we need to use a different method to put it visually in the player's hand. DemoRoom does it by activating/deactivating a PositionConstraint but you can do it with setting the transform position and rotation manually too.

    The big problem you'll run into is keeping the bone and item positions in sync for all observers. The ClientDriven sample has client-authoritative movement and animation using ClientNetworkAnimator and ClientNetworkTransform to keep movement feeling responsive for players and then uses server-authority for items. But neither server nor client authority will work for a picked up item because there's no guarantee that a given bone is in the same place for any two observers.

    You need the item visuals to be basically not reliant on any networking aside from the picking up and dropping code. You just have to completely stop syncing the transform while the item is held, because even if you give the client authority over the position it'll only look right for the specific client that's setting the transform. Every other observer will see it wrong.

    The outline of my solution (tested with the ClientDriven sample) is:
    1. The item pickup and drop is still all server-authoritative using serverRpcs like in the ClientDriven sample. The server should parent the picked up NetworkObject to the root of the player who picked it up (that's the only place you CAN put it).
    2. Create a regular old monobehavior script on the items that you're picking up. That script's sole job is to detect when it's been parented to a player, search the player for the hand bone or transform you want to use, and then keep the item's transform.position and transform.rotation matching the hand bone's position and rotation. A PositionConstraint may work here too, I just set the position and rotation in an update loop.
    3. In that script, on the first frame that an item is picked up you want to fetch its NetworkTransform and disable it. On the frame that the item is dropped, re-enable the NetworkTransform.
    The result is that the server is authoritative for when items can be picked up and can manage ownership and such, but while an item is being held its transforms immediately stop being synced. It doesn't matter if you're the server, the client, or a client looking at another player -- All items automatically snap to the hand bone of whatever player object they are parented to. All observers see the item correctly snapped to their own local position for the hand bone.

    Once the item is dropped, its current position according to the server starts getting sent out to everyone again. Worst case is if the client who drops the item is moving and slightly out of sync they may see the item jump slightly when they drop it.
     
  26. firebird721

    firebird721

    Joined:
    Jun 8, 2022
    Posts:
    101
    thank you so much - i wasted so much on it on you just got the problem correct - thanx!
     
    tonyomendoza and Nyphur like this.
  27. AlucardVergil

    AlucardVergil

    Joined:
    Jul 7, 2022
    Posts:
    2
    For anyone still having this problem, i came to provide my solution to the problem which works great for me even though Nyphur already gave a good solution too.

    The way i solved it was, instead of spawning the object with Spawn(), i abandoned this idea altogether and just got the senderID inside the ServerRpc and using that i then got the networkObjectID of the player object who called the serverRpc and then i called a ClientRpc. In the clientRpc i used the networkObjectID to get the playerObject of the client who called the serverRpc and instantiated the sword, spell or whatever in their hand as a simple GameObject and parented it to the hand for each client individually.

    Not sure but i think this is better performance-wise because you just instantiate it and parent it once for each client instead of syncing the position constantly in the update.


    For example this is to spawn and parent a spellWarmUp effect in the player's hand, right before they fire it:

    Code (CSharp):
    1.  
    2. if (NetworkManager.Singleton.ConnectedClients.TryGetValue(senderClientId, out NetworkClient targetClient))
    3. {            
    4.                 SpellWarmUpClientRpc(targetClient.PlayerObject.NetworkObjectId);
    5. }
    6.  

    Code (CSharp):
    1.  
    2. [ClientRpc]
    3. private void SpellWarmUpClientRpc(ulong targetNetPlayerObjId)
    4. {
    5.         Transform senderFirepoint = GetNetworkObject(targetNetPlayerObjId).GetComponent<ProjectileShooter>().firePoint;
    6.  
    7.         var warmUpObj = Instantiate(warmUp, senderFirepoint.position, Quaternion.identity);
    8.         warmUpObj.transform.parent = senderFirepoint;
    9. }
    10.  

    Hope this helps! Cheers
     
    choandepzai321 and RINNUXEI like this.
  28. silviu-georgian77

    silviu-georgian77

    Joined:
    Jan 17, 2016
    Posts:
    12
    In case the server does all the logic and the client just renders, I just did the following:
    1. Disabled the
      NetworkObject, NetworkTransform, NetworkRigidBody
      components on the object that needed reparenting
    2. Reparented what needed to be reparented
    3. Enabled what was disabled in the first step
    Here is a class that I use:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Netcode;
    3. using UnityEngine;
    4. #if UNITY_EDITOR
    5. using UnityEditor;
    6. #endif
    7.  
    8. [ExecuteInEditMode]
    9. public class NetworkBehavioursEnabler : MonoBehaviour
    10. {
    11.     [SerializeField]
    12.     private List<MonoBehaviour> networkBehaviours = new List<MonoBehaviour>();
    13.     public List<MonoBehaviour> NetworkBehaviours
    14.     {
    15.         get
    16.         {
    17.             return networkBehaviours;
    18.         }
    19.     }
    20.  
    21.     [SerializeField]
    22.     private bool areNetworkBehavioursEnabled;
    23.     public bool AreNetworkBehavioursEnabled
    24.     {
    25.         get
    26.         {
    27.             return areNetworkBehavioursEnabled;
    28.         }
    29.         set
    30.         {
    31.             areNetworkBehavioursEnabled = value;
    32.             SetNetworkBehavioursEnabled(value);
    33.         }
    34.     }
    35.  
    36.     [SerializeField]
    37.     private bool executeOnAwake;
    38.  
    39.     [SerializeField]
    40.     private bool lookInParents;
    41.  
    42.     [SerializeField]
    43.     private bool autoFetchComponents;
    44.  
    45.     private void Awake()
    46.     {
    47.         ManageAutoFetch();
    48.         if (executeOnAwake)
    49.         {
    50.             SetNetworkBehavioursEnabled(areNetworkBehavioursEnabled);
    51.         }
    52.     }
    53.  
    54. #if UNITY_EDITOR
    55.     private void OnEnable()
    56.     {
    57.         EditorApplication.update += Update;
    58.     }
    59.  
    60.     private void OnDisable()
    61.     {
    62.         EditorApplication.update -= Update;
    63.     }
    64.  
    65.     private void Update()
    66.     {
    67.         ManageAutoFetch();
    68.     }
    69. #endif
    70.  
    71.     private void ManageAutoFetch()
    72.     {
    73.         if (autoFetchComponents)
    74.         {
    75.             FetchComponents();
    76.             autoFetchComponents = false;
    77.         }
    78.     }
    79.  
    80.     public void FetchComponents()
    81.     {
    82.         networkBehaviours.Clear();
    83.         networkBehaviours.AddRange(
    84.                 GetComponentsInChildren<NetworkBehaviour>()
    85.             );
    86.         networkBehaviours.AddRange(
    87.             GetComponentsInChildren<NetworkObject>()
    88.         );
    89.         if (lookInParents)
    90.         {
    91.             var tempNetworkBehaviours = new List<MonoBehaviour>();
    92.             tempNetworkBehaviours.AddRange(
    93.                 GetComponentsInParent<NetworkBehaviour>()
    94.             );
    95.             tempNetworkBehaviours.AddRange(
    96.                 GetComponentsInParent<NetworkObject>()
    97.             );
    98.             foreach (var tempNetworkBehviour in tempNetworkBehaviours)
    99.             {
    100.                 if (!networkBehaviours.Contains(tempNetworkBehviour))
    101.                 {
    102.                     networkBehaviours.Add(tempNetworkBehviour);
    103.                 }
    104.             }
    105.         }
    106.  
    107. #if UNITY_EDITOR
    108.         EditorUtility.SetDirty(this);
    109. #endif
    110.     }
    111.  
    112.     private void SetNetworkBehavioursEnabled(bool enabled)
    113.     {
    114.         foreach (var networkBehaviour in networkBehaviours)
    115.         {
    116.             if (networkBehaviour == null)
    117.             {
    118.                 continue;
    119.             }
    120.             if (networkBehaviour.enabled != enabled)
    121.             {
    122.                 networkBehaviour.enabled = enabled;
    123.             }
    124.             if (networkBehaviour is NetworkObject networkObject)
    125.             {
    126.                 if (networkObject.AutoObjectParentSync != enabled)
    127.                 {
    128.                     networkObject.AutoObjectParentSync = enabled;
    129.                 }
    130.             }
    131.         }
    132.     }
    133. }
    134.  
    In the editor, place this on a network object, and click:
    autoFetchComponents
    .
    Then, at runtime, modify
    AreNetworkBehavioursEnabled
    .

    Important: After the session has ended, make sure to manually reset all the parents to what they were before the session started.

    Edit: Tested this solution only in the case where all network objects are already instantiated in the scene before the game starts.
     
    Last edited: Aug 1, 2023
  29. Eigulite

    Eigulite

    Joined:
    May 13, 2022
    Posts:
    60
    This will work only for players that were in the server at the moment the weapon instantiated. So anyone that joins the server after the weapon instantiated they won't instantiate the weapon. If you got any ideas to go around that I'd like to know.
     
  30. Eigulite

    Eigulite

    Joined:
    May 13, 2022
    Posts:
    60
    I actually fixed this issue where whenever a new player joins it doesn't spawn the weapon for them. The solution is really complicated btw but it works like a champ. You'll have to get a NetworkList and store the weapon/spell in there through server whenever you instantiate the weapon on ClientRpc.You'll have to serialize it but since you can't serialize GameObjects or Transform you'll have to serialize the ID of the client that spawned the weapon. That way you can find the hand that you want to instantiate the weapon at. Call an OnClientConnected() method by subscribing to it on OnNetworkSpawn() and in OnClientConnected make sure to prevent servers from interfering by doing if(IsServer) return; in the beginning then after that do if(IsClient) and loop through the networkList that contains all the instantiated weapons to instantiate every single weapon that was instantiated before. No problem!
     
  31. silviu-georgian77

    silviu-georgian77

    Joined:
    Jan 17, 2016
    Posts:
    12
    I glad it worked out for you. I forgot to mention that I tested the above solution in a game where there are no instantiations. Everything is already spawned in the scene when the game starts.
     
  32. Afterglow375

    Afterglow375

    Joined:
    Apr 10, 2021
    Posts:
    2
    Newcomer to NGO and networking overall and immediately had the problem described by OP. Thanks for the solutions. Not gonna lie, the fact that a player hierarchy this commonplace needs a workaround seems strange to me.
     
    firebird721 likes this.