创建对对象的引用并将这些引用保存在缓存中

时间:2019-01-17 08:30:05

标签: c# entity-component-system

我想为我的 Entity-Component-System 中的每个系统创建一个chache。当前,每个系统都会遍历所有实体并检查所需的组件。

internal class MySystem : 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
            {
                // Update the position of the entity
            }
        }
    }
}

ISystem仅需要实现Run方法。我认为如果每个系统都必须检查正确的组件,这种方法可能会变得很慢。

我将所有组件保存到组件类型的池中,并将这些池存储到集合中。

private Dictionary<Type, object> componentPools = new Dictionary<Type, object>();

其中object中的Dictionary<Type, object>始终是Dictionary<Guid, TComponent>()

运行系统时,最好只传递一组必需的组件。

这些是我的 EntityManager 类中的方法,会影响每个系统的缓存

    public Guid CreateEntity()
    {
        // Add and return entityID
    }

    public void DestroyEntity(Guid entityId)
    {
        // Remove entity by ID

        // Remove all components from all pools by refering to the entityID
    }

    public void AddComponentToEntity<TComponent>(Guid entityId, IComponent component) where TComponent : IComponent
    {
        // Add the component to the component pool by refering to the entityID
    }

    public void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent
    {
        // Remove the component from the component pool by refering to the entityID
    }

    public void AddComponentPool<TComponent>() where TComponent : IComponent
    {
        // Add a new component pool by its type
    }

    public void RemoveComponentPool<TComponent>() where TComponent : IComponent
    {
        // Remove a component pool by its type
    }

当调用上述方法之一时,如何创建仅引用必需组件的系统并更新其缓存?

我试图创建一个伪代码示例来说明我的意思

internal class Movement : ISystem
{
    // Just add entities with a Position and a MovementSpeed component
    List<Guid> cacheEntities = new List<Guid>();

    public void Run()
    {
        for (int i = 0; i < cacheEntities.Count; i++)
        {
            Guid entityId = cacheEntities[i];

            Position positionComponent = EntityManager.GetComponentPool<Position>()[entityId];
            MovementSpeed movementSpeedComponent = EntityManager.GetComponentPool<MovementSpeed>()[entityId];

            // Move
        }
    }
}

也许可以创建不需要entityId的集合,因此它们仅存储对应更新组件的引用。

1 个答案:

答案 0 :(得分:1)

Entity-components-system要求特定的设计。

  

ECS遵循着重继承原则

处理实质上是原始数据的组件池,有意义的是参考实际的组件类型来处理这些数据-假设您要为每个组件应用特定的行为。

decorator pattern可以很好地与合成配合,通过包装类型来增加行为。它还允许EntityManager将职责委派给组件池,而不用拥有一个庞大的决策树来处理所有情况。

让我们实现一个例子。

  

假设有一个绘图功能。这将是一个“系统”,它迭代具有物理和可见组件的所有实体,并绘制它们。可见组件通常可以提供有关实体外观的一些信息(例如人,怪物,火花四处飞动,飞行箭头),并使用物理组件知道在何处绘制它。

  1. Entity.cs
  

实体通常由ID和与其相连的组件列表组成。

class Entity
{
    public Entity(Guid entityId)
    {
        EntityId = entityId;
        Components = new List<IComponent>();
    }

    public Guid EntityId { get; }
    public List<IComponent> Components { get; }
}
  1. Component.cs

从标记界面开始。

interface IComponent { }

enum Appearance : byte
{
    Human,
    Monster,
    SparksFlyingAround,
    FlyingArrow
}

class VisibleComponent : IComponent
{
    public Appearance Appearance { get; set; }
}

class PhysicalComponent : IComponent
{
    public double X { get; set; }
    public double Y { get; set; }
}
  1. System.cs

SystemEntities添加一个集合。

interface ISystem
{
    ISet<Guid> SystemEntities { get; }
    Type[] ComponentTypes { get; }

    void Run();
}

class DrawingSystem : ISystem
{
    public DrawingSystem(params Type[] componentTypes)
    {
        ComponentTypes = componentTypes;
        SystemEntities = new HashSet<Guid>();
    }

    public ISet<Guid> SystemEntities { get; }

    public Type[] ComponentTypes { get; }

    public void Run()
    {
        foreach (var entity in SystemEntities)
        {
            Draw(entity);
        }
    }

    private void Draw(Guid entity) { /*Do Magic*/ }
}
  1. ComponentPool.cs

接下来,我们将为以后的工作打下基础。我们的组件池也应该有一个非通用的接口,当我们不能提供组件类型时,我们可以依靠它。

interface IComponentPool
{
    void RemoveEntity(Guid entityId);
    bool ContainsEntity(Guid entityId);
}

interface IComponentPool<T> : IComponentPool
{
    void AddEntity(Guid entityId, T component);
}

class ComponentPool<T> : IComponentPool<T>
{
    private Dictionary<Guid, T> component = new Dictionary<Guid, T>();

    public void AddEntity(Guid entityId, T component)
    {
        this.component.Add(entityId, component);
    }

    public void RemoveEntity(Guid entityId)
    {
        component.Remove(entityId);
    }

    public bool ContainsEntity(Guid entityId)
    {
        return component.ContainsKey(entityId);
    }
}

下一步是泳池装饰器。装饰器模式是通过公开与其包装的类相同的接口来实现的,并在过程中应用任何期望的行为。在我们的案例中,我们要检查添加的实体是否拥有系统所需的所有组件类型。然后,将它们添加到集合中。

