Hi,
I read quite a few articles on ECS and looked on a way to instantiate Prefab through a Scriptable Object and without a dedicated GameObject spawner.
Here is how I deal with this goal as I cannot find any example on the web. Code is inspired from various sources and mainly from : “Entity Prefab” Management – COFFEE BRAIN GAMES
This is perhaps a naive approach but it works on LTS 2019.4.5f1 and ECS 0.11.1-preview.4 / Hybrid Renderer 0.5.2
To be noted that I am just an hobbyist without any coding experience, so this code may certaintly be improved.
Creation of the S.O
The Scriptable Object is used to keep track of the prefabs and identified them with a convenient Id.
The SO is stored in the “Resources” sub-folder, and it is called “04.PrefabList”.
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public struct EntityPrefabItem
{
public string id;
public GameObject prefab;
}
// You can change the menu location
[CreateAssetMenu(menuName = "ECS_DiversTests/EntityPrefabItemsHolder")]
public class EntityPrefabItemsHolder : ScriptableObject
{
[SerializeField] private List<EntityPrefabItem> prefabs;
public IReadOnlyList<EntityPrefabItem> Prefabs
{
get { return this.prefabs; }
}
}
BlobManager
The BlobArray is made from a Struct with contains two variables. A FixedString64 to store the Prefab Id, and a Entity to store the Prefab.
Initialisation of the Blob is made within the “InitializationSystemGroup” through a SystemBase.
Call of the prefab information when instantiating is dependant of another SystemBase which is part of the “SimulationSystemGroup”
using System;
using UnityEngine;
using Unity.Entities;
using Unity.Collections;
using Unity.Assertions;
namespace test04
{
public struct PrefabItem
{
public FixedString64 itemName;
public Entity itemPrefab;
}
public struct PrefabBlobAsset
{
public BlobArray<PrefabItem> blobPrefabArray;
}
[UpdateInGroup(typeof(InitializationSystemGroup))]
public class PrefabBlobCreation : SystemBase
{
public static BlobAssetReference<PrefabBlobAsset> prefabBlobReference;
private int prefabNumberToAllocate;
protected override void OnStartRunning()
{
EntityPrefabItemsHolder prefabHolderSO;
prefabHolderSO = Resources.Load<EntityPrefabItemsHolder>("04.PrefabList");
Assert.IsNotNull(prefabHolderSO);
this.prefabNumberToAllocate = prefabHolderSO.Prefabs.Count;;
}
protected override void OnUpdate()
{
using (BlobBuilder blobBuilder = new BlobBuilder(Allocator.Temp))
{
// Construction de la racine avec allocation de la mémoire
ref PrefabBlobAsset prefabBlobAsset = ref blobBuilder.ConstructRoot<PrefabBlobAsset>();
BlobBuilderArray<PrefabItem> blobBuilderArray = blobBuilder.Allocate(ref prefabBlobAsset.blobPrefabArray, this.prefabNumberToAllocate);
// Création de la référence
prefabBlobReference = blobBuilder.CreateBlobAssetReference<PrefabBlobAsset>(Allocator.Persistent);
}
Enabled = false;
}
}
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class PrefabBlobManagerSystem : SystemBase
{
protected override void OnUpdate()
{
}
/// <summary>
/// Search an return an entity corresponding to the Identifiant
/// </summary>
/// <param name="itemName">FixedString64 value</param>
public Entity GetEntityPrefab(ref PrefabBlobAsset blobAsset, FixedString64 itemName)
{
for (int i = 0; i < blobAsset.blobPrefabArray.Length; i++)
{
if (blobAsset.blobPrefabArray[i].itemName == itemName)
return blobAsset.blobPrefabArray[i].itemPrefab;
}
throw new Exception($"The prefab pool does not contain an entry for {itemName}");
}
}
}
Conversion of Prefabs into Entities
The conversion of Prefabs from the S.O. is made during the “InitializationSystemGroup” and after the creation of the BlobArray.
First step is to reference the S.O.
Second step is to convert the Prefabs into Entities thanks to the “ConvertGameObjectHierarchy” command and to store both the ID and the resulting entity in the BlobArray.
using UnityEngine;
using Unity.Entities;
using Unity.Assertions;
namespace test04
{
[UpdateInGroup(typeof(InitializationSystemGroup))]
[UpdateAfter(typeof(PrefabBlobCreation))]
public class PrefabConverterSystem : SystemBase
{
private EntityPrefabItemsHolder _prefabHolderSO;
protected override void OnStartRunning()
{
_prefabHolderSO = Resources.Load<EntityPrefabItemsHolder>("04.PrefabList");
Assert.IsNotNull(_prefabHolderSO);
}
protected override void OnUpdate()
{
ref BlobAssetReference<PrefabBlobAsset> prefabBlobRef = ref PrefabBlobCreation.prefabBlobReference;
var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
for (int i = 0; i < _prefabHolderSO.Prefabs.Count; ++i)
{
EntityPrefabItem item = _prefabHolderSO.Prefabs[i];
Entity prefabConverted = GameObjectConversionUtility.ConvertGameObjectHierarchy(item.prefab, settings);
EntityManager.SetName(prefabConverted, item.id);
prefabBlobRef.Value.blobPrefabArray[i] = new PrefabItem
{
itemName = item.id,
itemPrefab = prefabConverted
};
}
Enabled = false;
}
}
}
Instantiation & use of the new Entities
Now, we can instantiate the new entities through a basic SystemBase by requesting the BlobArray.
using UnityEngine;
using Unity.Entities;
namespace test04
{
public class PrefabBlobAnimationSystem : SystemBase
{
private BeginInitializationEntityCommandBufferSystem _entityCommandBufferSystem;
private PrefabBlobManagerSystem _prefabBlobManager;
protected override void OnCreate()
{
_entityCommandBufferSystem = this.World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
_prefabBlobManager = this.World.GetOrCreateSystem<PrefabBlobManagerSystem>();
}
protected override void OnUpdate()
{
var commandBuffer = _entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();
// Press Left Ctrl to instantiate
if (Input.GetKeyDown(KeyCode.LeftControl))
{
Entity entityPrefab = _prefabBlobManager.GetEntityPrefab(ref PrefabBlobCreation.prefabBlobReference.Value, "CubeTest04");
this.EntityManager.Instantiate(entityPrefab);
}
_entityCommandBufferSystem.AddJobHandleForProducer(Dependency);
}
}
}
I hope it can be useful.