ECS用数据驱动替代继承:Entity为带版本号的轻量ID,Component为纯数据,System处理组件组合;C++中通过编译期优化避免虚函数与指针跳转,提升内存友好性与复用性。

核心设计原则:用数据驱动代替继承
传统面向对象游戏架构常依赖深度继承树,比如 GameObject → Character → Player,导致逻辑耦合、复用困难、内存不友好。ECS(Entity-Component-System)反其道而行:实体(Entity)只是唯一ID,组件(Component)纯数据,系统(System)专注处理特定组件组合。C++中实现的关键是把运行时多态转为编译期/数据布局优化,避免虚函数和指针跳转。
实体(Entity):轻量ID,不存数据
Entity 不该是类,而应是紧凑、可快速比较/哈希的整数(如 uint32_t 或 uint64_t)。推荐用带版本号的稀疏索引(如 entity = (index ),防止悬挂引用。实际代码中可封装为:
struct Entity {
using id_type = uint32_t;
id_type id{0};
explicit Entity(id_type i) : id(i) {}
bool operator==(const Entity& other) const { return id == other.id; }
};
所有实体共用一个全局连续池(如 std::vector
组件(Component):POD结构,按需存储
每个组件必须是平凡类型(trivially copyable),不含虚函数、引用、非静态成员函数。例如:
立即学习“C++免费学习笔记(深入)”;
struct Position {
float x = 0.f, y = 0.f;
};
struct Velocity {
float dx = 0.f, dy = 0.f;
};
struct Renderable {
std::string sprite_name;
bool visible = true;
};
组件不挂载在实体上,而是由系统按类型集中管理。典型做法是为每种组件维护一个 std::vector
系统(System):遍历匹配组件,执行逻辑
System 是普通类或函数对象,不持有状态,只在帧更新时运行。关键在于如何高效查出同时拥有 Position 和 Velocity 的实体。简单可靠的做法是:
- 每个系统声明所需组件类型(如
requires) - 维护一份“该系统关心的实体ID列表”,在组件添加/移除时动态更新(事件驱动或脏标记)
- 运行时直接遍历这个ID列表,用ID查对应组件数据(O(1)随机访问)
示例更新逻辑:
void MovementSystem::update(World& world) {
for (auto entity : m_entities) {
auto& pos = world.get(entity);
auto& vel = world.get(entity);
pos.x += vel.dx * dt;
pos.y += vel.dy * dt;
}
}
进阶可引入 archetype(组件类型组合)或 chunk(批量内存块)提升缓存友好性,但简单项目用 vector + ID映射已足够清晰高效。