class PoolDecorator<T> : IComponentPool<T>
{
    private readonly IComponentPool<T> wrappedPool;
    private readonly EntityManager entityManager;
    private readonly ISystem system;

    public PoolDecorator(IComponentPool<T> componentPool, EntityManager entityManager, ISystem system)
    {
        this.wrappedPool = componentPool;
        this.entityManager = entityManager;
        this.system = system;
    }

    public void AddEntity(Guid entityId, T component)
    {
        wrappedPool.AddEntity(entityId, component);

        if (system.ComponentTypes
            .Select(t => entityManager.GetComponentPool(t))
            .All(p => p.ContainsEntity(entityId)))
        {
            system.SystemEntities.Add(entityId);
        }
    }

    public void RemoveEntity(Guid entityId)
    {
        wrappedPool.RemoveEntity(entityId);
        system.SystemEntities.Remove(entityId);
    }

    public bool ContainsEntity(Guid entityId)
    {
        return wrappedPool.ContainsEntity(entityId);
    }
}

如前所述,您可以将检查和管理系统集合的负担放在EntityManager上。但是从长远来看,我们当前的设计往往会降低复杂性并提供更大的灵活性。只需为它所属的每个系统包装一个池。如果系统要求非默认行为,则可以创建专门用于该系统的新装饰器-而不干扰其他系统。

  1. EntityManager.cs

编排器(又名调解员,控制员,...)

class EntityManager
{
    List<ISystem> systems;
    Dictionary<Type, object> componentPools;

    public EntityManager()
    {
        systems = new List<ISystem>();
        componentPools = new Dictionary<Type, object>();
        ActiveEntities = new HashSet<Guid>();
    }

    public ISet<Guid> ActiveEntities { get; }

    public Guid CreateEntity()
    {
        Guid entityId;
        do entityId = Guid.NewGuid();
        while (!ActiveEntities.Add(entityId));

        return entityId;
    }

    public void DestroyEntity(Guid entityId)
    {
        componentPools.Values.Select(kp => (IComponentPool)kp).ToList().ForEach(c => c.RemoveEntity(entityId));
        systems.ForEach(c => c.SystemEntities.Remove(entityId));
        ActiveEntities.Remove(entityId);
    }

    public void AddSystems(params ISystem[] system)
    {
        systems.AddRange(systems);
    }

    public IComponentPool GetComponentPool(Type componentType)
    {
        return (IComponentPool)componentPools[componentType];
    }

    public IComponentPool<TComponent> GetComponentPool<TComponent>() where TComponent : IComponent
    {
        return (IComponentPool<TComponent>)componentPools[typeof(TComponent)];
    }

    public void AddComponentPool<TComponent>(IComponentPool<TComponent> componentPool) where TComponent : IComponent
    {
        componentPools.Add(typeof(TComponent), componentPool);
    }

    public void AddComponentToEntity<TComponent>(Guid entityId, TComponent component) where TComponent : IComponent
    {
        var pool = GetComponentPool<TComponent>();
        pool.AddEntity(entityId, component);
    }

    public void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent
    {
        var pool = GetComponentPool<TComponent>();
        pool.RemoveEntity(entityId);
    }
}
  1. Program.cs

所有地方都放在一起。

class Program
{
    static void Main(string[] args)
    {
        #region Composition Root

        var entityManager = new EntityManager();

        var drawingComponentTypes = 
            new Type[] {
                typeof(VisibleComponent),
                typeof(PhysicalComponent) };

        var drawingSystem = new DrawingSystem(drawingComponentTypes);

        var visibleComponent =
            new PoolDecorator<VisibleComponent>(
                new ComponentPool<VisibleComponent>(), entityManager, drawingSystem);

        var physicalComponent =
            new PoolDecorator<PhysicalComponent>(
                new ComponentPool<PhysicalComponent>(), entityManager, drawingSystem);

        entityManager.AddSystems(drawingSystem);
        entityManager.AddComponentPool(visibleComponent);
        entityManager.AddComponentPool(physicalComponent);

        #endregion

        var entity = new Entity(entityManager.CreateEntity());

        entityManager.AddComponentToEntity(
            entity.EntityId,
            new PhysicalComponent() { X = 0, Y = 0 });

        Console.WriteLine($"Added physical component, number of drawn entities: {drawingSystem.SystemEntities.Count}.");

        entityManager.AddComponentToEntity(
            entity.EntityId,
            new VisibleComponent() { Appearance = Appearance.Monster });

        Console.WriteLine($"Added visible component, number of drawn entities: {drawingSystem.SystemEntities.Count}.");

        entityManager.RemoveComponentFromEntity<VisibleComponent>(entity.EntityId);

        Console.WriteLine($"Removed visible component, number of drawn entities: {drawingSystem.SystemEntities.Count}.");

        Console.ReadLine();
    }
}

  

也许可以创建不需要entityId的集合,因此它们仅存储对应更新组件的引用。

如所引用的Wiki中所述,实际上不建议这样做。

  

通常的做法是为每个实体使用唯一的ID。这不是必需的,但有几个优点:

     
      
  • 可以使用ID而不是指针来引用实体。这更加健壮,因为它可以破坏实体而不会留下悬空的指针。
  •   
  • 它有助于在外部保存状态。再次加载状态后,无需重建指针。
  •   
  • 可以根据需要在内存中随机移动数据。
  •   
  • 通过网络进行通信时,可以使用实体ID来唯一标识实体。
  •