I’m currently facing an issue while working on my Unity ECS project, specifically related to the GetSingletonEntity method. My setup involves using the Baker and BakingSystem to bake GameObjects that contain ScriptableObjects storing prefabs. The Baker adds a tag component to each baked package entity of the prefabs, leading to each package entity having a unique tag. Additionally, the BakingSystem adds other necessary components to the entity prefabs.
There are two main problems I’m encountering:
Visibility of Package Entities: In the Entities Hierarchy, I am aware that entity prefabs are automatically assigned a Prefab tag and are ignored by default in queries. However, package entities, which lack the Prefab tag, are still only visible when I use the “+IncludePrefab” filter. This is contrary to my understanding, as I expected only entity prefabs to require this filter for visibility.
GetSingletonEntity Issue: Furthermore, I’m unable to use the GetSingletonEntity method effectively to retrieve the package entity. Instead, I encounter the following exception: GetSingleton() requires that exactly one entity exists that matches this query, but there are 0.
public class MobEntityPropertiesAuthoring : MonoBehaviour
{
public GameObject prefab;
public float prefabCollisionRadius;
public byte healthPointAtBirth;
}
[BakingType]
public struct MobEntityPropertiesBaking : IComponentData
{
public byte HealthPointAtBirth;
}
public class MobEntityPropertiesBaker : Baker<MobEntityPropertiesAuthoring>
{
public override void Bake(MobEntityPropertiesAuthoring authoring)
{
var mobEntityPrefab = authoring.prefab;
DependsOn(mobEntityPrefab);
var mobPackageEntity = GetEntity(TransformUsageFlags.None);
AddComponent(mobPackageEntity, new GameEntityGenerationProperties
{
Prefab = GetEntity(mobEntityPrefab, TransformUsageFlags.Renderable),
PrefabCollisionRadius = authoring.prefabCollisionRadius,
});
AddComponent(mobPackageEntity,
Type.GetType("Components.Tags." + mobEntityPrefab.name)); //not supported by burst
AddComponent(mobPackageEntity, new MobEntityPropertiesBaking
{
HealthPointAtBirth = authoring.healthPointAtBirth
});
}
}
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
[RequireMatchingQueriesForUpdate]
public partial struct EntityPrefabBakingSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (gameEntityGenerationProperties, mobEntityPropertiesBaking) in SystemAPI
.Query<GameEntityGenerationProperties, MobEntityPropertiesBaking>())
{
var mobEntityPrefab = gameEntityGenerationProperties.Prefab;
ecb.AddSharedComponent(mobEntityPrefab, new MobEntityPropertiesShared
{
MaxHealthPoint = mobEntityPropertiesBaking.HealthPointAtBirth
});
ecb.AddComponent(mobEntityPrefab, new MobEntityCurrentState
{
CurrentHealthPoint = mobEntityPropertiesBaking.HealthPointAtBirth
});
}
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
}
var enemyOneGenerationProperties = state.EntityManager.GetComponentData<GameEntityGenerationProperties>(SystemAPI.GetSingletonEntity<EnemyOne>());
var enemyOneEntity = ecb.Instantiate(enemyOneGenerationProperties.Prefab);
ecb.SetComponent(enemyOneEntity, new LocalTransform
{
Position = obstaclePosition,
Rotation = quaternion.identity,
Scale = 2
});
var enemyTwoGenerationProperties = state.EntityManager.GetComponentData<GameEntityGenerationProperties>(SystemAPI.GetSingletonEntity<EnemyTwo>());
var enemyTwoEntity = ecb.Instantiate(enemyTwoGenerationProperties.Prefab);
......
Obstacle is a component not an entity, you would have to do a query or a foreach to get to the component data - maybe a different approach to the baking? I use a config approach to passing prefabs into ECS, you really don’t need scriptable objects at all. Follow this example and see if you can covert things over.
In my understanding, SystemAPI.GetSingletonEntity() should retrieve a singleton entity, which is the only entity that has the Obstacle component. Is that correct?
Edit:
Sorry for the misunderstanding. I have extended the code to make it more clear and added a clarification screenshot.
I will try using the authoring GameObject directly instead of both the authoring GameObject and scriptable objects. This will reduce the complexity of the code. Actually, I’m a bit confused about whether or not to use scriptable objects, especially considering reasons related to data serialization and the potential for future expansion.
Do you definitely have a GameObject in your Subscene with the above baker attached? Looks like the baker is never running as there are no matches to your query.
I think you would also want to use SystemAPI.GetSingleton() instead of getting the entity then getting the component data on it.
You can also stop the InvalidOperationException by adding state.RequireForUpdate() on the OnCreate method in the system.
Sorry for the misunderstanding. I have extended the code to make it more clear and added a clarification screenshot.
I confirm that I added the authoring component to a GameObject in the subscene, and I can see the baked authoring GameObject in the Entities Hierarchy.
Like I said in #1, I have multiple prefabs, so I have multiple entity prefabs. This means that SystemAPI.GetSingleton() will not work.
Because ECS does not allow adding components to a prefab entity in the baker, I need the package entity to store temporary components for baking. This allows the BakingSystem to add in-game character stats components (like HP) to the prefab entities.
I could move all the other non-essential data to the prefab entity within the BakingSystem, but it would be a redundant action and would increase code complexity in the future.
The official docs suggest using package entities. Is it because entity prefabs with a Prefab tag are ignored by queries by default? Or is it more related to situations like mine, where I need a package? I’m not entirely sure why.
Sorry for the misunderstanding. I have extended the code to make it more clear and added a clarification screenshot.
The ‘Obstacle’ tag is actually successfully added to the package entity by the code AddComponent(packageEntity, Type.GetType(“Components.Tags.” + authoring.gameObjectPropertiesScriptable.name));.
Other tags are added in the same way.
Just like AddComponent(packageEntity, new Obstacle); or AddComponent(packageEntity, typeof(Obstacle));, although it is not compatible with Burst.