Proper ECS Schema

I’m trying to learn DOTS and ECS with a very simple project, but I think I’m fundamentally misunderstanding some structural things because it seems to be impossible to do something incredibly simple.

I have a ‘soldier’ object made up of 3 meshes-- body, with two children: left arm & right arm. All I want is to manually animate this guy so that the whole body bounces up and down, and the arms each swing. Just need to be modifying 1 Translation, and 2 Rotations, derived from one shared animationTimer (that increments in a system based on deltaTime)

So I built a prefab with the following structure:

[Root Node: Skeleton_Data authored component, ConvertToEntity component]
.[Body, with a Render Mesh & Filter]
…[Left arm, with a Render Mesh & Filter]
…[Right arm, with a Render Mesh & Filter]

Skeleton_Data is just a simple IComponentData with just an animationTimer. (Maybe it should have references to the Body & Arms? But I can’t figure out how to do that, more on that later)

My conventional wisdom for how to build this would be to have one Skeleton_Animate_System, and in its OnUpdate, have an Entities.ForEach(Skeleton_Data) that…

  1. Looks at each skeleton_Data;
  2. Increases that skeleton_Data.animationTimer by deltaTime
  3. (Somehow) access skeleton_Data.body.translation, set translation derived from animationTimer
  4. (Somehow) access skeleton_Data.leftArm.rotation, set rotation derived from animationTimer
  5. (Somehow) access skeleton_Data.rightArm.rotation, set rotation derived from animationTimer

But this seems fundamentally contrary to the nature of ECS… because there’s no way for Skeleton_Data to store references to the Body translation & Arms’ rotations?

And as best I can tell, I can’t do a thing like Entities.ForEach(ref Skeleton_Data skeleton_Data, ref Translation body_Translation, ref Rotation armL_Rot, ref Rotation armR_Rot) => … because (1) you can’t have multiple of the same component (Rotation) in one ForEach, and (2), more importantly, can’t be combining components from different entities in one ForEach?

But maybe one of these assumptions is wrong?

Could someone please help me wrap my head around how this should be structured? What ComponentData and System(s) do I need for this incredibly simple project?

You can access components on other entities via ComponentDataFromEntity

ComponentDataFromEntity cdfe = GetComponentDataFromEntity<T>();
         if (cdfe.HasComponent(entity)) {
            T component = cdfe[entity];
         }

Store an entity reference to the body part, and you’re good to go.

If you’re accessing same data as in query, make sure to set isReadOnly parameter to true:

GetComponentDataFromEntity<T>(true);

otherwise you won’t be able to access it correctly;

Alternative is to copy required values in a separate data processing step;

As to how to add actual data - use custom conversion logic (IConvertGameObjectToEntity) to build up required setup:
https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/conversion.html

Thanks for the reply. I’d tried doing that without success. I assumed it was because the Translation I’m getting from GetComponentDataFromEntity is a struct, so I must be getting the value only, and thus when I set it’s new animation-based position, that change isn’t propagating to the actual entity’s Translation struct? Compared to the main/looped-on entity’s ref translation, which I can set directly to move the soldier. But maybe I’m doing something wrong?

Here’s my code:

ComponentDataFromEntity<Translation> allTranslations = GetComponentDataFromEntity<Translation>();

Entities.ForEach( (ref Skele_Data skeleData, ref Translation soldierTranslation) =>
{
    skeleData.animTimer += deltaTime;
    Translation cTrans = allTranslations[skeleData.armR];
    cTrans.Value.y = UpAndDown(skeleData.animTimer);
}

This seems like it should be moving the Right Arm up and down, but nothing moves. If I set soldierTranslation.Value.y to UpAndDown, then the whole thing moves up and down, so that function definitely works. And when I check out the Entity Debugger, skeleData.armR definitely points at the Right Arm entity. But it’s Translation never changes

Yes you’re correct. You need to write the value back again.

allTranslations[skeleData.armR] = CTrans;
1 Like

Amazing!! It works! Thank you!!!

Is this sort of thing standard-operating-procedure for DOTS? That is to say, efficient even when run on thousands or tens of thousands of entities?

If each soldier has a body, arms, legs, and a head, it seems like I’m going to be getting & setting at lots of different components of lots of different entities… among other things, it’s really weird trying to get a handle for what sort of operations are fine to run 10k times a frame and which ones aren’t!

There’s no way around random lookups when you have these kinds of relationships though it might make better use of the prefetcher to iterate the children and lookup the parent instead.
An alternative is to copy data from the parents to an intermmediate container like a hashmap and then read from that when iterating the children which may be more optimal for certain scenarios.

If you lookup the parent from the child instead, then your systems can be made simpler as your code wouldn’t be changing the left arm, right arm, left leg, right leg etc. in the one system.

ComponentDataFromEntity<Skele_Data> allSkeles = GetComponentDataFromEntity<Skele_Data>(true);

Entities
.With<Arm>()
.ForEach( (ref Translation trans, in ParentRef parentRef) =>
{
    Skele_Data skeleData = allSkelies[parentRef.entity];
    trans.Value.y = UpAndDown(skeleData.animTimer);
}

I always struggle myself with which option to choose as it’s dependent on a lot of factors which way is more optimal and or readable.