> For the complete documentation index, see [llms.txt](https://kinematicsoup.gitbook.io/reactor/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kinematicsoup.gitbook.io/reactor/tutorials/parenting.md).

# Parenting Entities

## Summary

This tutorial shows how to make one entity ride another: a character standing on a moving platform, a turret riding a vehicle hull, a crate carried on a lift.

Read this part before anything else, because it is the opposite of what most engines lead you to expect. **Reactor has no synced parent hierarchy, on the server or the client.** Nothing propagates a parent's movement to its children. `Entity.Parent` moves nothing, does not reparent Unity transforms, and is not what holds a rider on a platform. It is a client-side label recording a relationship you maintain yourself.

Parenting is therefore something you build rather than something you enable, out of three cooperating pieces:

1. The server applies the parent's per-frame motion to the child by hand, every frame.
2. The link is published as an ordinary entity property holding the parent's entity id.
3. The client reads that property and sets `Entity.Parent`, so client code can walk the relationship.

Only the first of those moves anything. Skip it and your rider stands still while the platform slides out from under it, no matter what the other two are doing.

It builds on [Networked Properties and RPCs](/reactor/tutorials/properties_and_rpcs.md) and the physics queries from [Detecting Collisions](/reactor/tutorials/physics.md).

### Requirements

* [Reactor Requirements](/reactor/overview.md#requirements)
* Completion of [Networked Properties and RPCs](/reactor/tutorials/properties_and_rpcs.md)

## There Is No Hierarchy to Sync

On the server, `ksIServerEntity` has a transform but not a parent transform. Setting one entity's position does not move another. This is deliberate: entity transforms are synced independently, and a server-side hierarchy would mean resolving that hierarchy before every sync.

On the client there is no transform hierarchy either. Every entity game object sits directly under the room object regardless of what its `Entity.Parent` says, and you can confirm that in the Unity hierarchy at runtime: a turret parented to a hull is still a sibling of it, not a child.

So there is nothing anywhere that moves a child when its parent moves. That job is yours.

Each frame you measure how far the parent moved and rotated since the last frame, and apply the same motion to the child. The three pieces fit together like this:

```
SERVER                                          CLIENT
  child applies the parent's per-frame delta
  to its own transform
          |
  child publishes the parent's entity id
  as an entity property  ------------------->   property change handler sets
                                                Entity.Parent = Room.GetEntity(id)
```

The server half is what actually keeps the child in the right place. The client half records the relationship on the client, so client code can walk from a child to its parent and back through `Entity.Parent`, `Entity.Children` and `Entity.ForEachDescendant` instead of re-reading the property and resolving the id every time it needs to know what something is riding.

> *`Entity.Parent` is a client-side relationship and does not reparent the Unity transform. Entity game objects stay where the entity factory put them, and `transform.parent` does not change. If you need the child's game object to be a Unity child of the parent's, reparent it yourself.*

## Applying the Parent's Motion

Both kinds of parenting below use the same helper. Put it in a common script so the client can use it too.

*Motion.cs*

```c#
using KS.Reactor;

public static class Motion
{
    /// <summary>Apply a parent's change in position and rotation to a child transform.</summary>
    public static void Apply(
        ksTransform transform,
        ksVector3 lastPosition,
        ksQuaternion lastRotation,
        ksVector3 newPosition,
        ksQuaternion newRotation)
    {
        // Turn the child by however much the parent turned.
        ksQuaternion rDelta = newRotation * lastRotation.Inverse();
        transform.Rotation = rDelta * transform.Rotation;

        // Move the child by however far the parent moved...
        ksVector3 pDelta = newPosition - lastPosition;

        // ...plus the arc it is carried through by the parent's rotation.
        ksVector3 localOffset = transform.Position - lastPosition;
        pDelta += rDelta * localOffset - localOffset;

        transform.Position += pDelta;
    }
}
```

The last term is the one worth understanding. `newPosition - lastPosition` alone is enough for a parent that only slides. As soon as the parent rotates, a child standing away from the parent's center must also swing around it, and `rDelta * localOffset - localOffset` is that arc. Leave it out and a rider on a turntable will stay put while the platform spins underneath them.

Note that it is a function of `localOffset`, so it contributes nothing for a child sitting exactly at the parent's center. A turret mounted on the middle of a hull will look correct with or without it. Test with an off-center child, or the bug hides.

## A Fixed Parent

The simplest case is a child attached once and never reassigned, such as a turret on a hull. Store the parent, publish its id, and apply its motion every frame.

*seTurret.cs*

```c#
using KS.Reactor;
using KS.Reactor.Server;

public class seTurret : ksServerEntityScript
{
    private ksIServerEntity m_parent;
    private ksVector3 m_lastPosition;
    private ksQuaternion m_lastRotation;

    public override void Initialize()
    {
        // Update group 2: after the hull has moved this frame. See 'Update Order' below.
        Room.OnUpdate[2] += Update;
    }

    public override void Detached()
    {
        Room.OnUpdate[2] -= Update;
    }

    /// <summary>Attach this entity to a parent. Publishes the link for clients.</summary>
    public void SetParent(ksIServerEntity parent)
    {
        m_parent = parent;
        if (m_parent != null && !m_parent.IsDestroyed)
        {
            Properties[Prop.PARENT] = m_parent.Id;
            m_lastPosition = m_parent.Transform.Position;
            m_lastRotation = m_parent.Transform.Rotation;
        }
        else
        {
            Properties[Prop.PARENT] = 0;
        }
    }

    private void Update()
    {
        if (m_parent == null)
        {
            return;
        }
        Motion.Apply(Transform, m_lastPosition, m_lastRotation,
            m_parent.Transform.Position, m_parent.Transform.Rotation);

        m_lastPosition = m_parent.Transform.Position;
        m_lastRotation = m_parent.Transform.Rotation;
    }
}
```

## A Dynamic Parent

A character should ride whatever it happens to be standing on, and that changes as it walks from the ground onto a platform and off again. Raycast down each frame and use whatever you hit.

*seRider.cs*

```c#
using KS.Reactor;
using KS.Reactor.Server;

public class seRider : ksServerEntityScript
{
    private Contact m_ground;

    public override void Initialize()
    {
        Room.OnUpdate[3] += Update;
    }

    public override void Detached()
    {
        Room.OnUpdate[3] -= Update;
    }

    private void Update()
    {
        // A destroyed entity can still have update handlers run against it before its scripts
        // are detached. Writing a property here logs a warning and is discarded. See below.
        if (Entity.IsDestroyed)
        {
            return;
        }

        Contact newGround = GetGround();

        // Only carry the rider if it is still standing on the same thing it was last frame.
        // Without this check, stepping from one platform to another applies the difference
        // between two unrelated positions and flings the rider across the level.
        if (newGround != null && m_ground != null && m_ground.Entity == newGround.Entity)
        {
            Motion.Apply(Transform,
                m_ground.Position, m_ground.Rotation,
                newGround.Position, newGround.Rotation);
        }
        m_ground = newGround;
    }

    private Contact GetGround()
    {
        ksRaycastParams raycast = new ksRaycastParams();
        raycast.ExcludeEntity = Entity;
        raycast.Origin = Transform.Position;
        raycast.Direction = ksVector3.Down;
        raycast.Distance = 1.25f;

        ksQueryHitResults<ksRaycastResult> hits = Physics.Raycast(raycast);
        if (hits.Touches.Count > 0)
        {
            hits.SortByDistance();
            Properties[Prop.PARENT] = hits.Touches[0].Entity.Id;
            return new Contact(hits.Touches[0]);
        }

        // Nothing underneath. Publish zero so clients clear the parent.
        Properties[Prop.PARENT] = 0;
        return null;
    }
}
```

> *The destroyed check is not optional, and it is easy to leave out because nothing fails visibly without it. A dynamic parent writes its parent property every frame, including the frame on which its own entity is destroyed. Removing the handler in `Detached` is not enough on its own, because the entity can be destroyed partway through a frame while a handler registered by its scripts still has a turn to run. The result is:*
>
> ```
> [WARNING; Reactor.ServerEntity]  Cannot set property 0 on destroyed entity 10.
>     at KS.Reactor.ksPropertyMap.set_Item(UInt32 propertyId, ksMultiType value)
>     at seRider.GetGround()
> ```
>
> *Reactor refuses the write and carries on, so nothing breaks. It is a warning rather than an error, and it appears when a player disconnects or leaves, which is exactly when nobody is watching the server log.*

`Contact` records what the parent's transform was at the moment of the hit, so the next frame has something to subtract from:

*Contact.cs*

```c#
using KS.Reactor;

public class Contact
{
    public ksIEntity Entity;
    public ksVector3 Position;
    public ksQuaternion Rotation;

    public Contact(ksRaycastResult hit)
    {
        Entity = hit.Entity;
        Position = hit.Entity.Transform.Position;
        Rotation = hit.Entity.Transform.Rotation;
    }
}
```

## Receiving the Link on the Client

Clients learn about parenting from the property. Add a client entity script to anything that can be parented.

*ceParent.cs*

```c#
using KS.Reactor;
using KS.Reactor.Client.Unity;

public class ceParent : ksEntityScript
{
    public override void Initialize()
    {
        Entity.OnPropertyChange[Prop.PARENT] += ChangeParent;

        // Apply the current value too. An entity that spawns already parented, or one that
        // comes into view later, never receives a change event for the initial value.
        Entity.Parent = Room.GetEntity(Properties[Prop.PARENT]);
    }

    private void ChangeParent(ksMultiType oldValue, ksMultiType newValue)
    {
        Entity.Parent = Room.GetEntity(newValue);
    }
}
```

> *Setting the initial value in `Initialize` matters as much as handling the change. Properties are synced to a client when it first sees the entity, so a client that joins after a turret is already mounted gets no change event and would otherwise never parent it.*

Parenting needs no predictor configuration. The parent link is an ordinary synced property holding an entity id, not a predicted one, and parented entities use whatever predictor they would have used anyway. Nothing in this tutorial requires a predictor override or a `PredictedProperties` entry.

## Update Order

A child reads its parent's transform, so the parent must have moved first. Reactor runs update handlers in order of their integer group, which is what you use to enforce that. A chain of movers, hulls, turrets and riders is ordered like this:

| Group | Script         | Moves                                           |
| ----- | -------------- | ----------------------------------------------- |
| 0     | platform mover | platforms and lifts                             |
| 1     | hull           | vehicles, which may be standing on a platform   |
| 2     | turret         | turrets, which ride a hull                      |
| 3     | rider          | characters, which may stand on any of the above |

Get this wrong and the child reads the parent's position from *before* it moved this frame, so it trails by exactly one frame of the parent's motion. It does not accumulate, and it disappears the moment the parent stops, which is what makes it easy to miss.

The size of the error is the parent's speed divided by the server frame rate. Measured on a platform travelling 35.36 units in 4 seconds (8.84 units/second) at a 60 Hz server frame rate, moving a rider from group 3 to a group ahead of the platform produced a lag of 0.147 units, directed opposite the platform's travel, against a predicted 8.84 / 60 = 0.147. At walking speeds the same mistake is a couple of millimeters and invisible until something moves quickly.

> *Update groups run in numeric order, and the physics step falls between group -1 and group 0. Handlers sharing a group run in registration order, which is not something to rely on. Give parents and children distinct groups.*

## Spawning a Parent and Child Together

A prefab may contain more than one `ksEntityComponent`, for example a hull with a turret as a child game object. Spawning it produces one entity per component, and `Room.SpawnCollection` returns them so you can link them up.

```c#
public void SpawnVehicle(ksVector3 position, ksQuaternion rotation)
{
    // Every entity in the collection shares the "Prefabs/Vehicle" type, but each has its own
    // asset id and its own scripts.
    List<ksIServerEntity> entities = Room.SpawnCollection("Prefabs/Vehicle", position, rotation);
    ksIServerEntity hull = entities[0];
    ksIServerEntity turret = entities[1];

    turret.Scripts.Get<seTurret>().SetParent(hull);
}
```

> *Because both entities share a type string, distinguish them by the scripts they carry rather than by* `Entity.Type`*. `entity.Scripts.Get<seTurret>() != null` identifies the turret; `Entity.Type` cannot.*

## Checking Your Work

Parenting bugs are easy to miss by eye, because a rider that is slightly wrong still looks attached. Measure instead. Take the child's offset from the parent, expressed in the parent's local space:

```c#
ksVector3 local = parent.Transform.Rotation.Inverse() * (child.Transform.Position - parent.Transform.Position);
```

While the child is parented and not moving under its own power, that value must stay constant:

* If it stays constant while the parent **slides**, translation is correct.
* If it stays constant while the parent **rotates**, and the child is **off-center**, the arc term is correct. This is the check that catches a missing `rDelta * localOffset - localOffset`.
* If it shifts when the parent starts moving and returns when the parent stops, your update order is wrong.

For reference, a correct implementation holds that offset to within a few millimeters through twenty degrees of parent rotation, on a child sitting 4.6 units out from the parent's center.

## Common Problems

| Symptom                                                                   | Cause                                                                                                                                     |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Child ignores the parent entirely                                         | Nothing is publishing `Prop.PARENT`, or the client has no script setting `Entity.Parent` from it.                                         |
| Child follows a sliding parent but not a rotating one                     | The arc term is missing from the motion helper, or the child is being tested at the parent's exact center where that term is zero.        |
| Child trails the parent while moving and catches up when it stops         | Update order. The child runs before the parent has moved.                                                                                 |
| Child is flung away when stepping between platforms                       | The motion is being applied across a parent change. Only apply when this frame's parent is the same as last frame's.                      |
| A client that joins late never sees the parenting                         | `Initialize` reads the change event but not the current property value.                                                                   |
| "Cannot set property N on destroyed entity" warnings when a player leaves | An update handler wrote the parent property on the frame its entity was destroyed. Return early on `Entity.IsDestroyed`.                  |
| Parenting works in the editor but not for a second client                 | The parent's entity id is being sent as something other than the entity id, or the property id collides with another user of the same id. |

## Where to Go Next

* [Shared Entities and Ownership Transfer](/reactor/tutorials/shared_entities.md): let players take turns driving the vehicle the turret is mounted on.
* [The Server Update Loop](/reactor/architecture/update-loop.md): update groups and the time API.
* [Automatic Client Data Syncing](/reactor/architecture/data-syncing.md): how properties reach clients, and how prediction uses the parent relationship.
