Why does my Unity ECS simulation move all objects to the same position?

782 Views Asked by At

I am trying to learn the Unity Dots / ECS system, and am running into a really incomprehensible issue that has me completely stuck. I am trying to build a life simulation that has a collection of cells spawn and move around. But I haven't even got that far yet.

The full code for my project can be found here, but the snippet that isn't working is the following:

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;

[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct UpdateSystem : ISystem
{
    [BurstCompile]
    void OnUpdate(ref SystemState state)
    {
        float3 position; 

        foreach(var cell in SystemAPI.Query<CellAspect>())
        {
            // The following operation causes all the objects to be moved to the exact same position!
            position = cell.position;
            cell.position = position;
        }
    }
}

This appears to be as simple as can be. I iterate through all my "cells" and I store the position, and then assign it directly back. When I run this, all cells end up in the same position. I tried outputting the position (Debug.Log(cell.position)) and the output is as expected, with each position being where they were randomly spawned.

I get the impression that there is a fundamental concept that I am not getting here, but with how the the ECS api keeps changing, it is really had to find reliable tutorials online.

The definition of CellAspect is as follows:

public readonly partial struct CellAspect : IAspect
{
    private readonly TransformAspect _transformAspect;

    private readonly RefRW<CellProperties> _cellProperties;    

    public TransformAspect transform => _transformAspect;
    
    public float3 velocity
    {
        get => _cellProperties.ValueRO.velocity;
        set => _cellProperties.ValueRW.velocity = value;
    }

    public float3 position 
    {
        get => _transformAspect.WorldPosition;
        set => _transformAspect.WorldPosition = value;
    }

    public float detectionRadius => _cellProperties.ValueRO.detectionRadius;

}
1

There are 1 best solutions below

0
On BEST ANSWER

NOTE: TransformAspect is gone as of entities 1.0.0-pre.65 (2023-03-21)


IAspect was introduced in 1.0 and I don't like it already. This is an example of why. Because, as you will learn here, it obfuscates things that should be very explicit and apparent.

This is (part of) your CellAspect:

public readonly partial struct CellAspect : IAspect
{
    private readonly TransformAspect _transformAspect;

    public float3 position 
    {
        get => _transformAspect.WorldPosition;
        set => _transformAspect.WorldPosition = value;
    }
}

Notice that you created a property named position and it changes what exactly? WorldPosition of course. How is that even important? Well, it CAN be important if, for example, you initialized an entity's local position (LocalTransform) and never gave it a frame of time to propagate to become world position before overwriting this local position with this hidden _transformAspect.WorldPosition back to it's original prefab's value.


I suggest you either delete this vague position property OR introduce worldPosition and localPosition in it's place:

public readonly partial struct CellAspect : IAspect
{
    private readonly TransformAspect _transformAspect;
    public float3 worldPosition
    {
        get => _transformAspect.WorldPosition;
        set => _transformAspect.WorldPosition = value;// surprise: writes to local pos too!
    }
    public float3 localPosition 
    {
        get => _transformAspect.LocalPosition;
        set => _transformAspect.LocalPosition = value;// haven't checked but probably writes to world pos too
    }
}

TL;DR:

void OnUpdate(ref SystemState state)
{
    foreach(var cell in SystemAPI.Query<CellAspect>())
    {
        float3 pos = cell.localPosition;
        cell.localPosition = pos;
    }
}