How do you use the parent/child in ECS? (IBufferElementData)

Version:
Unity: 2022.2.5f
Burst: 1.8.2
Entities: 1.0.0-pre.15

Hello

I want to be able to handle child and parent entities for attaching weapons/items to characters or vehicle.
In Mono i could just make the item a child or directly just edit the transform of the item through a reference to the GameObject.

I have tried a bit of testing for parent and children to work but i will need to do a lot more testing to understand it. Especially since i cant find a good example which i can understand more easily.

At the moment i have haven’t really done research and testing on IBufferElementData, didnt think i would need it at this stage. But i encounter several situations were a parent/child solution would be better.

In this example i found on github, i shows how the syntax works, but i don’t know what this function is intended to do.

Older Local Example Code

using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public interface IReceiveEntity
{
}

public struct SentEntity : IBufferElementData
{
    public Entity Target;
}

public class EntitySender : MonoBehaviour
{
    public GameObject[] EntityReceivers;

    class EntitySenderBaker : Baker<EntitySender>
    {
        public override void Bake(EntitySender authoring)
        {
            var sentEntities = AddBuffer<SentEntity>();
            foreach (var entityReceiver in authoring.EntityReceivers)
            {
                List<MonoBehaviour> potentialReceivers = new List<MonoBehaviour>();
                GetComponents<MonoBehaviour>(entityReceiver, potentialReceivers);
                foreach (var potentialReceiver in potentialReceivers)
                {
                    if (potentialReceiver is IReceiveEntity)
                    {
                        sentEntities.Add(new SentEntity() {Target = GetEntity(entityReceiver)});
                    }
                }
            }
        }
    }
}

Can i get a simple example that can show how to use parent and child in ECS? Especially how to work with IBufferElementData, accessing child components and other relevant stuff. Have tried searching but i have found old examples that didn’t help my understanding. Any help would be appreciated.

In Unity ECS, just simply set parent entity component, with it’s realtive entity.
Unity ECS system will handle the rest. Including assigning children to relevant parents.

Thanks for the response.
I have started creating a test environment, for testing and stuff. My first testing with trying to create a entity with a child with a prefab, failed.
I am just trying see what the correct way to setup parent /child is, and if i can adapt it to my current work flow with prefabs.

And accessing the child requires the use of dynamic buffer. At this point i am not sure of how to interact with dynamic buffer.

I going to test some more later then i can post some code so people can see what i don’t understand.

I tested just simply adding a GameObject through a subscene and that created a parent and children like @Antypodish said.

But when i try to do it at runtime, i can’t get it work.

I want to be able to add, remove children and change parents at runtime. And i plan to have a lot of procedural systems.

Any suggestion to what i am doing wrong, or is it that changing parents and children can’t be done during runtime?

Added via Subscene (Works)
9257733--1295163--upload_2023-8-29_10-49-53.png

Added during runtime (Don’t create children)
9257733--1295166--upload_2023-8-29_10-50-35.png

Baker

public class ParentTesting : MonoBehaviour
{
    public GameObject parentGO;
    public GameObject childFirst;
    public GameObject childSecond;


    public class ParentTestingBaker : Baker<ParentTesting>
    {
        public override void Bake(ParentTesting authoring)
        {
            //var parentEntity = AddBuffer<ParentBufferData>();

            AddComponentObject(new ParentTestingClass
            {
                parent = authoring.parentGO,
                childFirst = authoring.childFirst,
                childSecond = authoring.childSecond,

                parentEntity = GetEntity(authoring.parentGO),
            });



        }
    }
}

Component

public class ParentTestingClass : IComponentData
{

    public GameObject parent;
    public GameObject childFirst;
    public GameObject childSecond;

    public Entity parentEntity;

}

System

public partial class ParentTestingSystem : SystemBase
{

    public bool testDebug = true;

    protected override void OnStartRunning()
    {
       
    }

    protected override void OnUpdate()
    {

        Entities.ForEach
        ((ParentTestingClass parentTestingClass) =>
        {
            if (testDebug == true)
            {
                Entity newEntity = EntityManager.Instantiate(parentTestingClass.parentEntity);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(newEntity, new FixedString64Bytes(" New Parent "));

                EntityManager.AddComponent<Parent>(newEntity);

                Debug.Log("ParentTestingClass spawned  ");
                testDebug = false;
            }


        }
        ).WithStructuralChanges().WithoutBurst().Run();

    }

}

Parent entity is missing (or invalid).

  • Add some debug steps at baking.
  • Check what entity is added & what is inside the component.
    Also, you don’t need managed components for this.
  • Check what entity is being added as parent in the system.
1 Like

The managed components is just for testing.

