This page contains some more specific questions you could have which are not covered by the general README overview.
- Why the name DefaultEcs?
- How to use DefaultEcs in a game loop?
- How to have multiple entities dependencies in systems?
- How to queue messages instead of handling them synchronously?
Naming things is hard, and I am certainly bad at it. The name DefaultEcs has two opposite meaning:
- making this framework as good as possible so it just become the default choice for ecs
- a play on the C#
default()
syntax, which return a zeroed variable for a struct type or null for a reference type,default(ecs)
giving you nothing of importance in the end as I am not that bright and the framework may not be that good
DefaultEcs is easy enough to insert into a game loop by using the provided system implementation.
ISystem<GameState> mainSystem;
GameState state;
while (true)
{
mainSystem.Update(state);
}
It is completely resonable to seperate your update and render process in their own systems as some game frameworks allow the update to run multiple times compared to the presentation of a frame.
ISystem<GameState> updateSystem;
ISystem<GameState> renderSystem;
GameState state;
while (true)
{
updateSystem.Update(state);
renderSystem.Update(state);
}
You are free to handle the exit clause however you want as DefaultEcs do not force any specific usage.
While the provided system implementation to handle entities update AEntitySystem only have one internal EntitySet, nothing stop you to generate more EntitySet if for example you need to handle collision between two sets.
// don't care about collision between enemies and collision between allies
[With(typeof(Enemy), typeof(CollisionBox))]
public sealed class CollisionSystem : AEntitySystem<float>
{
private readonly EntitySet _allies;
public CollisionSystem(World world, SystemRunner<float> runner)
: base(world, runner)
{
_allies = world.GetEntities().With<Ally>().With<CollisionBox>().Build();
}
protected override void Update(float state, ReadOnlySpan<Entity> entities)
{
foreach (ref readonly Entity ally in _allies.GetEntities())
{
foreach (ref readonly Entity entity in entities)
{
if (entity.Get<CollisionBox>().Intersects(ally.Get<CollisionBox>()))
{
...
}
}
}
}
}
The IPublisher implementation of the World type handle message synchronously. While nothing is provided to make them asynchronous it is a simple enough feat.
internal sealed class BulletSystem : ISystem<float>
{
private readonly List<NewBulletMessage> _newBullets;
public BulletSystem(World world, Configuration configuration)
{
_newBullets = new List<NewBulletMessage>(100);
_subscription = world.Subscribe(this);
}
[Subscribe]
private void On(in NewBulletMessage message) => _newBullets.Add(message);
public bool IsEnabled { get; set; } = true;
public void Update(float state)
{
if (IsEnabled)
{
foreach (NewBulletMessage message in _newBullets)
{
// setup bullet
}
_newBullets.Clear();
}
}
}
By handling them as an ISystem you can then easily decide when in your update workflow you want to actually handle them. Keep in mind to use a thread safe collection to store the received message if you use a multithreaded update.