Dynamic Entity Archetypes using Scriptable Objects

Done a bit of cleanup recently with code and thought someone might be interested in my alternate approach to defining entities dynamically - using ScriptableObjects! I’ve been using a similar blueprint system to create entities for a while now, even before the ECS package was even released but I’ve converted it to be used with Unity’s pure entities.

Instead of defining archetypes in code, each type of my entity gets it’s own blueprint and a collection of installers, which are all ScriptableObjects. [To achieve this I use Zenject Subcontainers just because I love Zenject]

Note: the following screenshots are from my demo not my actual project, they are a bit stripped back without custom editors and only basic components.

3610572--293473--upload_2018-8-27_10-26-58.png

Each blueprint has a collection of installers that add ComponentData, SharedComponentData or even BufferArrays.

3610572--293474--upload_2018-8-27_10-27-23.png

This MeshInstanceRender is defined as

    [CreateAssetMenu(fileName = "MeshInstanceRenderer", menuName = "BovineLabs/Entities/Component/Mesh Instance Renderer")]
    public class MeshInstanceRendererBlueprint : ComponentBlueprint
    {
        [SerializeField] private Mesh _mesh;
        [SerializeField] private Material _material;
 
        public override void Install(DiContainer container)
        {
            container.BindSharedComponent<MeshInstanceRenderer>().WithValue(new MeshInstanceRenderer
            {
                mesh = _mesh,
                material = _material
            });
        }
    }

The BindSharedComponent creates a binding for a ComponentType which will be used to create an archetype.

The WithValue method is optional, but if used will automatically call EntityManager.Set[Shared]ComponentData with your value once the entity is created.

Now this specific installer only installers 1 component, but they’re not limited to that. Sometimes you might want multiple components installed at once, for instance the parts of a mesh could be defined in 4 buffer arrays.

    [CreateAssetMenu(fileName = "Mesh", menuName = "BovineLabs/Entities/Component/Mesh")]
    public class MeshBlueprint : ComponentBlueprint
    {
        public override void Install(DiContainer container)
        {
            container.BindBufferArray<Face>().WithLength(6);
            container.BindBufferArray<Vertex>();
            container.BindBufferArray<Uv>();
            container.BindBufferArray<Normal>();
            container.BindBufferArray<Triangle>();
        }
    }

WithLength should not be confused with [InternalBufferCapacity(6)]. SetLength will initialize with
buffer.ResizeUninitialized(6) so you don’t need to worry about the length and can use it like a fixed array instead of a List.

Once blueprints are setup, you can create them with the factory, just passing a world (Optional) and a blueprint to the factory.

            public Init(EntityFactory factory)
            {
                var entity1 = factory.Create(_blueprint); // Will default to World.Active
                var entity2 = factory.Create(_blueprint, World.Active); // Can choose the your world as well
            }

You can obviously also set or add runtime specific component values as well once you’ve created your entity. For example setting the position of a unit that has just spawned.

entityManager.SetComponentData(entity1, new Position {Value = new float3(1, 2, 3)});

Under the hood, this will create an archetype like normal using the components bound in each installer. After the entity is created, it’ll go through and set and default values that were requested.

I do not claim this is an ideal way to define entities but from my experience I really like how it works.
Why use this over Prefabs (ComponentDataWrapper)? Easier to share data between entities and better support for custom inspectors.
Why use this over defining in source? Let’s team members who aren’t developers easily create new entities.

Can you try it?
Sure, I have a test version which is a bit unorganized but it is stripped of my project specific parts and 3rd party plugins (except Zenject as it’s a requirement).

Is this for you?
Probably not unless you want to add Zenject to your project.

I tried the demo, my blueprint list doesn’t look like yours?
I use odin inspector which I obviously can’t include.

2 Likes

Nice work! On my side I went with the ComponentDataWrapper approach which also works well with minimum effort.
I might try your approach as well since I would also prefer to use ScriptableObject for this kind of thing.

Did you consider having a way to call a blueprint on an existing entity ? For instance to add missing components froma blueprint, or override existing values.

I hate to necro this, but @tertle this looks really interesting! I noticed your GitHub link isn’t working anymore, is this still available publicly? Would love to see the code for ComponentBlueprint, etc

For some context, I’m searching for a way to store and edit entity “instances” via the editor, and custom ScriptableObjects like this seems to be the best way to accomplish that at the moment. The ideal design workflow would include serialized entity files with custom editors for each of their components available to pre-load data into the entity.

Right now I can’t fit ECS into a design workflow at all since nothing is serialized. Kind of feels like GameObjectEntity was made to transition Monobehaviours into ECS but there’s no ready made solution for ScriptableObjects. I don’t want Transform components on all my entities, so saving a prefab with GameObjectEntity on it isn’t the right way to go.

Don’t know why I set the repo private, it’s public again. It hasn’t been updated in 8 months though so will be out of date but you’re welcome to inspect it.

Just note I don’t use this anymore as I now use game object conversion.

Thank you and good to know, that makes sense. I just found out about the StaticOptimizeEntity component, which removes my biggest concern with game object conversion.

Does it help to store Archetype database?