Hi guys,
Like all of you, I am using prefabs to preconfigure some of my entities. After that, I wrote an EntityPrefab custom implementation to automatically convert them via the custom GameObjectConversionSystem at runtime. And it works, but I feel like that could be optimized a bit and be converted at build time (you can check the warning from the learning course DOTS Best practices (Part 2, point 8 “Runtime data is not the same as authoring data”).
So, the question is, how could I achieve that?
Is it possible to convert those prefabs during build time (in editor it is okay to convert them in play mode, for now)?
Here is my custom implementation to do that with GameObjectConversionSystem:
EntityPrefab.cs
[CreateAssetMenu(fileName = nameof(EntityPrefab), menuName = "ECS/" + nameof(EntityPrefab), order = 0)]
public partial class EntityPrefab : SerializedScriptableObject
{
[OdinSerialize, NonSerialized] private GameObject _prefab;
[OdinSerialize, NonSerialized] private string _prefabEntityName = string.Empty;
private Entity _entity = Entity.Null;
public GameObject Prefab => _prefab;
public string PrefabEntityName => _prefabEntityName;
// Keep in mind, that prefab is disabled by default
public Entity Entity
{
get
{
if (_entity != Entity.Null)
{
return _entity;
}
Debug.LogError($"PrefabEntity is NULL. Check {nameof(EntityPrefab)}({name}) registration in your {nameof(EntityPrefabsHolder)}", this);
return Entity.Null;
}
}
public void SetPrefabEntity(Entity entity)
{
Assert.AreNotEqual(entity, Entity.Null);
_entity = entity;
}
}
EntityPrefabsHolder.cs
[CreateAssetMenu(fileName = nameof(EntityPrefabsHolder), menuName = "ECS/" + nameof(EntityPrefabsHolder), order = 0)]
public partial class EntityPrefabsHolder : SerializedScriptableObject
{
[AssetSelector(IsUniqueList = true, ExcludeExistingValuesInList = true)]
[OdinSerialize, NonSerialized] private List<EntityPrefab> _entityPrefabs = new List<EntityPrefab>();
public IReadOnlyList<EntityPrefab> EntityPrefabs => _entityPrefabs;
private void OnValidate()
{
#if UNITY_EDITOR
Editor_OnValidate();
#endif // UNITY_EDITOR
}
}
DeclareEntityPrefabsGOCS.cs
[UpdateInGroup(typeof(GameObjectDeclareReferencedObjectsGroup))]
public partial class DeclareEntityPrefabsGOCS : GameObjectConversionSystem
{
private EntityPrefabsHolder _prefabsHolder;
protected override void OnCreate()
{
base.OnCreate();
// hack, cause could not get this conversion system in installer
_prefabsHolder = ProjectContext.Instance.Container.Resolve<EntityPrefabsHolder>();
Assert.IsNotNull(_prefabsHolder);
var gameObjectExportGroup = World.CreateSystem<GameObjectExportGroup>();
var assignPrimaryEntityToEntityPrefabGOCS = World.CreateSystem<AssignPrimaryEntityToEntityPrefabGOCS>();
gameObjectExportGroup.AddSystemToUpdateList(assignPrimaryEntityToEntityPrefabGOCS);
}
protected override void OnUpdate()
{
_prefabsHolder.EntityPrefabs.ForEach(entityPrefab => DeclareReferencedPrefab(entityPrefab.Prefab));
}
}
AssignPrimaryEntityToEntityPrefabGOCS.cs
public struct EntityPrefabContainer : IComponentData { }
[DisableAutoCreation]
[UpdateInGroup(typeof(GameObjectExportGroup))]
public partial class AssignPrimaryEntityToEntityPrefabGOCS : GameObjectConversionSystem
{
private EntityPrefabsHolder _prefabsHolder; // could not Inject
private Entity _entityPrefabContainer = Entity.Null;
protected override void OnCreate()
{
base.OnCreate();
// hack, cause could not get this conversion system in installer
_prefabsHolder = ProjectContext.Instance.Container.Resolve<EntityPrefabsHolder>();
Assert.IsNotNull(_prefabsHolder);
Assert.AreEqual(_entityPrefabContainer, Entity.Null);
_entityPrefabContainer = DstEntityManager.CreateEntity("EntityPrefabContainer" ,typeof(EntityPrefabContainer), typeof(Child), typeof(Prefab));
}
protected override void OnUpdate()
{
foreach (var entityPrefab in _prefabsHolder.EntityPrefabs)
{
var prefabEntity = GetPrimaryEntity(entityPrefab.Prefab);
if (prefabEntity != Entity.Null)
{
entityPrefab.SetPrefabEntity(prefabEntity);
if (!string.IsNullOrEmpty(entityPrefab.PrefabEntityName))
{
DstEntityManager.SetNameSafe(prefabEntity, entityPrefab.PrefabEntityName);
}
DstEntityManager.AddComponentData(prefabEntity, new Parent { Value = _entityPrefabContainer });
DstEntityManager.SetEnabled(prefabEntity, false);
}
else
{
Debug.LogError($"Primary entity for prefab `{entityPrefab.name}` is NULL", entityPrefab);
}
}
}
}