c# - 为系统循环创建高性能组件池

标签 c# entity-component-system

我正在设置一个非常小的实体-组件-系统示例,但组件池存在一些问题。

目前我的实体只是 ID (GUID),这很好。

每个系统都必须实现 ISystem界面

internal interface ISystem
{
    void Run();
}

并存储在 KeyedByTypeCollection 中.该集合确保每个系统都是独一无二的。

每个组件都必须实现 IComponent界面。

internal interface IComponent
{
}

通过这样做,我可以将所有不同的组件类型存储到它们匹配的组件池中。每个池都是一个 Dictionary<Guid, IComponent> .键表示实体的 ID,值是该实体的组件。每个池都存储在 KeyedByTypeCollection 中以确保组件池是唯一的。

目前我的EntityManager类包含核心逻辑。我不知道是否需要让它保持静态,但目前我的系统需要从中获取一些信息。

处理组件池的重要核心方法是:

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

我创建了一个用于测试目的的小型移动系统:

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

由于字典查找,这应该没问题。我不确定是否可以对其进行优化,但我认为我必须先遍历所有实体。

问题是:

我无法使用 KeyedByTypeCollection<Dictionary<Guid, IComponent>>因为我需要有类似的东西

KeyedByTypeCollection<Dictionary<Guid, Type where Type : IComponent>>

我的 GetComponentPoolAddComponentPool方法抛出错误。

GetComponentPool:无法将 IComponent 隐式转换为 TComponent

调用GetComponentPool时我将不得不从 IComponent 中转换字典值至 TComponent .

AddComponentPool:无法从 TComponent 转换为 IComponent

调用AddComponentPool时我将不得不投 TComponentIComponent .

我不认为转换是一种选择,因为这似乎会降低性能。

有人介意帮我解决类型问题吗?


更新:

调试时我用这段代码来测试整个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();
        }
    }
}

我已经展示了 Movement系统,这里是缺少的组件

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

最佳答案

问题的核心是Dictionary<Guid, TComponent>Dictionary<Guid, IComponent>是完全不相关的类型。它们不能相互转换。这是因为字典是可以读写的。如果你能转换 List<Giraffe>List<Animal>然后你可以存储 Tiger这会破坏类型安全。如果你可以反过来转换你可以阅读 Tiger实例并将它们视为 Giraffe这又会破坏类型安全。您可以在网络上搜索 .net covariance contravariance查找更多信息。


一个简单的解决方案是改变

KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools

Dictionary<Type, object> componentPools

.

这使您能够存储 Dictionary<Guid, TComponent>在以前不可能的收藏中。缺点是你需要现在施法。但是您只需要转换相当便宜的字典实例。您不需要在 O(N) 时间内转换整个字典。

访问器:

public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
    return (Dictionary<Guid, TComponent>)componentPools[typeof(TComponent)]; //cast inserted
}

public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
    componentPools.Add(typeof(TComponent), (object)new Dictionary<Guid, TComponent>()); //unneeded cast inserted for clarity
}

也许您根本不喜欢我们需要施法。我看不出有什么办法。实践经验表明,当您详细说明类型系统(泛型、变体)时,您会得到一个更糟糕的解决方案。所以不用担心。做有用的事。

关于c# - 为系统循环创建高性能组件池,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54129989/

相关文章:

c++ - 如何在 ECS 模型中组织实体?

c++ - 我的游戏引擎的Entity-Component-System中的GetComponent <>()函数返回编译器错误C2440

c# - 是否可以使用 NETMF 对嵌入式设备进行编程以将 geochron 记录写入 Azure?

c# - 如何强制继承者在自己实现之前运行某些代码

从套接字接收数据的 C# 性能方法?

c++ - 实体组件系统 undefined symbol

rust - 在Amethyst ECS架构中添加事件 channel

C# 异步并等待不等待代码完成

c# - 使用 Unity 框架和验证应用程序 block 验证方法参数

c++ - 实体组件系统的自定义堆预分配