I think i was able to create children during runtime, based on this thread.
[Create parent-child at runtime - Unity Forum]( https://discussions.unity.com/t/874142 #:~:text=var%20parentPrefab%20%3D%20GetSingleton%20%3C%20ParentPrefab%20%3E%28%29.Value%3B%20var,GetSingleton%20%3C%20ChildPrefab%20%3E%28%29.Value%3B%20var%20child%20%3D%20EntityManager.Instantiate%28childPrefab%29%3B)

Now i just need to learn and test how to access child entities.
9257997--1295241--upload_2023-8-29_12-14-24.png

New Baker

public class ParentTesting : MonoBehaviour
{
    public GameObject parentGO;
    public GameObject childFirst;
    public GameObject childSecond;


    public class ParentTestingBaker : Baker<ParentTesting>
    {
        public override void Bake(ParentTesting authoring)
        {
            //var parentEntity = AddBuffer<ParentBufferData>();

            AddComponentObject(new ParentTestingClass
            {
                parent = authoring.parentGO,
                childFirst = authoring.childFirst,
                childSecond = authoring.childSecond,

                parentEntity = GetEntity(authoring.parentGO),
                childFirstEntity= GetEntity(authoring.childFirst),
                childSecondEntity= GetEntity(authoring.childSecond),


            });



        }
    }
}

New Component

public class ParentTestingClass : IComponentData
{

    public GameObject parent;
    public GameObject childFirst;
    public GameObject childSecond;

    public Entity parentEntity;
    public Entity childFirstEntity;
    public Entity childSecondEntity;




}

New System

    protected override void OnUpdate()
    {

        Entities.ForEach
        ((ParentTestingClass parentTestingClass) =>
        {
            if (testDebug == true)
            {

                var parentPrefab = parentTestingClass.parentEntity;
                var parent = EntityManager.Instantiate(parentPrefab);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(parent, new FixedString64Bytes(" New Parent Test two "));

                var childPrefab1 = parentTestingClass.childFirstEntity;
                var child1 = EntityManager.Instantiate(childPrefab1);

                var childPrefab2 = parentTestingClass.childSecondEntity;
                var child2 = EntityManager.Instantiate(childPrefab2);

                EntityManager.AddComponentData(child1, new Parent { Value = parent });
                EntityManager.AddComponentData(child1, new LocalTransform() 
                { 
                    Position = new float3(parentTestingClass.parent.transform.position)
         
                });

                EntityManager.AddComponentData(child2, new Parent { Value = parent });
                EntityManager.AddComponentData(child2, new LocalToWorld() 
                { 
                    Value = new float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) 

                });

                // Set Rotation on parent



                Debug.Log("ParentTestingClass spawned  ");
                testDebug = false;
            }


        }
        ).WithStructuralChanges().WithoutBurst().Run();

    }

}

Tried to access the child component from my created parent, but i get this error below. Not really sure how to approach this error. Any suggestion would be nice.

Error

InvalidOperationException: No suitable code replacement generated, this is either due to generators failing, or lack of support in your current context
ParentTestingSystem.ParentTestingSystem_3E95A25C_LambdaJob_0_LambdaBody (ParentTestingClass parentTestingClass) (at Temp/GeneratedCode/Assembly-CSharp/ParentTesting__System_2056218616.g.cs:80)
ParentTestingSystem+ParentTestingSystem_3E95A25C_LambdaJob_0_Job.RunWithStructuralChange (Unity.Entities.EntityQuery query) (at Temp/GeneratedCode/Assembly-CSharp/ParentTesting__System_2056218616.g.cs:44)
ParentTestingSystem.ParentTestingSystem_3E95A25C_LambdaJob_0_Execute () (at Temp/GeneratedCode/Assembly-CSharp/ParentTesting__System_2056218616.g.cs:92)
ParentTestingSystem.OnUpdate () (at Assets/Scenes/Testing Parent/ParentTesting.cs:84)
Unity.Entities.SystemBase.Update () (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/SystemBase.cs:428)
Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/ComponentSystemGroup.cs:670)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/Stubs/Unity/Debug.cs:19)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/ComponentSystemGroup.cs:675)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/ComponentSystemGroup.cs:628)
Unity.Entities.SystemBase:Update() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/SystemBase.cs:416)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.15/Unity.Entities/ScriptBehaviourUpdateOrder.cs:526)

This code cause error (Total system show it in context)

            if (EntityManager.HasBuffer<Child>(parentMain) && (testDebug == 10)) // <------------------- This line cause Error
            {
                var test = SystemAPI.GetBuffer<Child>(parentMain);

                   
                Debug.Log(" test.Length " + test.Length);
            }

Total System

public partial class ParentTestingSystem : SystemBase
{

    public int testDebug = 0;
    public Entity parentMain;
    public Entity child1;
    public Entity child2;


    protected override void OnStartRunning()
    {
        testDebug = 0;
    }

