[Baking] Adding components to entities of child game objects in a prefab during baking

I have a game object prefab which has a child game object.

During baking of the parent prefab (game object) I wish to get the entity of this child game object and add an entity component to it.

I get the error:
"InvalidOperationException: Entity Entity.Null doesn’t belong to the current authoring …‘’

Is there any way to easily add entity components to child game objects during baking instead of making an separate authoring script for each of them also?

Isn’t a dynamic buffer of linked entities created on the root entity?
Or in fact you may want access children Dynamic Buffer, to get children of given enity. Is not that what haapens in your case?

I what case this happens?
At the baking time of nested prefabs?
Or you try access invalid entity in other way?

The problem isn’t “acessing child entities”. It’s modifying them: adding a new component to the child entity from the baking (authoring) script of the parent.

Yes.

Basicaly I do this inside the Bake function of the parent (main) prefab:

 var ChildEntity = GetEntity(Authoring.MyChildGameObject, TransformUsageFlags.Dynamic);

// Errors
 AddComponent(ChildEntity , new SomeMyComponent()));

You can’t do this in bakers. If you must have this behavior you’d need to do it in a baking system.

Bakers can only modify themselves or additional entities they have created.

Thank you.

But how to differentiate children(say we have root element of prefab and children are some 3d models) in baking system, since we cannot add tag components to children in baker(or is there a way to do so)?
[sorry for necroing]

1 Like

You just add authoring Components on the prefabs.
Each prefab and sub prefabs have own baking.
Then you can add any components during baking, as you like.

Hi @Antypodish

I have a model of a player, this model has a torso, head, and arms and legs divided into two parts. At the moment, when I spawn an entity in the system, I check the global coordinates of each of the child elements of the instance prefab, with the coordinates that were obtained during baking (the internal elements are nested inside each other, forming something like a skeleton), thereby I add a component to each instance of the player entity. which contains references to the entities of its specific body parts. And it works great, but is there any easier way to create it?

If you need IComponent like hand, leg, head, then you need to add them during baking (easiest).

But if you referring to local / global positions, nested entities are part of linked dynamic buffer.
Each nested entity has local transforms components, to identify position / rotation.
So you don’t need setup manually transforms.
Just remember to set inside baking GetEntity(TransformUsageFlags.Dynamic);

I am not sure, if that what you meant? But you may need to elaborate a bit more, if I miss understood you.

1 Like

Hi @Antypodish, thank you for your reply!


Let’s just say that inside the [BurstCompile] ISystem, I want to access the transformation of every part of the body, how can I do this better?

[BodyAuthoring.cs]

using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;

public struct ComponentCube : IComponentData { }
public struct ComponentCapsule : IComponentData
{
	public float CurrentFrame;
}
public struct InstanceTime : IComponentData
{
	public float lifeTime;
}

public struct BodyAuthoringData : IComponentData
{
	public Entity EntityPrefab;

	public float3 PosBody;

	public float3 PosHead;

	public float3 PosHand_R_1;
	public float3 PosHand_R_2;

	public float3 PosHand_L_1;
	public float3 PosHand_L_2;

	public float3 PosLeg_R_1;
	public float3 PosLeg_R_2;

	public float3 PosLeg_L_1;
	public float3 PosLeg_L_2;

	public override readonly string ToString()
	{
		return
			$"PosBody:{PosBody}\n" +
			$"PosHead:{PosHead}\n" +
			$"PosHand_R_1:{PosHand_R_1}\n" +
			$"PosHand_R_2:{PosHand_R_2}\n" +
			$"PosHand_L_1:{PosHand_L_1}\n" +
			$"PosHand_L_2:{PosHand_L_2}\n" +
			$"PosLeg_R_1:{PosLeg_R_1}\n" +
			$"PosLeg_R_2:{PosLeg_R_2}\n" +
			$"PosLeg_L_1:{PosLeg_L_1}\n" +
			$"PosLeg_L_2:{PosLeg_L_2}";
	}
}

public struct BodyPartsData : IComponentData
{
	public Entity EntityBody;

	public Entity EntityHead;

	public Entity EntityHand_R_1;
	public Entity EntityHand_R_2;

	public Entity EntityHand_L_1;
	public Entity EntityHand_L_2;

	public Entity EntityLeg_R_1;
	public Entity EntityLeg_R_2;

	public Entity EntityLeg_L_1;
	public Entity EntityLeg_L_2;
}

public class BodyAuthoring : MonoBehaviour
{
    [Tooltip("Префаб тела")]
    public GameObject PrefabBody;

    public float3 GetPos(BodyTypes type)
	{
		if (PrefabBody == null) Debug.LogError($"[{nameof(PrefabBody)}] is null");

		var part = FindObjectByName(PrefabBody.transform, type.ToString());
		if (part == null) Debug.LogError($"[{type}] not found in [{PrefabBody.name}]");

        return part.transform.position; // Возвращает нулевой вектор, если объект не найден
    }

