04 — Predator Prey¶
Cross-entity relationships using EntityHandle. Predators chase prey, and cleanup handlers prevent dangling references.
Source: Samples/04_PredatorPrey/
What It Does¶
Predators (red) chase the nearest prey (green). When a predator catches its prey, the prey is removed and a new one spawns. Predators continuously re-target the nearest available prey.
Schema¶
Components¶
[Unwrap]
public partial struct ChosenPrey : IEntityComponent
{
public EntityHandle Value; // Handle to the prey being chased
}
[Unwrap]
public partial struct ApproachingPredator : IEntityComponent
{
public EntityHandle Value; // Handle to the predator chasing this prey
}
Plus Position, Velocity, Speed, MoveDirection, GameObjectId.
Tags & Templates¶
Template inheritance shares common movement components:
// Base template
public partial class Movable : ITemplate, IHasTags<SampleTags.Movable>
{
public Position Position = default;
public MoveDirection MoveDirection = default;
public Speed Speed;
public GameObjectId GameObjectId;
}
// Predator extends Movable
public partial class PredatorEntity : ITemplate,
IHasTags<SampleTags.Predator>,
IExtends<Movable>
{
public ChosenPrey ChosenPrey = default;
}
// Prey extends Movable
public partial class PreyEntity : ITemplate,
IHasTags<SampleTags.Prey>,
IExtends<Movable>
{
public ApproachingPredator ApproachingPredator = default;
}
Systems¶
PredatorChoosePreySystem¶
Finds the nearest unassigned prey for each predator using nested aspect queries:
foreach (var predator in Predator.Query(World).WithTags<SampleTags.Predator>())
{
if (!predator.ChosenPrey.IsNull) continue; // Already has a target
float nearestDistSq = float.MaxValue;
Prey chosenPrey = default;
bool found = false;
foreach (var prey in Prey.Query(World).WithTags<SampleTags.Prey>())
{
if (!prey.ApproachingPredator.IsNull) continue; // Already claimed
float distSq = math.distancesq(predator.Position, prey.Position);
if (distSq < nearestDistSq)
{
nearestDistSq = distSq;
chosenPrey = prey;
found = true;
}
}
if (found)
{
// Link predator ↔ prey via aspects
chosenPrey.ApproachingPredator = predator.EntityIndex.ToHandle(World);
predator.ChosenPrey = chosenPrey.EntityIndex.ToHandle(World);
}
}
partial struct Predator : IAspect, IRead<Position>, IWrite<ChosenPrey> { }
partial struct Prey : IAspect, IRead<Position>, IWrite<ApproachingPredator> { }
PredatorChaseSystem¶
Steers predators toward their target prey, removes prey on contact.
PreyRespawnSystem¶
Maintains the prey population by spawning replacements.
Cleanup Handler¶
When prey are removed, clean up their GameObjects using an OnRemoved event handler. Using events for cleanup is good practice for two reasons:
- Consistency — since entity removal is deferred, the entity still exists until the next submission. If not cleaning up via an event, subsequent systems could attempt to use stale data on the about-to-be-removed entity.
- Centralized cleanup — if entities can be removed from multiple places (e.g., caught by a predator, starvation, despawning), the same cleanup handler runs regardless of the removal source.
public partial class CleanupHandlers
{
readonly GameObjectRegistry _gameObjectRegistry;
readonly DisposeCollection _disposables = new();
public CleanupHandlers(World world, GameObjectRegistry gameObjectRegistry)
{
World = world.CreateAccessor();
_gameObjectRegistry = gameObjectRegistry;
World.Events
.EntitiesWithTags<SampleTags.Prey>()
.OnRemoved(OnPreyRemoved)
.AddTo(_disposables);
}
WorldAccessor World { get; }
[ForEachEntity]
void OnPreyRemoved(in Prey prey)
{
var go = _gameObjectRegistry.Resolve(prey.GameObjectId);
GameObject.Destroy(go);
_gameObjectRegistry.Unregister(prey.GameObjectId);
}
partial struct Prey : IAspect, IRead<GameObjectId, ApproachingPredator> { }
}
Concepts Introduced¶
EntityHandlefor stable cross-entity references that survive structural changes- Template inheritance with
IExtends<T>to share common components - Nested aspect queries — iterating one entity type while querying another
- Entity events (
OnRemoved) for cleanup of cross-references - Bidirectional linking — predator points to prey and prey points back to predator