Creating System Spawners to populate Level

While building the level of the game Plant Jumper i´ve been building, at first i decided to build an amount of items and then add another row of blocks, removing the very old ones.
This approach after a bunch of hours trying to understand how to create a simple and optimized solution led me to learn a little bit of the ECS way of think.
The first thing i decided to do it is to create a system able to create and remove itens in a coherent way, doing it every time the same way

I started deciding that the Level must be an entity because it will  deal with informations about the quality of the level and how many rows we already created.

public struct LevelState : IComponentData
{
    public float currentRow;
    public float higherRow;

    public float isDirty;
}

This component will be responsible to create the level from the scratch

In the Bootstrap, creates a entity with that component and set the currentRow = 0, hightRow wich defines the size of items we will pre create and isDirty that will be explained further

SetComponentData(level, new LevelState
        {
            currentRow = 0,
            higherRow = constants.maxRows,
            isDirty = 0
        });

Remove old items system


using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;

using Unity.Rendering;

using System.Collections;
using System.Collections.Generic;
using Unity.Jobs;

using Unity.Mathematics;

public class UpdateLevelSystem : ComponentSystem
{
    public struct PlayerData
    {
        public readonly int Length;
        [ReadOnly] public ComponentDataArray<Player> Player;
        [ReadOnly] public ComponentDataArray<Position> Position;
    }

    [Inject] private PlayerData m_PlayerData;

    public struct ItemData
    {
        public readonly int Length;
        public EntityArray Entities;
        public ComponentDataArray<Item> Item;
        public ComponentDataArray<Position> Position;
    }
    [Inject] private ItemData m_ItemData;

    public struct LevelData
    {
        public readonly int Length;
        public ComponentDataArray<LevelState> LevelState;
    }

    [Inject] private LevelData m_LevelData;

    protected override void OnUpdate()
    {
        for (int index = 0; index < m_ItemData.Length; ++index)
        {
            if (m_PlayerData.Position[0].Value.y - m_ItemData.Position[index].Value.y > 50)
            {
                PostUpdateCommands.DestroyEntity(m_ItemData.Entities[index]);

                LevelState state = m_LevelData.LevelState[0];

                if (state.isDirty == 0 && state.currentRow > 0)
                {
                    m_LevelData.LevelState[0] = new LevelState
                    {
                        currentRow = state.currentRow - 1,
                        isDirty = 1,
                        higherRow = state.higherRow
                    };
                }
            }
        }
    }
}

This system do two things

  • Removes the items with a distance greater than an amount, here defined as 50
  • Set the level component once per row as dirty and set the new currentrow. The is dirty is here to avoid the changing every block removed once it is in the same row as the previous one.

Add new items system

As we defined the maximun rows we want, it is easy to create a system that mantain this amount of rows existing and if anything removes a row, creates a new one


using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;

using Unity.Rendering;

using System.Collections;
using System.Collections.Generic;
using Unity.Jobs;

using Unity.Mathematics;

public class SpawnItemsSystem : ComponentSystem
{
    public struct LevelData
    {
        public readonly int Length;
        public ComponentDataArray<LevelState> LevelState;
    }

    [Inject] private LevelData m_LevelData;

    private void SetItemBaseComponent(LevelGenerator.Item item)
    {
        PostUpdateCommands.SetComponent(new Position { Value = item.position });
        PostUpdateCommands.SetComponent(default(Item));
    }

    private void createBlock()
    {
        string entityName = "block";

        PostUpdateCommands.CreateEntity(ArchetypeFactory.Instance.getArchetypeByName(entityName));

        MeshInstanceRenderer renderer = EntityLookFactory.Instance.getLook(entityName);
        PostUpdateCommands.SetComponent(EntityFactory.getColliderInfo(renderer));
        PostUpdateCommands.SetSharedComponent(renderer);
    }
    private void createBreakeable()
    {
        GameObject GameConstantsObject = GameObject.Find("GameConstants");
        GameConstants _constants = GameConstantsObject.GetComponent<GameConstants>();

        string entityName = "breakeable";
        PostUpdateCommands.CreateEntity(ArchetypeFactory.Instance.getArchetypeByName(entityName));

        PostUpdateCommands.SetComponent(new BreakComponent
        {
            coolDown = _constants.breakTimeInSeconds,
            started = 0
        });

        MeshInstanceRenderer renderer = EntityLookFactory.Instance.getLook(entityName);
        PostUpdateCommands.SetComponent(EntityFactory.getColliderInfo(renderer));
        PostUpdateCommands.SetSharedComponent(renderer);
    }

    private void createZigZag()
    {
        GameObject GameConstantsObject = GameObject.Find("GameConstants");
        GameConstants _constants = GameConstantsObject.GetComponent<GameConstants>();

        string entityName = "zigzag";

        PostUpdateCommands.CreateEntity(ArchetypeFactory.Instance.getArchetypeByName(entityName));

        PostUpdateCommands.SetComponent(new ZigZagMoveable
        {
            Amplitude = _constants.MoveableBlockAmplitude * _constants.blockSize,
            Speed = _constants.MoveableBlockSpeed,
            CurrentPosition = 0,
            Direction = 1
        });

        MeshInstanceRenderer renderer = EntityLookFactory.Instance.getLook(entityName);
        PostUpdateCommands.SetComponent(EntityFactory.getColliderInfo(renderer));
        PostUpdateCommands.SetSharedComponent(renderer);
    }


    private void createEntity(LevelGenerator.Item item)
    {
        switch (item.itemProperty.entityName)
        {
            case "block":
                createBlock();
                break;
            case "breakeable":
                createBreakeable();
                break;
            case "zigzag":
                createZigZag();
                break;
            default:
                createBlock();
                break;
        }

        SetItemBaseComponent(item);
    }


    protected override void OnUpdate()
    {
        LevelState state = m_LevelData.LevelState[0];

        if (state.currentRow < state.higherRow)
        {
            List<LevelGenerator.Item> items = LevelGenerator.Instance.buildValidRow();
            foreach (var item in items)
            {
                createEntity(item);
            }

            m_LevelData.LevelState[0] = new LevelState
            {
                currentRow = state.currentRow + 1,
                isDirty = 0,
                higherRow = state.higherRow
            };
        }
    }
}

This code here contains creators to every type o items in the game. I don´t think this is the best solution because i´ve already created the EntityFactory and i am here creating again the methods. I did it this way because we have two different interfaces to createentities and add components.
Once we are at the creation time, the ECS uses the entityManager, but as we started runing the systems we cannot access it anymore, and in the runtime moment we have to use the PostUpdateCommands which has not the same inheritance of the entityManager, and so i cannot declare in my factory the interface.
I will think in a better solution for that further