I'm setting up a really small Entity-Component-System example and have some problems with the component pools.
Currently my entities are just IDs (GUIDs) which is fine.
Each system has to implement the ISystem
interface
internal interface ISystem
{
void Run();
}
and is stored in a KeyedByTypeCollection
. This collection makes sure, that each system is unique.
Each component has to implement the IComponent
interface.
internal interface IComponent
{
}
By doing so I can store all the different component types to their matching component pool. Each pool is a Dictionary<Guid, IComponent>
. The key represents the ID of the Entity and the value is the component of that entity. Each pool is stored in a KeyedByTypeCollection
to make sure the component pool is unique.
Currently my EntityManager
class contains the core logic. I don't know if it's required to have it static but currently my systems need to get some information from it.
The important core methods to deal with the component pools are:
internal static class EntityManager
{
public static List<Guid> activeEntities = new List<Guid>();
private static KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools = new KeyedByTypeCollection<Dictionary<Guid, IComponent>>();
public static KeyedByTypeCollection<ISystem> systems = new KeyedByTypeCollection<ISystem>(); // Hold unique Systems
public static Guid CreateEntity() // Add a new GUID and return it
{
Guid entityId = Guid.NewGuid();
activeEntities.Add(entityId);
return entityId;
}
public static void DestroyEntity(Guid entityId) // Remove the entity from every component pool
{
activeEntities.Remove(entityId);
for (int i = 0; i < componentPools.Count; i++)
{
componentPools[i].Remove(entityId);
}
}
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return componentPools[typeof(TComponent)];
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(new Dictionary<Guid, TComponent>());
}
public static void RemoveComponentPool<TComponent>() where TComponent : IComponent // remove a pool by its component type
{
componentPools.Remove(typeof(TComponent));
}
public static void AddComponentToEntity(Guid entityId, IComponent component) // add a component to an entity by the component type
{
componentPools[component.GetType()].Add(entityId, component);
}
public static void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent // remove a component from an entity by the component type
{
componentPools[typeof(TComponent)].Remove(entityId);
}
}
I created a small movement system for testing purposes:
internal class Movement : ISystem
{
public void Run()
{
for (int i = 0; i < EntityManager.activeEntities.Count; i++)
{
Guid entityId = EntityManager.activeEntities[i];
if (EntityManager.GetComponentPool<Position>().TryGetValue(entityId, out Position positionComponent)) // Get the position component
{
positionComponent.X++; // Move one to the right and one up ...
positionComponent.Y++;
}
}
}
}
Which should be fine because of the dictionary lookups. I am not sure if I can optimize it but I think I have to loop through all the entities first.
And here is the problem:
I am not able to use KeyedByTypeCollection<Dictionary<Guid, IComponent>>
because I need to have something like
KeyedByTypeCollection<Dictionary<Guid, Type where Type : IComponent>>
My GetComponentPool
and AddComponentPool
methods throw errors.
GetComponentPool: Cannot implicitly convert IComponent to TComponent
When calling GetComponentPool
I would have to cast the dictionary values from IComponent
to TComponent
.
AddComponentPool: Cannot convert from TComponent to IComponent
When calling AddComponentPool
I would have to cast TComponent
to IComponent
.
I don't think casting is an option because this seems to lower the performance.
Would someone mind helping me fix the type problem?
Update:
When debugging I use this Code to test the whole ECS
internal class Program
{
private static void Main(string[] args)
{
EntityManager.AddComponentPool<Position>();
EntityManager.AddComponentPool<MovementSpeed>();
EntityManager.Systems.Add(new Movement());
Guid playerId = EntityManager.CreateEntity();
EntityManager.AddComponentToEntity(playerId, new Position());
EntityManager.AddComponentToEntity(playerId, new MovementSpeed(1));
foreach (ISystem system in EntityManager.Systems)
{
system.Run();
}
}
}
I already showed the Movement
System, here are the missing components
internal class Position : IComponent
{
public Position(float x = 0, float y = 0)
{
X = x;
Y = y;
}
public float X { get; set; }
public float Y { get; set; }
}
internal class MovementSpeed : IComponent
{
public MovementSpeed(float value = 0)
{
Value = value;
}
public float Value { get; set; }
}