Shared Entities and Ownership Transfer
Summary
This tutorial shows how to build an entity that several players take turns controlling, such as a vehicle, a turret or a mounted weapon, by transferring ownership between them instead of spawning one per player. It continues from Client Authority and State Relay, reusing that scene, room and player prefab, and adds a vehicle a player can get into and out of.
The previous tutorial built a character that belongs to one player: it spawns already owned, stays owned for its whole life, and goes away when that player leaves. A shared entity inverts all three. It exists before anyone owns it, its owner changes hands, and it must outlive whoever is currently driving. Those differences change several settings from the values a player entity wants, and they make ownership something the server hands out on request rather than something set at spawn time.
Requirements
Completion of Client Authority and State Relay
How a Shared Entity Differs
For this tutorial, there are some differences in how a shared entity is configured so you will need to change some settings from their default values.
It cannot be permanent. Permanent entities never move and are not synced to clients, since clients are assumed to already know where they are. Anything a player drives fails both conditions. Permanence also makes an entity unspawnable, which matters when you come to restore the player's own character. Make sure IsPermanent is not checked.
It must survive its owner disconnecting. DestroyOnOwnerDisconnect is on by default, which is correct for a player's own character and wrong for anything shared: the vehicle would be destroyed the moment its driver's connection dropped, taking it away from everyone else in the room.
Its owner should not get every permission. A driver needs TRANSFORM to move it and usually PROPERTIES to publish state. It rarely needs DESTROY, which would let any one player delete an object the whole room shares. See Ownership and Authority for the full permission set.
A shared entity usually belongs to the level rather than to a player, so it is placed in the scene rather than spawned from Resources. That is the opposite of the player prefab in the previous tutorial, and it brings its own requirement, covered in 'Publish the Scene Entity' below.
Scene Setup
Create the Vehicle
Open the Playground scene from the previous tutorial.
Create a cube and name it 'Vehicle'. Position it a few meters from the player's start point.
Add a CharacterController so the vehicle can be driven around the level. Set its 'Height' and 'Radius' to cover the cube.
Add a ksEntityComponent.
Uncheck 'Is Permanent'. A permanent entity never moves and is never synced, so leaving this checked produces a vehicle that appears to work locally and never moves for anyone else.
Uncheck 'Destroy On Owner Disconnect'. Otherwise the vehicle is destroyed for every player the moment its driver disconnects.
In the inspector for the CharacterController, expand the 'Reactor Collider Data' foldout and set 'Existence' to 'Client-Only'. Movement here is client-authoritative, so the server has no need to simulate the capsule.
Do not add a ksAutoSpawn. That component spawns one entity per player, which is what a player character wants and the opposite of what a shared vehicle wants. Leave the vehicle in the scene as a single object.
Publish the Scene Entity
Adding a ksEntityComponent to a scene object is not enough on its own. A scene entity exists for the server only once the scene configs are built, which assigns it an EntityId.
Build the scene configs (CTRL + F2).
Select the 'Vehicle' and confirm 'Entity Id' in the ksEntityComponent inspector is no longer zero.
Save the scene. The id is written into the scene file, and other clients resolve it from there.
An unpublished scene entity does not raise an error. Every client instead gets an ordinary local game object: the scene looks correct, nothing syncs, and each client quietly interacts with its own copy. If a shared object behaves as though every player has their own, check its 'Entity Id' before anything else.
Rebuild the scene configs whenever you add or remove a scene entity.
Scripting
Create a Class for Constants
The ids for properties and RPCs have to agree on the client and the server, so they belong in a common script. In 'Assets/ReactorScripts/Common', create a script named 'VehicleConsts'.
VehicleConsts.cs
If the entity also has a ksAnimationSync*, that component claims a range of property ids for animator parameters starting at its 'Animation Property Ids' value, which defaults to 1000. Keep your own property ids clear of that range.*
Create the Server Entity Script
Only the server can assign ownership. SetOwner does not exist on the client, and a client cannot take an entity or hand one on. Getting in is therefore a request the server answers, and the answer arrives as an ownership change rather than as a reply.
Select the 'Vehicle' object.
In the 'Add Component' menu, select 'Reactor->New Server Entity Script'.
Name the script 'ServerVehicle'.
ServerVehicle.cs
Ownership has already been cleared by the time
Room.OnPlayerLeaveruns. Do not gate disconnect cleanup onEntity.Owner == player: the comparison will not match, and any cleanup behind it is skipped without an error. If you keep state of your own alongside ownership, clear it here unconditionally.
Create the Client Entity Script
The client script sends the requests and reacts to ownership changing hands.
Select the 'Vehicle' object.
In the 'Add Component' menu, select 'Reactor->New Client Entity Script'.
Name the script 'ClientVehicle'.
ClientVehicle.cs
Create the script that actually reads input and moves the vehicle, and assign it to the 'Driver Script' field. Because the local player owns the entity with the
TRANSFORMpermission, moving the transform is enough. Reactor detects the change and syncs it.
Ask for Control
Give the player a way to send the request. This example uses a proximity check and a key press, and belongs on whatever object manages your interactions.
Note that both branches only ask. Nothing about the local state changes here; that happens in ClientVehicle.Update when ownership actually changes.
Deciding What Syncs
A shared entity usually carries three kinds of state, and they travel by different routes.
Position and rotation
The owner's TRANSFORM permission
Animator parameters
ksAnimationSync
Anything else
Entity properties
The third row is easy to overlook. Presentation that is computed in script rather than driven by the Animator, such as a turret's yaw, a steering angle or a hatch position, is not covered by ksAnimationSync and needs a property of its own. The owner writes it; every other client reads it.
Read Occupancy From Ownership
It is tempting to publish a property naming the current driver and let your UI read that. Ownership already answers the question, and Reactor keeps it correct on its own, including when a player disconnects. A second property is a second copy of the same truth, and the two drift: the vehicle ends up genuinely free while the interface still reports it occupied.
Keep a "who is driving" property only for what ownership cannot express, such as a display name, and derive availability from Entity.OwnerPermissions as ClientVehicle.IsOccupiedByOther does above.
Scripts on Remote Clients
The previous tutorial set ksOwnershipScriptManager to 'Disable When Unowned', which suits a player character: a remote instance needs nothing but its animation. A shared entity is usually different, because some of its presentation has to be applied on every client from synced state rather than from input.
Two approaches work:
Add a ksOwnershipScriptManager and set the scripts that must always run to 'Leave Unchanged', as the previous tutorial does for the Animator.
Leave the manager off the entity and let one always-enabled script decide, as ClientVehicle does with its 'Driver Script' field. This is often clearer for a vehicle, because the same script already has to handle the ownership change.
Whichever you choose, a script that runs on a client which does not own the entity must not fight the synced state. It should not move the transform and it should not write animator parameters, because both are already arriving from the owner.
Do not use 'Destroy When Unowned' on an entity whose ownership can change. Destroyed scripts cannot be recreated when the local player later takes over.
Removing and Restoring the Player's Character
Many games hide the player's own character while they are driving. Because the player owns their character entity with the DESTROY permission, destroying the game object asks the server to destroy the entity, so the character disappears for everyone rather than only on the local client.
Getting out has to put one back. ksAutoSpawn.Spawn spawns an entity for an instantiated prefab:
IsSpawnable requires the object to have a ksEntityComponent, an asset id, no entity already linked, and not to be permanent.
Those conditions are read from the prefab you are instantiating, not from any instance of it in the scene. A prefab marked permanent, with 'Is Permanent' overridden on the scene instance, passes in the scene and fails on every respawn. The failure is silent, because "cannot spawn" and "already spawned" both report
IsSpawnable() == false. CheckksEntityComponent.Entityto tell the two cases apart.
If a follow camera or any other system holds a reference into the old character, re-point it at the new one. The old transform was destroyed with the old entity.
Testing
Ownership transfer cannot be verified with a single client. Use Unity Multiplayer Play Mode, ParrelSync, or a built client alongside the editor.
Build the scene configs (CTRL + F2) and save the scene.
Start a local server.
Connect two clients.
Check each of these, since several only fail with more than one client connected:
One client takes control and drives. The vehicle moves on the other client.
The second client tries to take control while it is occupied and is refused.
The first client releases control, and the second can then take it.
A client disconnects while driving. The vehicle stops, stays in the room, and becomes available.
Common Problems
Every client drives its own copy and nothing syncs
The scene entity was never published. Build the scene configs and confirm 'Entity Id' is non-zero, then save the scene.
The entity never moves for other players
'Is Permanent' is still checked.
The vehicle disappears when its driver disconnects
'Destroy On Owner Disconnect' is still checked.
Two players both believe they are driving
Local control is being enabled by the key press instead of by the ownership change.
Cleanup on disconnect never runs
It is gated on Entity.Owner == player, which no longer matches by the time OnPlayerLeave fires.
A vehicle reads as occupied after its driver has left
Availability is being read from a property of your own rather than from ownership.
A respawned prefab exists only on the client that spawned it
IsSpawnable returned false. Check the prefab for 'Is Permanent' and a missing asset id.
A client entity script never receives RPCs or property changes
Awake was declared without override, hiding the base implementation that attaches the script to its entity.
Where to Go Next
Ownership and Authority: the permissions and owner-update validators this tutorial builds on.
Client Authority and State Relay: the per-player case, for contrast.
Messaging and RPCs: the request half of the handshake.
Validators: keeping a client-authoritative owner honest.
Parenting Entities: attach a turret or a rider to the vehicle players take turns driving.
Handling Disconnects and Reconnecting: what happens to a shared object when its driver drops, and how to hold it for their return.
Last updated

