A lightweight and easy to use entity component system with an effective feature set for making games.
Components
// Components are simple structs.structPosition{publicintX,Y;}structVelocity{publicintX,Y;}
Systems
// Systems add all the functionality to the Entity Component System.// Usually, you would run them from within your game loop.publicclassMoveSystem:ISystem{publicvoidRun(Worldworld){// iterate sets of components.varquery= world.Query<Position,Velocity>().Build();
query.Run((count,positions,velocities)=>{for(vari=0;i<count;i++){ positions[i].X += velocities[i].X; positions[i].Y += velocities[i].Y;}});}}
Spawning / Despawning Entities
publicvoidRun(Worldworld){// Spawn a new entity into the world and store the id for later useEntityentity= world.Spawn().Id();// Despawn an entity.
world.Despawn(entity);}
Adding / Removing Components
publicvoidRun(Worldworld){// Spawn an entity with componentsEntityentity= world.Spawn().Add(new Position()).Add(new Velocity {X=5}).Add<Tag>().Id();// Change an Entities Components
world.On(entity).Add(new Name {Value="Bob"}).Remove<Tag>();}
Relations
// Like components, relations are structs.structApples{}structLikes{}structOwes{publicintAmount;}
publicvoidRun(Worldworld){varbob= world.Spawn().Id();varfrank= world.Spawn().Id();// Relations consist of components, associated with a "target".// The target can either be another component, or an entity.
world.On(bob).Add<Likes>(typeof(Apples));// Component ^^^^^^^^^^^^^^
world.On(frank).Add(new Owes {Amount=100}, bob);// Entity ^^^// if you want to know if an entity has a componentbooldoesBobHaveApples= world.HasComponent<Apples>(bob);// if you want to know if an entity has a relationbooldoesBobLikeApples= world.HasComponent<Likes>(bob,typeof(Apples));// Or get it directly.// In this case, we retrieve the amount that Frank owes Bob.varowes=this.GetComponent<Owes>(frank, bob);
Console.WriteLine($"Frank owes Bob {owes.Amount} dollars");}
Queries
publicvoid Run(Worldworld){// With queries, we can get a list of components that we can iterate through.// A simple query looks like thisvarquery= world.Query<Position,Velocity>().Build();// Now we can loop through these components
query.Run((count,positions,velocities)=>{for(vari=0;i<count;i++){ positions[i].X += velocities[i].X; positions[i].Y += velocities[i].Y;}});// we can also iterate through them using multithreading!// for that, we simply replace `Run` with `RunParallel`// note that HypEcs is an arche type based ECS.// when running iterations multithreaded, that means we parallelise each *Table* in the ecs,// not each component iteration. This means MultiThreading benefits from archetype fragmentation,// but does not bring any benefits when there is only one archetype existing in the ecs that is iterated.
query.RunParallel((count,positions,velocities)=>{for(vari=0;i<count;i++){ positions[i].X += velocities[i].X; positions[i].Y += velocities[i].Y;}});// You can create more complex, expressive queries through the QueryBuilder.// Here, we request every entity that has a Name component, owes money to Bob and does not have the Dead tag.varappleLovers= world.QueryBuilder<Entity,Name>().Has<Owes>(bob).Not<Dead>().Build();// Note that we only get the components inside Query<>.// Has<T>, Not<T> and Any<T> only filter, but we don't actually get T int he loop.
appleLovers.Run((count,entities,names)=>{for(vari=0;i<count;i++){ Console.WriteLine($"Entity {entities[i]} with name {names[i].Value} owes bob money and is still alive.")}});}
Creating a World
// A world is a container for different kinds of data like entities & components.Worldworld=new World();
Running a System
// Create an instance of your system.varmoveSystem=new MoveSystem();// Run the system.// The system will match all entities of the world you enter as the parameter.
moveSystem.Run(world);// You can run a system as many times as you like.
moveSystem.Run(world);
moveSystem.Run(world);
moveSystem.Run(world);// Usually, systems are run once a frame, inside your game loop.
SystemGroups
// You can create system groups, which bundle together multiple systems.SystemGroupgroup=new SystemGroup();// Add any amount of systems to the group.
group.Add(new SomeSystem()).Add(new SomeOtherSystem()).Add(new AThirdSystem());// Running a system group will run all of its systems in the order they were added.
group.Run(world);
Example of a Game Loop
// In this example, we are using the Godot Engine.using Godot;using RelEcs;using World = RelEcs.World;// Godot also has a World class, so we need to specify this.publicclassGameLoop:Node{Worldworld=new World();SystemGroupinitSystems=new SystemGroup();SystemGrouprunSystems=new SystemGroup();SystemGroupcleanupSystems=new SystemGroup();// Called once on node construction.publicGameLoop(){// Add your initialization systems.
initSystem.Add(new SomeSpawnSystem());// Add systems that should run every frame.
runSystems.Add(new PhysicsSystem()).Add(new AnimationSystem()).Add(new PlayerControlSystem());// Add systems that are called once when the Node is removed.
cleanupSystems.Add(new DespawnSystem());}// Called every time the node is added to the scene.publicoverridevoid_Ready(){// Run the init systems.
initSystems.Run(world);}// Called every frame. Delta is time since the last frame.publicoverridevoid_Process(floatdelta){// Run the run systems.
runSystems.Run(world);// IMPORTANT: For RelEcs to work properly, we need to tell the world when a frame is done.// For that, we call Tick() on the world, at the end of the function.
world.Tick();}// Called when the node is removed from the SceneTree.publicoverridevoid_ExitTree(){// Run the cleanup systems.
cleanupSystems.Run(world);}}