As a semi-relative side note on some of this discussion:
You can have a complex parent-child hierarchies with nested NetworkBehaviours under a single NetworkObject. NGO v1.2.0 (now) properly synchronizes NetworkTransforms within complex parent-child hierarchies (Just thought that was worth mentioning).
Regarding parenting, if you want a “bare-bones” example to look at, in the testproject within the NGO repository you can find several additional samples under Assets\Tests\Manual. I put together manual tests for visual testing prior to writing integration tests.
One of those manual tests you may (or may not) find useful to look over is:
Assets\Tests\Manual\InSceneObjectParentingTests
Within that manual test folder you will find ChildObjectScript.cs which provides a very basic example.
I was using this for testing of the WorldPositionStays parameter, but it could also be used as a “bare-bone” parenting reference.
Regarding dropping an item you picked up by using null, there are really two ways I would recommend doing this:
Use NetworkObject.TryRemoveParent: This is a helper method really to automatically handle the casting of a null value to NetworkObject:
public bool TryRemoveParent(bool worldPositionStays = true)
{
return TrySetParent((NetworkObject)null, worldPositionStays);
}
The second way would be calling TrySetParent just like it does in the TryRemoveParent script snippet above.
If you are not concerned about world or local space relative parenting (i.e. “ok” with it to default to world space relative), then you should be able to just directly set the transform of the GameObject to null…otherwise using TrySetParent and TryRemoveParent allows NGO to know how to synchronize the parenting action (world or local space relative).
Advanced Parenting:
One way to handle parenting and be free of the NetworkObject parenting restrictions once parented, is to do the NGO parenting first and then apply normal GameObject parenting once parented.
For Example:
You have a player and item you want to pickup, but you want to be able to parent under a GameObject (with no NetworkObject component) that is nested deep within a model (common issue one might run into).
Assume the RootPlayer and ItemToPickup both have NetworkObject components and any of their respective children could have NetworkBehaviour
components (but no NetworkObject components).
Step 1: Setup a NetworkBehaviour similar to the ChildObjectScript.cs with the only difference is you have a public property that points to the “ItemVisuals” GameObject and have a way to know about the “RightHand”. There are several ways to handle this, one way is to use an interface and make one of the components of the RootPlayer implement it so you have a method to be able to easily obtain the target visual parenting node.
public interface PlayerVisualNodeHandler
{
GameObject GetRightHand();
}
Something along those lines…
Step 2: Do the initial parenting using NGO so this parenting action is synchronized with clients and OnNetworkObjectParentChanged
is invoked (both server and client sides)
Step 3: Within OnNetworkObjectParentChanged
you can then do normal “Unity style” (like you would in a single player game) parenting of the “root visual” under the player’s “hand” GameObject
. Both the server and client sides would invoke this portion of the script.
The end result would look like this:
Since the ItemToPickup children’s NetworkBehaviour components are already registered with the ItemToPickup NetworkObject component, NGO will already know how to route messages for RPCs and handle NetworkVariable synchronization. Where the ItemToPickup’s (non-NetworkObject) children exist after it is spawned shouldn’t matter once it is spawned and parented under something.
When the player drops the item you can follow the same steps but in reverse:
Step1: Migrate ItemVisuals back under ItemToPickup.
Step2: Remove the parent of ItemToPickup using TryRemoveParent (or the like).
This approach does require a few more steps than one might have to run through than when making a single player game, but then again it is a bit more complex because there is the added complexity of synchronizing clients with specific states/actions.
However, I felt it might be useful to provide you with this alternate (albeit a bit more complex) approach.