    public GameObject FindObjectByName(Transform parent, string name)
    {
        if (parent.name == name)
        {
            return parent.gameObject;
        }

        for (int i = 0; i < parent.childCount; i++)
        {
            Transform child = parent.GetChild(i);
            GameObject result = FindObjectByName(child, name);
            if (result != null)
            {
                return result;
            }
        }
        return null;
    }
}

public enum BodyTypes
{
	Body,

	Head,

	Hand_R_1,
	Hand_R_2,

	Hand_L_1,
	Hand_L_2,

	Leg_R_1,
	Leg_R_2,

	Leg_L_1,
	Leg_L_2,
}

public class PrefabSimpleDetailedBaker : Baker<BodyAuthoring>
{
	public override void Bake(BodyAuthoring authoring)
	{
		var entityBodyAuthoring = GetEntity(TransformUsageFlags.Dynamic);

		AddComponent(entityBodyAuthoring, new BodyAuthoringData
		{
			EntityPrefab = GetEntity(authoring.PrefabBody, TransformUsageFlags.Dynamic),

			PosBody = authoring.GetPos(BodyTypes.Body),

			PosHead = authoring.GetPos(BodyTypes.Head),

			PosHand_L_1 = authoring.GetPos(BodyTypes.Hand_L_1),
			PosHand_L_2 = authoring.GetPos(BodyTypes.Hand_L_2),

			PosHand_R_1 = authoring.GetPos(BodyTypes.Hand_R_1),
			PosHand_R_2 = authoring.GetPos(BodyTypes.Hand_R_2),

			PosLeg_L_1 = authoring.GetPos(BodyTypes.Leg_L_1),
			PosLeg_L_2 = authoring.GetPos(BodyTypes.Leg_L_2),

			PosLeg_R_1 = authoring.GetPos(BodyTypes.Leg_R_1),
			PosLeg_R_2 = authoring.GetPos(BodyTypes.Leg_R_2),
		});
	}
}

[BodyAnimationSystem.cs]

using Unity.Burst;
using Unity.Entities;
using Unity.Transforms;

[BurstCompile]
public partial struct BodyAnimationSystem : ISystem
{
	[BurstCompile]
	public void OnCreate(ref SystemState state)
	{
		state.RequireForUpdate<BodyPartsData>();
	}

	[BurstCompile]
	public void OnUpdate(ref SystemState state)
	{
		var delta = SystemAPI.Time.DeltaTime;
		foreach (var parts in SystemAPI.Query<RefRW<BodyPartsData>>())
		{
			var hand_L = parts.ValueRW.EntityHand_L_1;
			var tran = state.EntityManager.GetComponentData<LocalTransform>(hand_L);
			state.EntityManager.SetComponentData(hand_L, tran.RotateX(delta*5));

			var hand_R = parts.ValueRW.EntityHand_R_1;
			var tran_r = state.EntityManager.GetComponentData<LocalTransform>(hand_R);
			state.EntityManager.SetComponentData(hand_R, tran_r.RotateX(-delta*5));
		}
	}
}

I think you are on the right direction.
As you did, basically you want to create reference on the parent character to your each body part.
So like your left / right hand become an entity children reference on the parent entity of BodyPartsData component.
Therefor it is right approach for that use case.

Then in the ISystem you access these body part as you did and set their values as desired.

From performance stand point, that is probably one of the fastest way to access referenced children data, while keeping it easy to read and maintain.

Side note, I personalny would change EntityManager to ComponentLookup, then your code inside job would be a bit less verbose.

1 Like

Wow, I think I did something right) Thanks!

Thanks for the hint with ComponentLookup.

And one more question, if I add links to Authoring body parts, will it work, or will it be perceived as separate entities rather than child parts of a common body?

public class BodyAuthoring : MonoBehaviour
{
    [Tooltip("Префаб тела")]
    public GameObject PrefabBodyMain;

	private GameObject PrefabHead;
	private GameObject PrefabBody;

	private GameObject PrefabHand_L_1;
	private GameObject PrefabHand_L_2;
	private GameObject PrefabHand_R_1;
	private GameObject PrefabHand_R_2;

	private GameObject PrefabLeg_L_1;
	private GameObject PrefabLeg_L_2;
	private GameObject PrefabLeg_R_1;
	private GameObject PrefabLeg_R_2;

	public float3 GetPos(BodyTypes type)
	{
		if (PrefabBodyMain == null) Debug.LogError($"[{nameof(PrefabBodyMain)}] is null");

		var part = FindObjectByName(PrefabBodyMain.transform, type);
		if (part == null) Debug.LogError($"[{type}] not found in [{PrefabBodyMain.name}]");

        return part.transform.position; // Возвращает нулевой вектор, если объект не найден
    }

