Synchronization of Child Transforms, best practices

I’m relatively new to DOTS and still trying to get to grips with the theory of it, so bear with me here:

I’m in a situation where I need to replicate a rotation on a transform that is the child of a ghost. In my case this is for aim IK, but the situation is functionally more or less identical to a turret on a vehicle. Right now my character controller is entirely server-authoritative, which works great as long as I’m only working with the root transform of my ghost. Trouble is, in order to drive my IK system, I need to move a transform childed to that ghost to serve as an IK target. I wrote a system to do this on the server, but naturally the movement doesn’t replicate because the target child is not itself a ghost. After a day or so of research, it seems like I have a few options:

1. Make the target a GhostChildEntity and assign it to the player prefab’s GhostGroup
2. Something having to do with GhostComponentAttribute and SendDataForChildEntity (mentioned here: DOTS NetCode 0.3.0 released ), but I haven’t had much luck turning up what exactly this does or how to implement it.
3. Don’t control the IK target on the server at all. Leave it on the client.

My understanding so far:

1:
I started today chasing option 1, but I haven’t had much luck working out how to actually assign a GhostChildEntity to a GhostGroup somewhere on a parent. I understand this assignment has to be done at runtime, but I’m not clear on how I would get, be it by ijobentity or foreach, a reference to both the GhostChildEntity and GhostGroup and know I was looking at the correct pair of them.

My efforts to understand how to do this led me through a couple of posts that suggested to me I might be barking up the wrong tree anyway, and that a GhostGroup would be a very expensive solution for a relatively simple problem, but I’m not sure. In any case I’d still appreciate some clarification on how this is best done, as I’m pretty sure I will have to resort to it for the weapons themselves.

2:
Honestly no idea what’s going on with these right now or whether they’re the tool for the job.

3:
This I think I could do pretty easily. I’m already synchronizing a HeadPitch float ghostfield to drive the player camera, so it wouldn’t be out of my way to write a clientside ISystem to drive the IK targets too. This is a bit different from the camera though in that it would have to operate on each client for every player connected to the server (rather than just the local ghost owner), and from a structural perspective I’m not a huge fan of running aim IK for all players on the every client while every other character controller feature is handled by the server. I’m not convinced this isn’t the best approach, it just feels “icky” to me in a way that makes me think there’s a better solution.

Which of these approaches, if any, should I be pursuing? Am I totally barking up the wrong trees here?

Hi, I’m interested in this and facing the same conundrum. What approach did you end up using?

First: please also check this post that clarify how to setup replication for child entities:
Question - How to control Netcode child entities - Unity Forum

Second: there is a limitation for child entities: Only the first level hierarchy can be synced. So, if you have an object like this:

Root
  child1  <--- This can be replicated (pos, rot, scale)
     child1_1 <--- this will be never replicated

That being said, in your specific case, I think you have something like this:

Root
   IKTarget  <-- You want to move this

What you need to do is:

  • Add a baker for the IKTarget that add the localtransform component (via GetEntity()) and any other component necessary
  • Customize the replication via GhostAuthoringInspectionComponent.

That should be sufficient to replicate the position of the IKTarget from server to client without trouble.

You can also use GhostGroup for that purpose, in which case you need to have two different ghost, one for the root, and one for the IKTarget. The IKTarget is not a child of the root object anymore**, it should be a ghost of that, it MUST be root gameobject.**

Root  <-- This has a GhostAuthoring, this have GhostGroup enabled in the inspector.
IKTarget <-- This has a GhostAuthoring

You need to have specific baker for the Root and for the IKTarget such that:

  • A GhostGroupRoot is added to the Root entity
  • A GhostChildEntity is added to the IKTarget

In code, you have then to add to the Root.GhostGroup buffer the IKTarget entity

//some pseudo code, somewhere in some system
entityManager.GetBuffer<GhostGroup>(root).Add(ikTargetEntity);

Ghost Group as feature is meant to be used to force sending together different ghosts in same frame/packet (i.e dynamic parenting).
In you case looks like using GhostGroup is not necessary, and normal child entities can be used instead.

Is there a sample available that illustrates the techniques described in this post? I tried to replicate but specifically the step of “Customize the replication via GhostAuthoringInspectionComponent” didn’t work for me because the GhostAuthoringInspectionComponent didn’t seem to be aware of the child’s components.

I have two thing here.

First: ERRATA CORRIGE (sorry, my mistake).

1- Child ghost components are replicated for any child entity in the hierarchy (do not depend on the level) because they are now all present in the LinkedEntityGroup buffer (changed after 0.5). So, no changes in hierarchy are necessary to make your child replicated @DecoFox

2- @kitae the child are not aware of transforms because by default entities doesn’t not add transform for children in the hierarchy, nor they are parented.
In order to have children to have a transform, you need first to add a baker for the child that request the entity to have a transform component, by specifying the appropriate TransformFlag.

class ChildAuhoring : MonoBehaviour
{
   public bool addTransform;
   
   class ChildBaked : Baker<ChildAuhtoring>
   {
      public void Bake(ChildAuhtoring authoring)
      {
           var flag = addTransform ? TransformFlag.Dynamic : TransformFlag.None;
           var child = GetEntity(flag); <------ THIS IS THE KEY          
      }
   }
}
[code]

As soon as at least one of the baker request the child to have a transform component, this will be added.

If you have a baker that add your own component

[code]
class MyComponentAuhoring : MonoBehaviour
{  
   class ChildBaked : Baker<MyComponentAuhoring>
   {
      public void Bake(MyComponentAuhoring authoring)
      {
           var entity = GetEntity(TransformFlag.None);
           AddComponent<MyComponent>(entity);
      }
   }
}

This will add the component to the children, and if the component is one that is replicated you can customize variants and other properties.
If not replicated, you can customize the stripping option (if present on server or client for example).

The condition for that to work is always the same: you MUST author the prefab, not an instance of it in the scene (otherwise all options are disabled).