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.
Declaring and registering
Section titled “Declaring and registering”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.
Reading and writing
Section titled “Reading and writing”world.GetResource<Score>(); // a copyworld.GetResourceRef<Score>().Value++; // a mutable reference into storageworld.TryGetResource<Score>(out var score); // no throw when absentGetResource 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.
Injecting into systems
Section titled “Injecting into systems”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
Section titled “On a plain EcsSystem”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.