实体组件系统和共享公共基础类型的多个组件

2024-01-04

我正在尝试为我的游戏引擎实现一个简单的 ECS。我知道我的实现并不是严格意义上的 ECS,但我正在重构我的代码以使其更加基于组件。到目前为止,我有以下课程:

Entity:它是组件的容器,并且由于我希望我的实体具有相同类型的多个组件,因此它将它们存储在std::map<ComponentID,std::vector<std::unique_ptr<Component>>>。每个组件都有一个唯一的 ID(无符号整数),这是我从网上学到的一个简单模板技巧中获得的:

一个名为 GetUniqueComponentID 的函数:

using ComponentID = unsigned int;

inline ComponentID GetUniqueComponentID()
{
    static ComponentID id = 0;

    return id++;
}

包含一个仅生成递增数字的计数器。 我从名为 GetComponentID 的函数模板调用此函数:

template <typename T>
ComponentID GetComponentID()
{
    static ComponentID id = GetUniqueComponentID();

    return id;
}

该模板为我添加到实体的每个组件实例化一个不同的函数,因此需要检索组件的代码可以使用以下方式索引地图GetComponentId<Component_type>,将具体组件类型作为函数的模板参数。

实体类具有像 AddComponent 和 GetComponent 这样的方法,它们分别创建一个组件并将其添加到实体中,并检索一个组件(如果存在):

class Entity
{
public:
    Entity();
    ~Entity();
    template <typename T, typename... TArgs>
    T &AddComponent(TArgs&&... args);
    template <typename T>
    bool HasComponent();
    //template <typename T>
    //T &GetComponent();
    template <typename T> 
    std::vector<T*> GetComponents();
    bool IsAlive() { return mIsAlive; }
    void Destroy() { mIsAlive = false; }
private:
    //std::map<ComponentID, std::unique_ptr<Component>> mComponents;              // single component per type
    std::map<ComponentID, std::vector<std::unique_ptr<Component>>> mComponents;   // multiple components per type
    bool mIsAlive = true;
};


template <typename T, typename... TArgs>
T &Entity::AddComponent(TArgs&&... args)
{
    T *c = new T(std::forward<TArgs>(args)...);
    std::unique_ptr<Component> component(c);
    component->SetEntity(this);
    mComponents[GetComponentID<T>()].push_back(std::move(component));
    return *c;
}

template <typename T>
bool Entity::HasComponent()  // use bitset (faster)
{
    std::map<ComponentID, std::vector<std::unique_ptr<Component>>>::iterator it = mComponents.find(GetComponentID<T>());
    if (it != mComponents.end())
        return true;
    return false;
}

template <typename T>
std::vector<T*> Entity::GetComponents()
{
    std::vector<T*> components;
    for (std::unique_ptr<Component> &component : mComponents[GetComponentID<T>()])
        components.push_back(static_cast<T*>(component.get()));

    return components;
}

由于我想存储同一类型的多个组件,因此我将它们存储在std::map<ComponentID,std::vector<std::unique_ptr<Component>>>.

现在我的问题是:

我需要为某种类型的组件创建一个组件层次结构:我有一个 ForceGenerator 组件,它是各种具体 ForceGenerator(弹簧、重力等)的(抽象)基类。因此,我需要创建具体组件,但我需要通过指向基类的指针来多态地使用它们:我的物理子系统只需要关心指向基 ForceGenerator 的指针,调用其负责更新力的 Update() 方法。

我无法使用当前的方法,因为每次创建特定的 ForceGenerator 组件时,我都会使用不同的类型调用 AddComponent,而我需要将它们存储在同一个数组中(映射到基础 ForceGenerator 的组件 ID)。

我该如何解决这个问题?


您可以使用默认模板参数,如下所示:

class Entity
{
template <typename T,typename StoreAs=T, typename... TArgs>
    T &Entity::AddComponent(TArgs&&... args);
};
template <typename T,typename StoreAs, typename... TArgs>
T &Entity::AddComponent(TArgs&&... args)
{
     T *c = new T(std::forward<TArgs>(args)...);
     std::unique_ptr<Component> component(c);
     component->SetEntity(this);
     mComponents[GetComponentID<StoreAs()].push_back(std::move(component));
     return *c;
}

被称为像

 entity.AddComponent<T>(...)//Will instatiate AddComponent<T,T,...>
 entity.AddComponent<T,U>(...)//Will instatiate AddComponent<T,U,...>

您甚至可以更进一步,使用一些 SFINAE 仅当组件可以存储为该类型时才启用此功能:(实际上可能会也可能不会改善错误消息)

template <typename T,typename StoreAs, typename... TArgs>
std::enable_if_t<std::is_base_of_v<StoreAs,T>,T&> //Return type is `T&`
Entity::AddComponent(TArgs&&... args)
{
     T *c = new T(std::forward<TArgs>(args)...);
     std::unique_ptr<Component> component(c);
     component->SetEntity(this);
     mComponents[GetComponentID<StoreAs>()].push_back(std::move(component));
     return *c;
}

我假设Component是所有组件的基类。如果您有一组有限的、已知的组件,您可以将它们存储在std::variant<List types here>而不是唯一的指针。

编辑: 显然 clang 抱怨:“模板参数重新定义了默认参数”。 Gcc 不介意,但为了正确起见,将StoreAs初始化StoreAs=T只在Entity类中,不去实现。我编辑了源代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实体组件系统和共享公共基础类型的多个组件 的相关文章

  • 编译时运算符

    有人可以列出 C 中可用的所有编译时运算符吗 C 中有两个运算符 无论操作数如何 它们的结果始终可以在编译时确定 它们是sizeof 1 and 2 当然 其他运算符的许多特殊用途可以在编译时解决 例如标准中列出的那些整数常量表达式 1 与
  • “构建”构建我的项目,“构建解决方案”则不构建

    我刚刚开始使用VS2010 我有一个较大的解决方案 已从 VS2008 成功迁移 我已将一个名为 Test 的控制台应用程序项目添加到解决方案中 选择构建 gt 构建解决方案不编译新项目 选择构建 gt 构建测试确实构建了项目 在失败的情况
  • 以文化中立的方式将字符串拆分为单词

    我提出了下面的方法 旨在将可变长度的文本拆分为单词数组 以进行进一步的全文索引处理 删除停止词 然后进行词干分析 结果似乎不错 但我想听听关于这种实现对于不同语言的文本的可靠性的意见 您会建议使用正则表达式来代替吗 请注意 我选择不使用 S
  • Web 客户端和 Expect100Continue

    使用 WebClient C NET 时设置 Expect100Continue 的最佳方法是什么 我有下面的代码 我仍然在标题中看到 100 continue 愚蠢的 apache 仍然抱怨 505 错误 string url http
  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 按成员序列化

    我已经实现了template
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • 在 ASP.NET 5 中使用 DI 调用构造函数时解决依赖关系

    Web 上似乎充斥着如何在 ASP NET 5 中使用 DI 的示例 但没有一个示例显示如何调用构造函数并解决依赖关系 以下只是众多案例之一 http social technet microsoft com wiki contents a
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 这些作业之间是否存在顺序点?

    以下代码中的两个赋值之间是否存在序列点 f f x 1 1 x 2 不 没有 在这种情况下 标准确实是含糊不清的 如果你想确认这一点 gcc 有这个非常酷的选项 Wsequence point在这种情况下 它会警告您该操作可能未定义
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