A lightweight and easy to use entity component system with an effective feature set for making games.
Components
// Components are simple classes.classPosition{publicintX,Y;}classVelocity{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{publicWorldWorld{get;set;}publicvoidRun(){// iterate sets of components.foreach(var(pos, vel)in World.Query<Position,Velocity>().Build()){
pos.X += vel.X;
pos.Y += vel.Y;}}}
Spawning / Despawning Entities
publicvoidRun(){// 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(){// 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 classes.classApples{}classLikes{}classOwes{publicintAmount;}
publicvoidRun(){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= World.GetComponent<Owes>(frank, bob);
Console.WriteLine($"Frank owes Bob {owes.Amount} dollars");}
Queries
publicvoidRun(){// 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 componentsforeach(var(pos, vel)in query){
pos.Value += vel.Value;}// You can create more complex, expressive queries with additional method chaining.// Here, we request every entity that has a Name component, owes money to Bob and does not have the Dead tag.varappleLovers= world.Query<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.foreach(var (entity, name)in query){
Console.WriteLine($"Entity {entity} with name {name.Value} owes bob money and is still alive.")}}
Triggers
// Triggers are also just classes and very similar to components.// They act much like a simplified, ECS version of C# events.classMyTrigger{}
publicvoidRun(){// You can send a bunch of triggers inside of a system.
World.Send(new MyTrigger());
World.Send(new MyTrigger());
World.Send(new MyTrigger());// In any system, including the origin system, you can now receive these triggers.foreach(var t in World.Receive<T>(this)){
Console.WriteLine("It's a trigger!");}// Output:// It's a trigger!// It's a trigger!// It's a trigger!// NOTE: Triggers live until the end of the next frame, to make sure every system receives them.// Each trigger is always received exactly ONCE per system.}
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);}}
About
A lightweight and easy to use entity component system with an effective feature set for making games.