【问题标题】:create references to objects and hold these references in a cache创建对对象的引用并将这些引用保存在缓存中
【发布时间】:2019-06-11 09:47:56
【问题描述】:

我想为我的 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&lt;Type, object&gt; 始终为Dictionary&lt;Guid, TComponent&gt;()

在运行系统时,最好只传递所需组件的集合。

这些是我的 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 的集合,因此它们只存储对应该更新的组件的引用。

【问题讨论】:

    标签: c# entity-component-system


    【解决方案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 来唯一标识实体。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-18
      • 1970-01-01
      • 1970-01-01
      • 2020-07-11
      • 2016-12-03
      • 2018-09-21
      • 2011-01-02
      • 1970-01-01
      相关资源
      最近更新 更多