ECS and cellular automata

Hey guys & girls,

I have created some pretty cool cellular automata stuff as a hobby project using the regular OO programming in unity. and it got me thinking about the possibility of doing such a thing using ECS as I can imagine I could make some larger pixel surfaces and therefore larger textures/resolutions. Think Noita but done using ECS.

The basics of how I went about doing something like that is explained in this video. It worked quite well and I was happy with the result. However, I am completely new to ECS and I quickly stumbled into some issues. All the resources that I find on this are outdated even if they are not that old and I would love to have some pointers to good places to get some information on this.

I understand the gist of ECS and I was starting my adventure when I realized that I was able to create an array of 3d objects that look like pixels and start simulating something, but that I would rather actually give each pixel of a 2D render texture some component data upon which I could run a system.

Is this even possible to do with the ECS system in its current state? How would one go about doing this?

You could map entities to pixels. Or you could just use parallel arrays.

I have tried a few approaches to “map entities to pixels”.

Following an approach found here.

I’m not quite sure how to do this. I have managed to make a map of 2 million entities and check that I can run a counter on each of them to check viability of the system. But in trying to visualize it I am having a hard time mapping it to pixels.

using Unity.Entities;
using UnityEngine;

public class GameHandler : MonoBehaviour
{
    [SerializeField] private int width = 0;
    [SerializeField] private int height = 0;

    private EntityManager entityManager;
    private Entity[,] map;

   
    private void Start()
    {
        entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

        GeneratePixelEntityMap();
    }

    private void GeneratePixelEntityMap()
    {
        map = new Entity[width, height];

        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                map[i,j] = CreatePixelEntity();
            }
        }
    }

    private Entity CreatePixelEntity()
    {
        Entity pixelEntity = entityManager.CreateEntity(
            typeof(PixelEntity),
            typeof(Updated),
            typeof(StartPosition),
            typeof(Counter)
        );

        return pixelEntity;
    }
}

Can you elaborate on this? I’m having difficulty understanding what specifically you are trying to do that isn’t working or you aren’t sure how to approach.

So I am creating a map of entities and that’s all working fine. But now I want to actually show it on screen and be able to see things.

My idea was to have a texture2D where I run though the entity map and foreach of the entities I updated a corresponding pixel on a texture2D.

What is stopping you from doing this?

Not quite sure how to go about setting pixel, is it something I should do in System? Im very new to ecs as I mentioned.

Unless you are playing with Project Tiny (in which case you should probably head over to that subforum), there’s nothing ECS-specific about modifying textures. The OnUpdate method of SystemBase is executed on the main thread, and you can call into nearly all the Unity API from there.

If you want stuff to be fast though, you can write the pixels to a NativeArray inside a job and then set that to the texture data. You can also write directly to the texture instead of a buffer array if you run your job synchronously.

You wouldn’t happen to have an idea or pseudocode of how to go about that would you?

I am handling jobs this way here, in the new systembase way of doing things.

I have the same problem. Can someone point to some more up-to-date resources? All the tutorials online are 2-3 years old.

Synchronous pseudocode:

texture = GetTextureReference()
pixels = texture.getDataRaw()

Entities.ForEach =>
pixels[entity.coordinate.y * textureWidth + entity.coordinate.x] = entity.color32
.Run()

texture.Apply()

do you mean something like this?

public class UpdateCells : SystemBase
{
    private NativeArray<PixelEntity> pixels;
   
    protected override void OnUpdate()
    {
        // somehow grab the pixels from the texture2D and place them inside the native array here?
        pixels = new NativeArray<PixelEntity>()
       
        Entities.ForEach((ref PixelEntity pixel, ref Counter counter) =>
        {
            // Grab a reference to the texture2D and and at the PixelEntity.position
            // of the PixelEntity update the appropriate pixel
            counter.count++;

        }).ScheduleParallel();
    }
}

[Serializable]
public struct PixelEntity : IComponentData
{
    public Color color;
    public int2 position;
}

I don’t understand how I can get references to things since there is no start method to set the reference and the system doesnt exist in the scene.

I usually store a reference to the texture in a hybrid component that I can query. But my use case has so far only been for UI where I have a single hybrid entity that bridges to the classical GameObject canvas hierarchy.

I did this but it seems wrong to me

public class UpdateCells : SystemBase
{
    private Texture2D texture = GameHandler.instance.mytexture;

    protected override void OnUpdate()
    {
        byte[] pixels = texture.GetRawTextureData();
      
        Entities.ForEach((ref PixelEntity pixel, ref Counter counter) =>
        {
            // Grab a reference to the texture2D and and at the PixelEntity.position
            // of the PixelEntity update the appropriate pixel
            pixels[pixel.position.y * texture.width + pixel.position.x] = pixel.color;
            counter.count++;

        }).ScheduleParallel();
      
        texture.Apply();
    }
}

this code gives an error because im setting the byte to a color.

Also, use Run() instead of ScheduleParallel() in this case since you need the results immediately when you invoke texture.Apply()

Hmm Run means Id just be doing this on tve mainthread then. Then there is no point to do thus in ecs no?

There’s a lot more benefit to ECS than just parallelism. I would even argue parallelism is the smallest of the performance gains available using ECS. Burst is going to be your big win here. Entities.ForEach automatically uses Burst.

There are ways to make this parallel and off the main thread, but they require a little more in-depth knowledge of ECS than what I think you are ready for.

Hey, thanks so much for the help so far. This seems to work:

        var pixels = texture.GetPixelData<Color32>(1);
       
        Entities.ForEach((ref PixelEntity pixel, ref Counter counter) =>
        {
            pixels[pixel.position.x * texture.width + pixel.position.y] = pixel.color;
            counter.count++;

        }).Run();
       
        texture.Apply();

What I would like to do however is to have each:

[Serializable]
public struct PixelEntity : IComponentData
{
    public Color color;
    public int2 position;
}

be initialized with the correct position. Is it possible for me to do it somewhere here?

 private void GeneratePixelEntityMap()
    {
        map = new Entity[width, height];
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                map[i,j] = CreatePixelEntity();
            }
        }
    }
    private Entity CreatePixelEntity()
    {
        Entity pixelEntity = entityManager.CreateEntity(
            typeof(PixelEntity), // would be nice to initialize this with a parameter passed into CreatePixelEntity()
            typeof(Updated),
            typeof(StartPosition),
            typeof(Counter)
        );

        return pixelEntity;
    }

Pass i and j into CreatePixelEntity and use entityManager.SetComponentData()