Skip to content

Resources

Not everything is a component. The player’s score, an input snapshot, a reference to the audio backend - state that exists once per world, not once per entity. Making it a component would mean inventing a singleton entity to hold it. Resources are the direct version: a struct registered on the world, readable and writable from anywhere.

public struct Score : IResource
{
public int Value;
}
var world = new WorldBuilder()
.AddResource(new Score { Value = 0 })
.AddSystem<ScoreSystem>()
.Build();

IResource is a marker interface, same shape as IComponent. Registration also works after Build() with world.AddResource(...), and a factory overload receives the world itself for resources that need to look at it first:

world.AddResource(w => new CameraRig { Main = SpawnCamera(w) });

One resource per type - registering a second throws. Call RemoveResource<T>() first to replace one.

world.GetResource<Score>(); // a copy
world.GetResourceRef<Score>().Value++; // a mutable reference into storage
world.TryGetResource<Score>(out var score); // no throw when absent

GetResource hands back a copy, mutating it changes nothing the world can see. GetResourceRef returns a ref T into the world’s own storage, writes through it are visible to every later reader. RemoveResource<T>() takes one out again.

A [Resource] property on a QuerySystem is refreshed from the world automatically:

public sealed partial class ScoreReaderSystem : QuerySystem
{
[Resource] public Score Score { get; private set; }
protected override IQuery DefineQuery(Query query) => query;
public void Update(Time time)
{
Console.WriteLine($"Score is {Score.Value}");
}
}

A system that owns the score declares a public setter instead and assigns a new value before returning:

public sealed partial class ScoreWriterSystem : QuerySystem
{
[Resource] public Score Score { get; set; }
protected override IQuery DefineQuery(Query query) => query.With<KillConfirmed>();
public void Update(Time time, ref KillConfirmed kill)
{
Score = new Score { Value = Score.Value + kill.Points };
}
}

The setter’s accessibility is the contract. A get-only or private-set property is read-only: fetched fresh before every Update, and the scheduler only counts the system as reading the resource. A public setter makes it read-write: whatever Update leaves in the property is written back to the world afterwards, and the scheduler counts the system as writing it, two systems writing the same resource won’t share a parallel stage.

On a plain EcsSystem subclass, declare a partial property:

public sealed partial class ShipControlSystem(World world) : EcsSystem
{
[Resource] public partial AudioPlayer Audio { get; }
[Resource] public partial GameAssets Assets { get; }
protected override void Execute(World world, Time time)
{
Audio.Play(Assets.EngineSound);
}
}

Add a set accessor ({ get; set; }) for write access, same read/write-from-accessibility rule as QuerySystem above. The difference is timing: QuerySystem fetches once and writes back once around its generated Execute, a plain EcsSystem’s partial property instead reads and writes CurrentWorld live on every access, there’s no generator-owned Execute to inject a fetch/writeback step into.

An ad hoc world.GetResource<T>()/GetResourceRef<T>() call works too, anywhere inside Execute, without declaring a property at all, and is tracked into the scheduler’s access table the same as a [Resource] property would be.

Resources are shared state systems read. For a system telling another system that something happened, rather than both reading the same state, see Events.