    public GameObject FindObjectByName(Transform parent, BodyTypes types)
    {
		string name = types.ToString();
        if (parent.name == name)
        {
            return parent.gameObject;
        }

        for (int i = 0; i < parent.childCount; i++)
        {
            Transform child = parent.GetChild(i);
            GameObject result = FindObjectByName(child, types);
            if (result != null)
            {
                return result;
            }
        }
        return null;
    }
}
public class PrefabSimpleDetailedBaker : Baker<BodyAuthoring>
{
	public override void Bake(BodyAuthoring authoring)
	{
		var entityBodyAuthoring = GetEntity(TransformUsageFlags.Dynamic);

		AddComponent(entityBodyAuthoring, new BodyAuthoringData
		{
			EntityPrefab = GetEntity(authoring.PrefabBodyMain, TransformUsageFlags.Dynamic),

			PosBody = authoring.GetPos(BodyTypes.Body),

			PosHead = authoring.GetPos(BodyTypes.Head),

			PosHand_L_1 = authoring.GetPos(BodyTypes.Hand_L_1),
			PosHand_L_2 = authoring.GetPos(BodyTypes.Hand_L_2),

			PosHand_R_1 = authoring.GetPos(BodyTypes.Hand_R_1),
			PosHand_R_2 = authoring.GetPos(BodyTypes.Hand_R_2),

			PosLeg_L_1 = authoring.GetPos(BodyTypes.Leg_L_1),
			PosLeg_L_2 = authoring.GetPos(BodyTypes.Leg_L_2),

			PosLeg_R_1 = authoring.GetPos(BodyTypes.Leg_R_1),
			PosLeg_R_2 = authoring.GetPos(BodyTypes.Leg_R_2),
		});

		AddComponent(entityBodyAuthoring, new BodyPartsData
		{
			EntityBody = GetEntity(authoring.FindObjectByName(
				authoring.PrefabBodyMain.transform, BodyTypes.Body), TransformUsageFlags.Dynamic),

			EntityHand_L_1 = GetEntity(authoring.FindObjectByName(
				authoring.PrefabBodyMain.transform, BodyTypes.Hand_L_1), TransformUsageFlags.Dynamic),

			EntityHand_L_2 = GetEntity(authoring.FindObjectByName(
				authoring.PrefabBodyMain.transform, BodyTypes.Hand_L_2), TransformUsageFlags.Dynamic),

			EntityHand_R_1 = GetEntity(authoring.FindObjectByName(
				authoring.PrefabBodyMain.transform, BodyTypes.Hand_R_1), TransformUsageFlags.Dynamic),

			EntityHand_R_2 = GetEntity(authoring.FindObjectByName(
				authoring.PrefabBodyMain.transform, BodyTypes.Hand_R_2), TransformUsageFlags.Dynamic),
		});
	}
}

I’m trying to specify the body components during baking, but it looks like the prefab elements are specified in the entity when baking, inside the onUpdate() system, not the body parts of the instance are updated, but the prefab itself.
Is there any way to specify these links to body parts during baking, or is searching for body parts by global position the only option after spawning an instance?

What I would do in such case, is:

public override void Bake(BodyAuthoring authoring)
	{
		public GameObject hand_L;
		public GameObject hand_R;
		...
		etc.
		...

		var entityBodyAuthoring = GetEntity(TransformUsageFlags.Dynamic);

		AddComponent(entityBodyAuthoring, new BodyAuthoringData
		{
			EntityPrefab = GetEntity(authoring.PrefabBodyMain, TransformUsageFlags.Dynamic);
			
			// Get linked entities
			Entity hand_L = GetEntity(authoring.hand_L, TransformUsageFlags.Dynamic);
			Entity hand_R = GetEntity(authoring.hand_R, TransformUsageFlags.Dynamic);
			...
			etc.
			...

			AddComponent(entityBodyAuthoring, new BodyPartsData
			{
				hand_L = hand_L,
				hand_R = hand_R
				...
				etc.
				...
			});
		}
	}

That way you don’t need for FindObjectByName.
You may also add authoring for baking to other body parts of GameObject(s).

1 Like

Thanks for another reply!

I think I did almost the same thing, only you have a call directly through GameObject, and I have a name search. In both cases, we get the right body parts.

But as I understand it, the problem is that we get the essence of the prefab, not the instance, and because of this, to the movement or animation system when updating the position, let’s say the hands of a particular instance, we get a link not to the essence of the hand of the instance, but to the essence of the hand of the baked prefab. As a result, when the transformation of this entity is changed, this transformation is applied to all instances, because we are changing the prefab, not the instance.

This is just my guess, correct me if I’m wrong.