    protected override void OnUpdate()
    {

        Entities.ForEach
        ((ParentTestingClass parentTestingClass) =>
        {
            if (testDebug == 1)
            {

                var parentPrefab = parentTestingClass.parentEntity;
                parentMain = EntityManager.Instantiate(parentPrefab);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(parentMain, new FixedString64Bytes(" New Parent Test two "));
                Debug.Log("ParentTestingClass spawned  ");

                var childPrefab1 = parentTestingClass.childFirstEntity;
                child1 = EntityManager.Instantiate(childPrefab1);

                var childPrefab2 = parentTestingClass.childSecondEntity;
                child2 = EntityManager.Instantiate(childPrefab2);

                EntityManager.AddComponentData(child1, new Parent { Value = parentMain });
                EntityManager.AddComponentData(child1, new LocalTransform() 
                { 
                    Position = new float3(parentTestingClass.parent.transform.position)
         
                });
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(child1, new FixedString64Bytes(" New child1 Test two "));


                EntityManager.AddComponentData(child2, new Parent { Value = parentMain });
                EntityManager.AddComponentData(child2, new LocalToWorld() 
                { 
                    Value = new float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) 

                });
                EntityManager.AddComponent<ChildTestData>(child2);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(child2, new FixedString64Bytes(" New child2 Test two "));




            }

            
            if (EntityManager.HasBuffer<Child>(parentMain) && (testDebug == 10)) // <------------------- This line cause Error
            {
                var test = SystemAPI.GetBuffer<Child>(parentMain);

                   
                Debug.Log(" test.Length " + test.Length);
            }             
             
             




        }
        ).WithStructuralChanges().WithoutBurst().Run();




        testDebug = testDebug + 1;
    }

}

SystemAPI cannot be used from inside of EFE. Use EntityManager.GetBuffer.

1 Like

Thanks, that solved it.

Working code

public partial class ParentTestingSystem : SystemBase
{

    public int testDebug = 0;
    public Entity parentMain;
    public Entity child1;
    public Entity child2;


    protected override void OnStartRunning()
    {
        testDebug = 0;
    }

    protected override void OnUpdate()
    {

        Entities.ForEach
        ((ParentTestingClass parentTestingClass) =>
        {
            if (testDebug == 1)
            {

                var parentPrefab = parentTestingClass.parentEntity;
                parentMain = EntityManager.Instantiate(parentPrefab);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(parentMain, new FixedString64Bytes(" New Parent Test two "));
                Debug.Log("ParentTestingClass spawned  ");

                var childPrefab1 = parentTestingClass.childFirstEntity;
                child1 = EntityManager.Instantiate(childPrefab1);

                var childPrefab2 = parentTestingClass.childSecondEntity;
                child2 = EntityManager.Instantiate(childPrefab2);

                EntityManager.AddComponentData(child1, new Parent { Value = parentMain });
                EntityManager.AddComponentData(child1, new LocalTransform() 
                { 
                    Position = new float3(parentTestingClass.parent.transform.position)
         
                });
                EntityManager.AddComponent<ChildTestData>(child1);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(child1, new FixedString64Bytes(" New child1 Test two "));


                EntityManager.AddComponentData(child2, new Parent { Value = parentMain });
                EntityManager.AddComponentData(child2, new LocalToWorld() 
                { 
                    Value = new float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) 

                });
                EntityManager.AddComponent<ChildTestData>(child2);
                World.DefaultGameObjectInjectionWorld.EntityManager.SetName(child2, new FixedString64Bytes(" New child2 Test two "));




            }

            /**/
            if (EntityManager.HasBuffer<Child>(parentMain) && (testDebug == 10)) // <------------------- This line cause Error
            {
                var test = EntityManager.GetBuffer<Child>(parentMain);
                var getComponent = EntityManager.GetComponentData<ChildTestData>(test[0].Value);

                Debug.Log(" test.Length " + test.Length);
                Debug.Log(" test.Length " + test[0] + "  component:  " + getComponent);

            }






        }
        ).WithStructuralChanges().WithoutBurst().Run();




        testDebug = testDebug + 1;
    }

}

I encountered a problem when i am trying to parent a weapon to a character. The weapon is a parent and have some child entities and the character don’t.

My current knowledge is enough solve this problem my own way, but is it possible to structure the parent/child hierarchy similar to the Mono example below?

Example of intended structure in Mono
9277579--1299517--upload_2023-9-7_4-55-1.png

Code

using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Collections;
using Unity.Physics;
using UnityEngine;


public partial class InteractionSystem : SystemBase
{


