Aspects
Same World... Different Neighborhoods!
What's an Aspect?
An Aspect is a self-contained collection of Archetypes inside a World – its very own, contiguously laid-out component storage universe. All Aspects of a World share the same Entities; only the component data lives apart.
Every World comes with one Aspect built in: World.Main, fittingly named "main". It's always first in world.Aspects, proudly reports IsMain == true, and every living Entity is a member. Any component type you don't explicitly assign elsewhere is stored there – which is why, until this very page, you never needed to know Aspects existed. You're welcome.
Adding more Aspects lets you group hot data – say, the Position and Matrix components your renderer touches every frame – into their own storage universe, where the comings and goings of gameplay components can't fragment them.
WHY THOUGH?
Fragmentation is the archetype-based ECS's natural predator: every distinct component combination creates its own Archetype, and many small Archetypes mean many small memory hops. An Aspect that owns just your hot types collapses that combinatorial explosion – Entities that differ wildly in gameplay components can still share one big, happy, cache-friendly Archetype in the "visuals" Aspect.
Quick Start
Declare an Aspect with AddAspect, tell it which component types it Owns, and... that's it. Components route themselves to their owning Aspect automatically.
private class GameWorld : World
{
public readonly Aspect Visuals;
public readonly Aspect Game;
public GameWorld()
{
// register everything up front, before any Entity materializes a type
Visuals = AddAspect("visuals").Owns<Position>().Owns<Velocity>();
Game = AddAspect("game").Owns<CrewData>();
}
}
using var world = new GameWorld();
var entity = world.Spawn()
.Add(new Position(1, 2)) // stored in the "visuals" Aspect
.Add(new CrewData(3)); // stored in the "game" Aspect
// one Entity, one identity - data in two Aspects
Console.WriteLine(world.Visuals.Count); // 1
Console.WriteLine(world.Game.Count); // 1
Console.WriteLine(entity.Has<Position>() && entity.Has<CrewData>()); // Trueusing var world = new World();
var visuals = world.AddAspect("visuals").Owns<Position>().Owns<Velocity>();
var game = world.AddAspect("game", typeof(CrewData)); // params Type[] shorthand
world.Spawn().Add(new Position(1, 2)).Add(new CrewData(3));
world.Spawn().Add(new Position(3, 4));
// stream just the "visuals" universe - contiguous and cozy
visuals.Stream<Position>().For((ref position) => position.X += 1f);When to reach for Aspects
When profiling tells you Fragmentation is eating your frame time – usually once entity counts climb into the hundreds of thousands and your hot loops hop between many small Archetypes.
DON'T PREMATURELY FOXIMIZE
One Main Aspect is plenty for most games, and fennecs is already fast as a fox without any of this. Profile first (your World's DebugString() shows Archetype counts and sizes), then carve out an Aspect for the data that actually runs hot.
The Main Aspect & IAspect
Both World and Aspect implement the same interface: IAspect. A World simply delegates its entire query surface to its Main Aspect – so all your existing code keeps working, unchanged, blissfully unaware.
world.Query<T>()automatically resolves to the Aspect that ownsT– you rarely need to name an Aspect to query it.aspect.Allis the universal Query matching all of an Aspect's members;world.Allisworld.Main.All(the same object).- Code that takes an
IAspectparameter happily accepts either a World or an Aspect.
Cheat Sheet
| Member | What it does |
|---|---|
World.Main | the built-in Aspect "main"; every living Entity is a member |
World.Aspects | all Aspects of the World (Main is always first) |
World.AddAspect(name) | adds a new Aspect (unique name, or ArgumentException) |
World.AddAspect(name, params Type[]) | add + register owned types in one call |
World.StrictAspects | init-only; unregistered types throw instead of routing to Main → Ownership |
Aspect.Owns<T>() / .Owns(params Type[]) | fluently declares the types this Aspect stores → Ownership |
Aspect.Name / .World / .IsMain | what it says on the tin |
Aspect.Count | number of member Entities → Membership |
foreach (var entity in aspect) | Aspects are IEnumerable<Entity> over their members |
Aspect.Query<C1...C5>() / .Stream<...>() / .All | the full query surface, per Aspect → Queries & Streams |
Where to next?
- Ownership – who stores what,
StrictAspects, and the freeze rule - Membership – how Entities join, leave, and live across Aspects
- Queries & Streams – the single-Aspect rule and how to work with it