-
Notifications
You must be signed in to change notification settings - Fork 216
WorldPositionStays update #778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d054d51
78b6b24
a386484
06bba9b
823cb7d
baf4c99
d250be1
bf31d59
ab81136
c2b419f
614dcb3
56eb2a7
91bf0a6
b51d430
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,22 +4,31 @@ title: NetworkObject Parenting | |
description: A `NetworkObject` parenting solution within Netcode for GameObjects (Netcode) to help developers with synchronizing transform parent-child relationships of `NetworkObjects`. | ||
|
||
--- | ||
|
||
:::important Opt-OUT | ||
This feature is behind a bool flag that can be toggled on the `NetworkObject` inspector UI. It will be enabled by default but you can opt-out from it if you want to implement your own solution | ||
### Overview | ||
If you aren't completely familiar with transform parenting in Unity, then it is highly recommended to [review over the existing Unity documentation](https://docs.unity3d.com/Manual/class-Transform.html) before reading further. In order to properly synchronize all connected clients with any change in a `GameObject`'s transform parented status, Netcode for GameObjects (NGO) requires that the parent and child `GameObject`s have `NetworkObject` components attached to them. | ||
|
||
### Parenting Rules | ||
- Setting the parent of a child's `Transform` directly (i.e. `transform.parent = childTransform;`) will always use the default `WorldPositionStays` value of `true`. | ||
- It is recommended to always use the `NetworkObject.TrySetParent` method when parenting if you plan on changing the `WorldPositionStays` default value. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of this phrasing because it starts by saying that the user should always use the How does this sound?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that better too! |
||
- Likewise, it is also recommended to use the `NetworkObject.TryRemoveParent` method to remove a parent from a child. | ||
- When a server parents a spawned `NetworkObject` under another spawned `NetowrkObject` during a netcode game session this parent child relationship is replicated across the network to all connected and future late joining clients. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Side question: what replicates the parent-child relationship across all clients? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The server sends a ParentSyncMessage to all clients when a NetworkObject's parenting status changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that's a great idea! if you start a draft, I'm more than happy to help :) |
||
- If, while editing a scene, you place an in-scene placed `NetworkObject` under a `GameObject` that does not have a `NetworkObject` component attached to it, NGO will preserve that parenting relationship. | ||
- During runtime, this parent-child hierarchy will remain true unless user code removes the GameObject parent from the child NetworkObject. | ||
- _Note: Once removed, NGO will not allow you to re-parent the `NetworkObject` back under the same or another `GameObject` that with no `NetworkObject` component attached to it._ | ||
- You can perform the same parenting actions with in-scene placed `NetworkObject`s as you can with dynamically spawned `NetworkObject`s. | ||
- Only in-scene placed `NetworkObject`s can have multiple generations of nested `NetworkObject` children. | ||
- You can parent dynamically spawned `NetworkObject`s under in-scene placed `NetworkObject`s and vice versa. | ||
- To adjust a child's transform values when parenting or when removing a parent: | ||
- Override the `NetworkBehaviour.OnNetworkObjectParentChanged` virtual method within a `NetworkBehaviour` attached to the child NetworkObject. | ||
- When `OnNetworkObjectParentChanged` is invoked, on the server side, adjust the child's transform values within the overridden method. | ||
- NGO will then synchronize all clients with the child's parenting and transform changes. | ||
|
||
:::tip | ||
When a NetworkObject is parented, NGO will synchronize both the parenting information along with the child's transform values. NGO uses the `WorldPositionStays` setting to determine whether to synchronize the local or world space transform values of the child `NetworkObject`. This means that a `NetworkObject` does not require you to include a `NetworkTransform` component if it never moves around, rotates, or changes its scale when it is not parented. This can be useful for world items a player might pickup (i.e. parent the item under the player) and the item in question needs to be adjusted relative to the player when it is parented or the parent is removed (i.e. dropped). This helps to reduce the item's over-all bandwidth and processing resources consumption. | ||
::: | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
[`MonoBehaviour.OnTransformParentChanged()`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnTransformParentChanged.html) under `NetworkObject` is utilized to catch `transform.parent` changes. | ||
|
||
Three additional state variables are stored in `NetworkObject`: | ||
|
||
```csharp | ||
bool m_IsReparented; // did parent change compared to initial scene hierarchy? | ||
ulong? m_LatestParent; // who (NetworkObjectId) is our latest (current) parent if we changed our parent? | ||
Transform m_CachedParent; // who (Transform) was our previously assigned parent? | ||
``` | ||
|
||
`NetworkBehaviour` includes a virtual method you can override to be notified when a `NetworkObject`'s parent has changed: | ||
### OnNetworkObjectParentChanged | ||
[`NetworkBehaviour.OnNetworkObjectParentChanged`](https://docs-multiplayer.unity3d.com/netcode/current/api/Unity.Netcode.NetworkBehaviour#onnetworkobjectparentchangednetworkobject) is a virtual method you can override to be notified when a `NetworkObject`'s parent has changed. The [`MonoBehaviour.OnTransformParentChanged()`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnTransformParentChanged.html) method is used by `NetworkObject` to catch `transform.parent` changes and notify its associated `NetworkBehaviour`s. | ||
|
||
```csharp | ||
/// <summary> | ||
|
@@ -28,30 +37,10 @@ Transform m_CachedParent; // who (Transform) was our previously assigned parent? | |
virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } | ||
``` | ||
|
||
You need to consider two main code paths when synchronizing `NetworkObject` parenting: | ||
|
||
1. At Object Spawn | ||
- Client spawns objects including static scene objects and dynamic spawned objects on join. | ||
- Serialize `NetworkObject`s with their payloads (such as `NetworkBehaviour`s etc.) | ||
- Write `m_IsReparented` and `m_LatestParent` fields to sync on the client-side | ||
2. During Gameplay | ||
- When a server parents a spawned `NetworkObject` under another spawned `NetowrkObject` during a netcode game session this parent child relationship is replicated across the network to all connected clients. | ||
:::info | ||
The server will writes the `m_IsReparented` and `m_LatestParent` fields into a `NetworkBuffer` and sends a `PARENT_SYNC` message on the `MLAPI_INTERNAL` channel to all connected clients. | ||
:::caution Multi-Generation Children and Scale | ||
If you are dealing with more than one generation of nested children where each parent and child have scale values other than `Vector3.one`, then mixing the `WorldPositionStays` value when parenting and removing a parent will impact how the final scale is calculated! If you want to maintain the same values prior to parenting when removing a parent from a child, then you need to use the same `WorldPositionStays` value used when the child was parented. | ||
::: | ||
|
||
:::important | ||
Transform parent synchronization relies on the initial formation of transforms in the scene hierarchy being identical on all standalone instances. | ||
::: | ||
|
||
## NetworkObject Parenting Rules | ||
A few basic `NetworkObject` Parenting rules are listed below. | ||
|
||
:::warning Limiting Non-Networked NetworkObject Transform Parenting | ||
Rules outlined below are applied and enforced even while not networking (not hosting or connected). Specifically, if you were to try parenting a `NetworkObject` under a non-`NetworkObject`, that'd be invalid and reverted even though you are not hosting or connected to a server. | ||
::: | ||
|
||
|
||
### Only A Server (or A Host) Can Parent NetworkObjects | ||
Similar to [Ownership](../basics/networkobject#ownership), only the server (or host) can control `NetworkObject` parenting. | ||
|
||
|
@@ -60,27 +49,64 @@ If you run into a situation where your client must trigger parenting a `NetworkO | |
::: | ||
|
||
### Only Parent Under A `NetworkObject` Or Nothing (i.e. The Root or null) | ||
A `NetworkObject` can only be parented under another `NetworkObject`. The only exception is if you don't want the `NetworkObject` to have any parent. Under this case, you would parent to the root of the scene hierarchy (i.e. setting the `transform.parent` to `null`). | ||
|
||
:::info | ||
The `NetworkObject` requirement is primarily for identification purposes (i.e. knowing which GameObject's transform we are going to parent under). | ||
::: | ||
You can only parent a NetworkObject under another NetworkObject. The only exception is if you don't want the NetworkObject to have a parent. In this case, you would can remove the NetworkObject's parent by invoking `NetworkObject.TryRemoveParent`. If this operation succeeds, the the parent of the child will be set to `null` (root of the scene hierarchy). | ||
|
||
### Only Spawned NetworkObjects Can Be Parented | ||
A `NetworkObject` can only be parented if it is spawned and can only be parented under another spawned `NetworkObject`. This also means that `NetworkObject` parenting can only occur during a network session (netcode enabled game session). Think of `NetworkObject` parenting as a netcode event. In order for it to happen, you must have, at very minimum, a server or host instance started and listening. | ||
|
||
### Invalid `NetworkObject` Parenting Will Be Reverted | ||
If an invalid/unsupported `NetworkObject` parenting happens, Netcode will immediately revert it back to its original parenting status. | ||
### Invalid Parenting Actions Are Reverted | ||
If an invalid/unsupported `NetworkObject` parenting action occurs, the attempted parenting action will be reverted back to the `NetworkObject`'s original parenting state. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is simpler, less to read, and conveys the same point. |
||
**For example:** | ||
If you had a `NetworkObject` who's current parent was root and tried to parent it in an invalid way (i.e. under a GameObject without a `NetworkObject` component) then a warning message would be logged and the `NetworkObject` would revert back to having root as its parent. | ||
|
||
### In-scene NetworkObject parenting of players | ||
In-scene placed `NetworkObject` parenting of players requires the client to be synchronized first. Since a server can only perform parenting related actions, the server must have already received the `NetworkSceneManager` generated `SceneEventType.SynchronizeComplete` message before the server can parent the client's player `NetworkObject`. | ||
:::info | ||
For more information, see the "[Real World In-scene NetworkObject Parenting of Players Solution](inscene_parenting_player.md)". | ||
### In-scene Object Parenting and Player Objects | ||
If you plan on parenting in-scene placed `NetworkObject`s with a player `NetworkObject` when it is initially spawned, you need to wait until the client has finished synchronizing with the server first. Since parenting can only be performed on the server side, you should perform the parenting action only when the server has received the `NetworkSceneManager` generated `SceneEventType.SynchronizeComplete` message from the client that owns the player `NetworkObject` to be parented (as a child or parent). | ||
:::info For More Information | ||
- [Real World In-scene NetworkObject Parenting of Players Solution](inscene_parenting_player.md) <br /> | ||
- [Scene Event Notifications](../basics/scenemanagement/scene-events#scene-event-notifications) <br /> | ||
- [In-Scene NetworkObjects](../basics/scenemanagement/inscene-placed-networkobjects.md) | ||
::: | ||
|
||
### WorldPositionStays usage | ||
When using the `NetworkObject.TrySetParent` or `NetworkObject.TryRemoveParent` methods, the `WorldPositionStays` parameter is synchronized with currently connected and late joining clients. When removing a child from its parent, you should use the same `WorldPositionStays` value that was used to parent the child. More specifically when `WorldPositionStays` is set to false this applies, but if you are using the defalt value of `true` then this is not required (because it is the default). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah... that adjustment in the middle has a better flow to it. |
||
|
||
When the `WorldPositionStays` parameter in `NetworkObject.TrySetParent` is the default value of `true`, this will preserve the world space values of the child `NetworkObject` relative to the parent. However, sometimes you might want to only preserve the local space values (i.e. pick up an object that only has some initial changes to the child's transform when parented). Through a combination of `NetworkObject.TrySetParent` and `NetworkBehaviour.OnNetworkObjectParentChanged` you can accomplish this without the need for a `NetworkTransform`. To better understand how this works, it is important to understand the order of operations for both of these two methods: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are allowed to use NGO? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good question - one I've been wondering, too. In the gaming service documentation (e.g., Relay), we refer to Netcode for GameObjects as NGO (after introducing the acronym). Maybe that's not how we want to refer to it, though. Especially since we called it "netcode" everywhere else, this confused me a bit when I first started working with netcode. Do you know who might be able to say authoritatively what we should and shouldn't use to refer to netcode? |
||
|
||
**Server-Side** | ||
- `NetworkObject.TrySetParent` invokes `NetworkBehaviour.OnNetworkObjectParentChanged` | ||
- You can make adjustments to the child's position, rotation, and scale in the overridden `OnNetworkObjectParentChanged` method. | ||
- The ParentSyncMessage is generated, the transform values are added to the message, and the message is then sent. | ||
- ParentSyncMessage includes the child's position, rotation, and scale | ||
- Currently connected or late joining clients will be synchronized with the parenting and the child's associated transform values | ||
|
||
**When to use a NetworkTransform** <br /> | ||
If you plan on the child `NetowrkObject` moving around, rotating, or scaling independently (when parented or not) then you will still want to use a NetworkTransform. | ||
If you only plan on making a one time adjustment to the child `NetworkObject`'s transform when parented or having a parent removed, then the child does not need a `NetworkTransform` component. | ||
|
||
:::info For More Information | ||
- [Learn More About WorldPositionStays](https://docs.unity3d.com/ScriptReference/Transform.SetParent.html) | ||
::: | ||
|
||
### Network Prefabs, Parenting, and NetworkTransforms | ||
Since the `NetworkTransform` component synchronizes the transform of a `GameObject` (with a `NetworkObject` component attached to it), it can become tricky to understand the parent-child transform relationship and how that translates when synchronizing late joining clients. Currently, a network prefab can only have one `NetworkObject` component within on the root `GameObject` of the prefab. However, you can have a complex hierarchy of `GameObject`s nested under the root `GameObjet` and each child `GameObject` can have a `NetworkBehaviour` attached to it. Since a `NetworkTransform` synchronizes the transform of the `GameObject` it is attached to, you might be tempted to setup a network prefab like this: | ||
|
||
``` | ||
Network Prefab Root (GameObject with NetworkObject and NetworkTransform components attached to it) | ||
├─ Child #1 (GameObject with NetworkTransform component attached to it) | ||
│ | ||
└─ Child #2 (GameObject with NetworkTransform component attached to it) | ||
``` | ||
While this will not give you any warnings and, depending upon the transform settings of Child #1 & #2, it might appear to work properly (i.e. synchronizes clients, etc.), it is important to understand how the child `GameObject`, with no `NetworkObject` component attached to it, and parent `GameObject`, that does have a `NetworkObject` component attached to it, will be synchronized when a client connects to an already in-progress network session (i.e. late joins or late joining client). If Child #1 or Child #2 have had changes to their respective `GameObject`'s transform prior to a client joining, then upon a client late joining the two child `GameObject`'s transforms will not get synchronized during the initial synchronization period because they do not have `NetworkObject` components attached to them: | ||
|
||
``` | ||
Network Prefab Root (Late joining client is synchronized with `GameObject`'s current transform state) | ||
├─ Child #1 (Late joining client *is not synchronized* with `GameObject`'s current transform state) | ||
│ | ||
└─ Child #2 (Late joining client *is not synchronized* with `GameObject`'s current transform state) | ||
``` | ||
This *is important* to understand because the `NetworkTransform` component initializes itself, during `NetworkTransform.OnNetworkSpawn`, with the `GameObject`'s current transform state. Just below, in the parenting examples, we provide you with some valid and invalid parenting rules. As such, you should take these rules into consideration when using `NetworkTransform` components if you plan on using a complex parent-child hierarchy and should make sure to organize your project's assets where any children that have `NetworkTransform` components attached to them also have `NetworkObject` components attached to them to avoid late-joining client synchronization issues. | ||
|
||
## Parenting Examples | ||
|
||
### Simple Example: | ||
|
@@ -157,4 +183,10 @@ Vehicle (GameObject->NetworkObject) | |
├─Seat1 (GameObject->NetworkObject) | ||
│ └─Player (GameObject->NetworkObject) | ||
└─Seat2 (GameObject->NetworkObject) | ||
``` | ||
``` | ||
|
||
:::note Optional Auto Synchronized Parenting | ||
The Auto Object Parent Sync property of NetworkObject, enabled by default, allows you to disable automatic parent change synchronization in the event you want to implement your own parenting solution for one or more NetworkObjects. | ||
::: | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what this sentence is saying. Are you saying that if you directly set the parent of a child's
Transform
, theWorldPositionStays
value will always betrue
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.