    protected override void OnUpdate()
    {
        EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.Temp);
        EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp).WithAll<PhysicsWorldSingleton>();

        var singletonPhysicsQuery = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntityQuery(builder);
        var collisionWorld = singletonPhysicsQuery.GetSingleton<PhysicsWorldSingleton>().CollisionWorld;

        //Look at interation and pick up for Player
        Entities.ForEach
            ((PlayerCameraClass playerCameraClass, ref PlayerInputData playerInputData, ref Entity entity, in LocalTransform localTransform) =>
            {


                ECSRaycastTools.CustomRayCast customRayCast= new ECSRaycastTools.CustomRayCast()
                {
                    collisionWorld = collisionWorld,
                };

                var ray = playerCameraClass.playerCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2,0));
                float3 startPosition = new float3(ray.origin);
                float3 direction = new float3(ray.GetPoint(5));
                Unity.Physics.RaycastHit raycastHit = new Unity.Physics.RaycastHit();

                bool raycastBool = customRayCast.CheckRay(startPosition, direction, out raycastHit, ECSRaycastTools.CustomCollisionLayers.InteractionObject, ECSRaycastTools.CustomCollisionLayers.InteractionObject);
                if ((raycastBool == true))
                {


                    if ((World.DefaultGameObjectInjectionWorld.EntityManager.HasComponent<InteractionData>(raycastHit.Entity) == true) && (playerInputData.inputInteration == true))
                    {
                        var test = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<InteractionData>(raycastHit.Entity);
                        var weaponParent = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<Parent>(raycastHit.Entity).Value;
                        var objectType = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<ObejctData>(weaponParent).objectType;
                        playerInputData.inputInteration = true;

                        if ((playerInputData.inputInteration == true) && (test.interactionType == EnumObjects.InterationType.pickItem))
                        {
                            Debug.Log(" Working ");

                            ecb.SetComponent(raycastHit.Entity, new InteractionData
                            {
                                interactionType = EnumObjects.InterationType.inventory,
                            });

                            ecb.AddComponent(weaponParent, new Parent // <==================  Adding to character
                            {
                                Value = entity,
                            });
                            ecb.AddComponent(weaponParent, new LinkingTestComponent
                            {
                                parent = entity,
                                position = localTransform.Position,

                            });


                        }
                        Debug.Log("Hit " + test + "  parent " + weaponParent + "  Parent ObjectType  " + objectType + "  playerInputData.inputInteration  " + playerInputData.inputInteration);
                    }


                    /*
                                        bool buttonDown = false;
                                        if (playerInputData.inputInteration == true)
                                        {
                                            buttonDown = true;
                                            playerInputData.interationDuration = playerInputData.interationDuration + 0.01f;
                                        }
                                        else
                                        {
                                            if ((World.DefaultGameObjectInjectionWorld.EntityManager.HasComponent<InteractionData>(raycastHit.Entity) == true) && (playerInputData.inputInteration == true))
                                            {
                                                var test = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<InteractionData>(raycastHit.Entity);
                                                var parent = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<Parent>(raycastHit.Entity).Value;
                                                var objectType = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<ObejctData>(parent).objectType;
                                                buttonDown = true;

                                                if ((buttonDown == true) && (playerInputData.interationDuration >= 1) && (test.interactionType == EnumObjects.InterationType.pickItem))
                                                {
                                                    Debug.Log(" Working " + "  interationDuration  " + playerInputData.interationDuration);
                                                    test.interactionType = EnumObjects.InterationType.inventory;
                                                    ecb.AddComponent(parent, new Parent { Value = entity });
                                                }
                                                Debug.Log("Hit "  + test + "  parent " + parent + "  Parent ObjectType  " + objectType);
                                            }

                                            playerInputData.interationDuration = 0;
                                        }
                                        //Debug.Log("playerInputData.interationDuration  " + playerInputData.interationDuration);
                    */


                }

                Debug.DrawLine(startPosition, direction);
            }
            ).WithoutBurst().WithStructuralChanges().Run();


        //Inventory
        builder.Dispose();
        ecb.Playback(EntityManager);
        ecb.Dispose();
    }

}

public struct LinkingTestComponent : IComponentData
{
    public Entity parent;
    public float3 position;

}

Left is the weapon entity and the right is the character entity. The character didn’t get a parent component, but the LinkingTestComponent on the weapon identify the correct entity.

I’ll admit, what you are seeing is weird. But then again, you are using an experimental version of Entities and not the latest 1.0.14. How do I know? Because WorldTransform doesn’t exist in the latest Entities. I suspect that your character not having a LocalToWorld could be part of the problem, but I don’t remember all the details from that buggy mess.

1 Like

Thanks for the reply, will see if adding LocalToWorld when i spawn character might work. Otherwise i will update to the latest version, but there are several things that i know will break.

Was planning to update when i finished some thing.

Adding LocalToWorld solved it.

Now i can finish my prototype and most of my test before updating to latest version.

Thanks for the help.