简体   繁体   English

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

[英]create references to objects and hold these references in a cache

I would like to create a chache for each system in my Entity-Component-System .我想为我的Entity-Component-System 中的每个系统创建一个 chache 。 Currently each system would loop through all entities and check for required components.目前,每个系统都会遍历所有实体并检查所需的组件。

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 just requires to implement a Run method. ISystem只需要实现一个Run方法。 I think this approach might get really slow if each system has to check for the right components.我认为如果每个系统都必须检查正确的组件,这种方法可能会变得非常慢。

I save all the components to a pool of the component type and these pools get stored to a collection.我将所有组件保存到组件类型的池中,并将这些池存储到一个集合中。

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

where object of Dictionary<Type, object> is always Dictionary<Guid, TComponent>() .其中objectDictionary<Type, object>总是Dictionary<Guid, TComponent>()

When running a system it would be better passing in a collection of the required components only.在运行系统时,最好只传入所需组件的集合。

These are the methods from my EntityManager class that would affect the cache of each system这些是我的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
    }

How can I create systems that refer to the required components only and update their cache when calling one of these methods shown above?如何创建仅引用所需组件的系统并在调用上面显示的这些方法之一时更新其缓存?

I tried to create a pseudo code example to show what I mean我试图创建一个伪代码示例来说明我的意思

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
        }
    }
}

and maybe it's possible to create collections that require no entityId , so they only store the reference to the component that should get updated.并且也许可以创建不需要entityId ,因此它们只存储对应该更新的组件的引用。

The Entity-components-system asks for a specific design. 实体组件系统要求特定的设计。

ECS follows the composition over inheritance principle ECS遵循组合优于继承原则

Dealing with pools of components, which are in essence raw data, it makes sense to handle this data with reference to the actual component type -- given you'll want to apply specific behaviors for each.处理组件池(本质上是原始数据),参考实际组件类型来处理这些数据是有意义的——假设您希望为每个组件应用特定的行为。

The decorator pattern plays nicely with composition, adding behaviors by wrapping types.装饰器模式与组合配合得很好,通过包装类型来添加行为。 It also allows EntityManager to delegate responsibilities to the component pools, instead of having one massive decision tree that has to handle all cases.它还允许EntityManager将职责委托给组件池,而不是拥有一个必须处理所有情况的庞大决策树。

Let's implement an example.让我们实现一个例子。

Suppose there is a drawing function.假设有一个绘图功能。 This would be a "System" that iterates through all entities that have both a physical and a visible component, and draws them.这将是一个“系统”,它遍历所有具有物理和可见组件的实体,并绘制它们。 The visible component could typically have some information about how an entity should look (eg human, monster, sparks flying around, flying arrow), and use the physical component to know where to draw it.可见组件通常可以包含有关实体外观的一些信息(例如,人类、怪物、四处飞溅的火花、飞箭),并使用物理组件知道在哪里绘制它。

  1. Entity.cs实体.cs

An entity will be typically made up of an ID and a list of components that are attached to it.一个实体通常由一个 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组件.cs

Starting with the marker interface.从标记界面开始。

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系统文件

Adding a collection for the SystemEntities .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组件池.cs

Next, we'll lay the ground work for what's to come.接下来,我们将为即将发生的事情奠定基础。 Our component pools should also have a non-generic interface, that we can fall back upon when we can't supply a component type.我们的组件池也应该有一个非通用接口,当我们不能提供组件类型时,我们可以依靠它。

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);
    }
}

Next step is the pool decorator.下一步是池装饰器。 The decorator pattern is implemented by exposing the same interface as the class it wraps, applying any desired behavior in the process.装饰器模式是通过公开与它包装的类相同的接口来实现的,在过程中应用任何所需的行为。 In our case, we want to check if added entities posses all the component types a system requires.在我们的例子中,我们想要检查添加的实体是否拥有系统所需的所有组件类型。 And if they do, add them to the collection.如果有,请将它们添加到集合中。

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);
    }
}

As said, you could place the burden of checking, and managing the system collections, on EntityManager .如上所述,您可以将检查和管理系统集合的负担放在EntityManager But our current design tends to diminish complexity and provide more flexibility in the long run.但从长远来看,我们目前的设计往往会降低复杂性并提供更大的灵活性。 Just wrap a pool once for each system it belongs to.只需为它所属的每个系统包装一次池。 If the system requires non default behavior, then you can create a new decorator specialized for that system -- without interfering with other systems.如果系统需要非默认行为,那么您可以创建一个专门用于该系统的新装饰器——而不会干扰其他系统。

  1. EntityManager.cs实体管理器.cs

The orchestrator (aka mediator, controller,...)协调器(又名调解器、控制器、...)

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程序.cs

Where it all comes together.这一切都聚集在一起的地方。

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();
    }
}

and maybe it's possible to create collections that require no entityId , so they only store the reference to the component that should get updated.并且也许可以创建不需要entityId ,因此它们只存储对应该更新的组件的引用。

As mentioned in the referenced wiki, that's actually discouraged.正如引用的 wiki 中所提到的,这实际上是不鼓励的。

It is a common practice to use a unique ID for each entity.为每个实体使用唯一 ID 是一种常见做法。 This is not a requirement, but it has several advantages:这不是必需的,但它有几个优点:

  • The entity can be referred using the ID instead of a pointer.可以使用 ID 而不是指针来引用实体。 This is more robust, as it would allow for the entity to be destroyed without leaving dangling pointers.这更加健壮,因为它允许在不留下悬空指针的情况下销毁实体。
  • It helps for saving state externally.它有助于在外部保存状态。 When the state is loaded again, there is no need for pointers to be reconstructed.当再次加载状态时,不需要重新构造指针。
  • Data can be shuffled around in memory as needed.数据可以根据需要在内存中混洗。
  • Entity ids can be used when communicating over a network to uniquely identify the entity.当通过网络进行通信时,可以使用实体 ID 来唯一标识实体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM