Dynamic containers
Create a container that moves with a networked entity, transfer its contents, or assign its interior to another worker.
Use a dynamic container for a moving space that can contain other networked entities. Examples include a ship interior, train car, lift, or moving platform.
Nebula creates the container on each process where its carrier entity exists. It removes the container when that copy despawns. By default, the worker that controls the carrier also controls the dynamic container and the entities inside it.
Add a dynamic container to a prefab
Add these components to the root of a spawnable prefab:
| Component | Purpose |
|---|---|
NetworkIdentity | Makes the carrier a networked entity that workers can spawn and transfer. |
NetworkTransform | Replicates the carrier's root pose to clients and ghost workers. |
Container | Defines the box in the carrier's local space with Center and Size. |
A Container next to a NetworkIdentity is carried by that entity: Nebula registers the box when the entity spawns on a process and removes it when the entity despawns there. You don't set anything to make this happen. NetworkIdentity.Carried returns the box.
Keep all three components on the root. Nebula reconstructs positions from the carrier's root pose, so a Container on a child object of an entity does not move with the entity: Nebula treats it as a fixed container, and the Editor warns about it.
Keep NetworkTransform enabled, use Authority = Server, and enable every position and rotation axis. Enable scale synchronization if the carrier changes scale. The Editor reports an error for a Container on an entity's root without a NetworkTransform. Run Nebula > Validate Project before building. Add NetworkRigidbody too when PhysX simulates the carrier; scripted movement does not require it.
Upgrading from DynamicContainer
Earlier versions needed a DynamicContainer component on the carrier. It is now obsolete and does nothing. Choose Nebula > Migrate > Remove DynamicContainer to remove it from every prefab and open scene. If one of your scripts has [RequireComponent(typeof(DynamicContainer))], remove that attribute first; the migration names any object it could not change. Replace DynamicContainer.IsCarrierGeometry and DynamicContainer.OfRigidbody with Container.IsCarrierGeometry and Container.OfRigidbody, and DynamicContainer.Contents with Container.Entities. Removing the component changes the numbering of the carrier's network behaviors, so this release uses wire protocol 19 and does not admit clients built with an earlier version.
Set ContainerId to a readable label. At runtime, Nebula appends the carrier's network ID and produces an ID such as interior#281474976710657.
Spawn the carrier like another server-driven entity:
var identity = NetworkPrefabs.Instantiate(
prefabId,
position,
rotation,
enclosingContainer.transform);
worker.SpawnServerDriven(identity, enclosingContainer);After OnNetworkSpawn, the dynamic container participates in container lookups on that process. ContainerRegistry.Find returns the deepest container that holds a position, and the smaller one when two are equally deep. Resolve applies handover hysteresis, and Along includes a dynamic container crossed by a line segment.
To give the carrier's inside a physics scene of its own, so the crew walk and crates rest in the carrier's own coordinates whatever it does, turn on Own Physics Frame on its Container. See Physics frames.
Use ContainerRegistry.Dynamic to enumerate registered dynamic containers. Read Container.IsDynamic, Carrier, Parent, and Entities to inspect one. Parent is the container the carrier is in.
The carrier itself is not inside its own container. Nebula excludes the carried box when it resolves the carrier's position. This also allows nested carriers, such as a vehicle inside a larger vehicle.
Overlapping carriers
Two carriers never ride inside each other. When Nebula resolves an entity's position, it skips every dynamic container that the entity carries, at any depth: its own box, and the box of any carrier that rides inside it. It uses the next smallest container that holds the position instead. It also skips, for a carrier with a physics frame, every carrier with a frame whose box is smaller than its own: the smaller of two such carriers goes inside the larger.
For example, when two ships spawn with overlapping interiors, one ship moves into the other ship's container. The other ship stays in the surrounding container, because the first ship's box now rides inside it. When more ships overlap, each one nests inside the next, and one ship stays in the surrounding container. If your carriers should not board each other, spawn them far enough apart that no box holds another carrier's origin.
Nebula ignores a placement that would make an entity ride inside itself. The entity stays in its current container, and Nebula logs a warning once for that entity. This can happen when two workers place overlapping carriers during the same tick. On its next tick, the authoritative worker resolves the entity's position again.
To check a placement in your own code, call container.IsCarriedBy(entity). It returns true when entity carries container, directly or through a chain of carriers.
Let ownership follow the carrier
By default, a dynamic container follows the carrier's authoritative worker. Its OwnerWorkerIndex matches the carrier, and its LeaseEpoch uses the carrier's authority epoch.
The carrier's worker creates a control-plane assignment for the dynamic container so it appears in the dashboard. It removes that record when the carrier despawns. While the assignment follows the carrier, the record is informational; the carrier determines authority.
When an entity enters the dynamic container, Nebula transfers it to the carrier's worker when necessary. The entity becomes a child of the container transform and uses container-local coordinates.
When the carrier moves to another worker, Nebula transfers it first. It then transfers authoritative entities inside its dynamic container on the same tick and reliable channel. The receiving worker applies the carrier before the contents. Nested dynamic containers follow the same order.
Nebula also sends the contents to each worker that holds a ghost of the carrier. This prepares the carrier and its contents for a later handover.
Lease a dynamic container
Set Authority on the carrier's Container to Leased to give the dynamic container a lease of its own from the start. The worker that spawns the carrier holds the lease first, and the orchestrator assigns it like any other leased container. When that worker stops, the orchestrator gives the lease to another worker instead of returning the container to the carrier's worker.
Use it for a structure whose inside can get busy, such as a base built on a planet: the base can move to a worker of its own while the ground around it stays where it is. A leased dynamic container must sit in a container that does not move, or in a moving one that has a physics frame of its own. Anywhere else, Nebula follows the carrier's worker until the container is somewhere allowed.
Pin a dynamic container
Pin a busy dynamic container when you want another worker to simulate its contents separately from the carrier. For a carrier that moves, give it a physics frame first: without one, the pinned worker simulates the inside against the carrier pose it receives, one replication delay late.
Use the Carried containers section in the dashboard, or send:
POST /api/containers/pin
Content-Type: application/json
{"containerId":"interior#281474976710657","workerId":"w2"}The orchestrator changes the assignment state to pinned. The selected worker then controls entities inside the dynamic container. The carrier remains on its own worker, which sends the selected worker a permanent ghost of the carrier.
To return the container to the carrier's worker, use Unpin or send:
POST /api/containers/unpin
Content-Type: application/json
{"containerId":"interior#281474976710657"}Nebula also returns a pinned container to the carrier's worker if the selected worker stops or begins retiring.
When code inside a pinned container must change the carrier, use AuthorityRpc on a carrier behavior. The call runs directly when both are on one worker and crosses the worker link when they are separate.
Work in container-local coordinates
Nebula sends entity poses relative to the current container. NetworkIdentity.SetLocalPose applies a received pose under the current container transform. This keeps a passenger aligned with a moving carrier even when update order differs between processes.
Prediction also records positions relative to the container. The client and worker can therefore compare a passenger's location inside a moving carrier even when they display the carrier at slightly different world positions.
If your input includes a direction relative to the carrier, define how it changes when the player enters or leaves the dynamic container. Input already in transit still uses the previous frame of reference. One approach is to add a small frame version to the input and convert values with the previous carrier rotation until input with the new version arrives.
For custom movement based on physics casts, use Container.IsCarrierGeometry(collider) to identify colliders that belong to the carrier, and Container.OfRigidbody(body) to find the box a carrier's Rigidbody carries. This can help you treat a moving floor as the entity's current platform.
Handle container removal
When a dynamic container unregisters, Nebula moves remaining entities to the surrounding container. It resolves that surrounding container from each entity's current world position.
On the authoritative worker, an entity that now belongs to another worker follows the normal handover path. On clients and ghost workers, Nebula updates the local container relationship until new authoritative state arrives.
Destroy or despawn the carrier through Nebula's entity lifecycle. Avoid destroying only the Container component while contents still depend on it.
Despawning a carrier on its own sets its riders down in the surrounding container, as described above. When Nebula empties the container the carrier is in, for example when a runtime chunk retires, the riders leave with the carrier instead. Nebula despawns them before the carrier, at every depth, and saves each persistent rider aboard its carrier, so the rider is restored with the carrier.
Understand network references
Nebula identifies containers in messages with ContainerRef:
- A static container reference contains its registry index.
- A dynamic container reference contains a marker and the carrier's network ID.
A dynamic reference can arrive before the carrier. For example, the gateway can receive a passenger update before a carrier keyframe. Nebula holds the dependent update and applies it after the carrier becomes available. If the carrier never arrives, the dependent entity cannot be placed in that dynamic container.
The gateway uses the carrier pose to reconstruct a passenger's world position for clients. It does not instantiate carrier entities itself.
Interest behavior
An entity inside a dynamic container is bucketed for interest management at its root carrier's position — the outermost carrier, if one dynamic container rides inside another — and inherits the root carrier's radius, always-relevance, and enter/exit decision for each client. Everything riding a carrier enters and leaves a client's set together with it, at any speed, instead of a passenger popping in or out independently as it nears a region edge. A client also only learns the dynamic container exists (through ContainerOwnership) once one of its entities is in that client's interest set, or the container overlaps a window the client's own foci opened.
A player riding a carrier is in the carrier's scope. The gateway follows the carriers of every player it serves by network ID, wherever they go. When a ship moves into a container of another scope, each rider's client keeps the ship and its own pawn and switches to the ship's new scope without reconnecting. To move a ship and its crew between scopes, see Move a crewed carrier into another scope.
Inspect dynamic containers
Open the dashboard's World map to inspect:
- the worker that controls the carrier;
- the worker that controls the dynamic container;
- the enclosing container;
- speed and heading;
- authoritative and ghost entity counts; and
- pin and unpin controls.
Use POST /api/telemetry only through the built-in worker telemetry. Do not send per-tick game state through the dashboard API.
World partition
Split a large world into cell scenes, stream cells around players and workers, and shift the Unity origin.
Physics frames
Give a ship, station, or planet a physics scene of its own in which it stands still, so what is inside simulates in the container's own coordinates and can be simulated by a different worker than the hull.