Skip to content

Tag Components Neofox: bongo_down ​

an invisible fennec an invisible fennec

Neofox: thumbsup Zero-Cost Markers

Tags are components that store no data – just their presence carries meaning! They're perfect for categorizing entities and filtering with Queries. Best of all? They use practically zero memory.

What is a Tag? ​

A Tag is simply an empty struct. It marks an entity as having a certain trait or belonging to a category, without storing any actual data.

cs
public struct Enemy;
public struct Poisoned;
public struct CanFly;
public struct Player;

Usage Examples ​

Marking Entities ​

cs
var player = world.Spawn()
    .Add(new Position { X = 0, Y = 0 })
    .Add(new Health { Value = 100 })
    .Add<Player>();  // Tag: this is the player!

var enemy = world.Spawn()
    .Add(new Position { X = 50, Y = 30 })
    .Add(new Health { Value = 25 })
    .Add<Enemy>()    // Tag: this is an enemy
    .Add<CanFly>();  // Tag: it can fly!

Querying with Tags ​

cs
// Find all enemies
var enemies = world.Query<Health>()
    .Has<Enemy>()
    .Build();

// Find all flying enemies
var flyingEnemies = world.Query<Position, Health>()
    .Has<Enemy>()
    .Has<CanFly>()
    .Build();

// Find entities that are NOT enemies
var friendlies = world.Query<Position>()
    .Not<Enemy>()
    .Build();

Adding and Removing Tags ​

cs
// Add a status effect tag
entity.Add<Poisoned>();

// Check for tag
if (entity.Has<Poisoned>())
{
    Console.WriteLine("Entity is poisoned!");
}

// Remove the tag
entity.Remove<Poisoned>();

The Classic Example Neofox: comfy ​

cs
public struct Pretty;
public struct Smart;
public struct Cool;

var you = world.Spawn()
    .Add<Pretty>()
    .Add<Smart>()
    .Add<Cool>();

var fennecsUsers = world.Query()
    .Has<Pretty>()
    .Has<Smart>()
    .Has<Cool>()
    .Compile();

Assert.Contains(you, fennecsUsers);  // ✅ You're in!

Defining Tags ​

cs
// Most minimal definition
public struct Enemy;
public struct Friendly;
public struct Active;
cs
// Works too, nicer formatting in mixed codebases
public record struct Enemy;
public record struct Friendly;
public record struct Active;

Common Use Cases ​

Use CaseExample Tags
Entity TypesPlayer, Enemy, NPC, Projectile
Status EffectsPoisoned, Stunned, Invincible, Dead
StatesActive, Paused, Loading, Ready
CategoriesFriendly, Hostile, Neutral
CapabilitiesCanFly, CanSwim, CanAttack
MarkersJustSpawned, NeedsUpdate, Dirty

Tags in Queries Neofox: heart ​

Tags are excellent for Query filtering – that's their primary purpose!

cs
// Good: Use Has<> for filtering
var enemies = world.Query<Position, Health>()
    .Has<Enemy>()
    .Build();

foreach (var entity in enemies)
{
    // Process enemy...
}

Neofox: think Tags in Stream Types

Tags can be used as Stream Types, but they're not particularly useful there since they have no data to access:

cs
// This works, but the Enemy ref is useless
stream.For((ref pos, ref tag) =>
{
    // 'tag' has no data - waste of a stream slot!
});

// Better: use Has<> filter instead
var enemyStream = world.Query<Position>()
    .Has<Enemy>()
    .Stream();

enemyStream.For((ref pos) =>
{
    // Cleaner, and you have all 5 stream slots for real data!
});

Tags as Relation Markers ​

Tags can also be used with relations – the tag itself has no backing data, but the relation target provides meaning:

cs
public struct Likes;
public struct MemberOf;

// Entity-to-entity relations with tag backing
alice.Add<Likes>(bob);     // Alice likes Bob
alice.Add<MemberOf>(team); // Alice is member of team

Quick Reference ​

AspectTags
MemoryZero storage cost
PurposeMarking, categorizing, filtering
Query UseHas<T>(), Not<T>(), Any<T>()
Best ForEntity classification

Neofox: science Why No Memory?

Empty structs in .NET have a minimum size of 1 byte in isolation, but fennecs optimizes tag storage. The archetype knows which entities have which tags through its signature – no per-entity storage needed!