Skip to content

03 — Aspects

Aspects group related read/write component operations into a single reusable struct, instead of listing individual component parameters.

Source: com.trecs.core/Samples~/Tutorials/03_Aspects/

What it does

Boids move in straight lines and wrap around a bounded area, rotated to face their movement direction.

Schema

Components

Position from Common/, plus Velocity and Speed defined in this sample. The template also pulls in PrefabId / GameObjectId via the RenderableGameObject base.

Tags & template

public struct Boid : ITag { }

public partial class BoidEntity
    : ITemplate,
        IExtends<CommonTemplates.RenderableGameObject>,
        ITagged<SampleTags.Boid>
{
    Position Position = default;
    Velocity Velocity;
    Speed Speed;
    ColorComponent Color = new(UnityEngine.Color.white);
    PrefabId PrefabId = new(AspectsPrefabs.Boid);
}

Systems

BoidMovementSystem

Defines an aspect and iterates over it:

public partial class BoidMovementSystem : ISystem
{
    public void Execute()
    {
        foreach (var boid in Boid.Query(World).MatchByComponents())
        {
            boid.Position += World.DeltaTime * boid.Speed * boid.Velocity;
        }
    }

    partial struct Boid : IAspect, IRead<Velocity, Speed>, IWrite<Position> { }
}

The Boid aspect provides:

  • ref readonly float3 Velocity (read-only, unwrapped from Velocity component)
  • ref readonly float Speed (read-only, unwrapped from Speed component)
  • ref float3 Position (read-write, unwrapped from Position component)

BoidWrapSystem

Wraps boids that go out of bounds. [ExecuteAfter] ensures it runs after movement:

[ExecuteAfter(typeof(BoidMovementSystem))]
public partial class BoidWrapSystem : ISystem
{
    readonly float _halfSize;

    public BoidWrapSystem(float areaSize)
    {
        _halfSize = areaSize / 2f;
    }

    [ForEachEntity(MatchByComponents = true)]
    void Execute(in Boid boid)
    {
        ref var p = ref boid.Position;

        if (p.x > _halfSize) p.x -= _halfSize * 2;
        else if (p.x < -_halfSize) p.x += _halfSize * 2;
        if (p.z > _halfSize) p.z -= _halfSize * 2;
        else if (p.z < -_halfSize) p.z += _halfSize * 2;
    }

    partial struct Boid : IAspect, IWrite<Position> { }
}

BoidPresenter (variable update)

Reads position and velocity, then updates the GameObject transform to face the movement direction.

Concepts introduced

  • Aspectspartial struct implementing IAspect, IRead<T>, IWrite<T>. See Aspects.
  • [Unwrap] components expose their inner value type through aspect properties. See Components.
  • Multiple aspects per system — different systems can define different aspect views over the same components.
  • Read vs WriteIRead<T> provides ref readonly, IWrite<T> provides ref.
  • Aspect.Query(World).MatchByComponents() vs [ForEachEntity] — two ways to drive iteration over an aspect. See Queries & Iteration.