关于OOP到组件式到ECS的思考

本文主要总结最近在学习ECS架构时的一些心得体会。主要关于OOP、组件式开发和ECS架构三者之间的联系。

面向对象

在面向对象的开发中,如果我们需要复用一些代码,通常会用到类的集成和多态。
示例:

classDiagram
    class WorldTarget {
        +position()
        +render()
    }

    class Player {
        +position()
        +render()
        +attack()
    }

    class Enemy {
        +position()
        +render()
        +chasePlayer()
    }

    class EnemyX {
        +position()
        +render()
        +chasePlayer()
        +specialAttack()
    }

    class EnemyY {
        +position()
        +render()
        +chasePlayer()
        +defend()
    }
    

    WorldTarget <|-- Player
    WorldTarget <|-- Enemy
    Enemy <|-- EnemyX
    Enemy <|-- EnemyY

一个显而易见的问题是,随着需求的增加,类的层次结构会变得越来越复杂,代码的维护也会变得越来越困难。

组件式

基于继承的开发方式的缺点,我们想到使用组件式开发。
示例:

classDiagram
    class PositionComponent {
        +x
        +y
    }

    class RenderComponent {
        +render()
    }

    class AttackComponent {
        +attack()
    }

    class ChasePlayerComponent {
        +chasePlayer()
    }

    class SpecialAttackComponent {
        +specialAttack()
    }

    class DefendComponent {
        +defend()
    }

    class PlayerEntity {
        +position: PositionComponent
        +render: RenderComponent
        +attack: AttackComponent
    }

    class EnemyXEntity {
        +position: PositionComponent
        +render: RenderComponent
        +chasePlayer: ChasePlayerComponent
        +specialAttack: SpecialAttackComponent
    }

    class EnemyYEntity {
        +position: PositionComponent
        +render: RenderComponent
        +chasePlayer: ChasePlayerComponent
        +defend: DefendComponent
    }

    PlayerEntity o-- PositionComponent
    PlayerEntity o-- RenderComponent
    PlayerEntity o-- AttackComponent
    
    EnemyXEntity o-- PositionComponent
    EnemyXEntity o-- RenderComponent
    EnemyXEntity o-- ChasePlayerComponent
    EnemyXEntity o-- SpecialAttackComponent
    
    EnemyYEntity o-- PositionComponent
    EnemyYEntity o-- RenderComponent
    EnemyYEntity o-- ChasePlayerComponent
    EnemyYEntity o-- DefendComponent

从开发和维护的角度来看,组件式已经非常优秀了。

组件式的缺点

  1. CPU缓存未命中率高:由于组件数据在内存中是分散存储的,处理实体时需要频繁跳转内存地址,导致CPU缓存未命中率高,影响性能。
  2. 如果使用组件式,如果不进行组件进行统一管理,系统会变得混乱,难以维护。

主要讲一下第二点。
假设我们有两个entity:AvatarEntity和EnemyEntity。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class AvatarEntity {
private PositionComponent _position;
private PhysicsComponent _physics;

void Init(){
this.AddComponent<PhysicsComponent>();
this.AddComponent<PositionComponent>();
}

void Update(){
_physics.Update();
_position.Update();
}
}

class EnemyEntity {
private PositionComponent _position;
private PhysicsComponent _physics;

void Init(){
this.AddComponent<PhysicsComponent>();
this.AddComponent<PositionComponent>();
}

void Update(){
_physics.Update();
_position.Update();
}
}

在上面的代码中会有一个问题,在严格的游戏中,理论上我们应该严格的控制更新顺序,比如先更新物理组件,再更新位置组件。
但在上面代码中,AvatarEntity和EnemyEntity都各自管理自己的组件更新顺序,这样就会导致更新顺序混乱出现的bug。

实体-组件-系统架构

为了解决组件式的缺点,我们引入实体-组件-系统(ECS)架构。
ECS架构将组件的管理和更新交给系统来处理,从而保证了组件的统一管理和更新顺序。
ECS的三个核心概念如下:

  • 实体(Entity):作为纯粹的标识符,不保存逻辑,只负责关联组件。
  • 组件(Component):仅承载数据,遵守“只做数据不做行为”的约束。
  • 系统(System):针对特定组件组合进行遍历和更新,实现业务逻辑。

相比传统组件式开发,ECS能够带来以下直接收益:

  1. 更新顺序可控:所有系统由统一调度器串联执行,消除实体内部更新顺序不一致的问题。
  2. 数据更聚合:同类组件集中存储更能命中缓存,提高遍历效率。
  3. 易于并行化:系统之间仅共享只读数据或通过消息通信,可天然划分线程或任务。

统一调度示例

1
2
3
4
5
6
7
8
9
10
11
12
13
public sealed class Simulation {
private readonly List<ISystem> _updateSystems = new() {
new PhysicsSystem(),
new PositionSyncSystem(),
new RenderSystem()
};

public void Tick(float deltaTime, IReadOnlyList<Entity> entities) {
foreach (var system in _updateSystems) {
system.Update(deltaTime, entities);
}
}
}

该调度器集中控制系统的执行顺序,使得Avatar、Enemy等所有实体都按照统一的逻辑管线逐步更新,避免不同实体各自管理组件顺序造成的状态错乱。下图展示了调度器与系统、实体、组件之间的关系:

graph TD
    Scheduler[调度器 Simulation]
    Physics[PhysicsSystem]
    Position[PositionSyncSystem]
    Render[RenderSystem]
    Entities[(实体集合)]
    Components[(组件数据)]
    Scheduler --> Physics --> Entities
    Scheduler --> Position --> Entities
    Scheduler --> Render --> Entities
    Physics --> Components
    Position --> Components
    Render --> Components
    Entities -.引用.-> Components

关于OOP到组件式到ECS的思考
https://lshgame.com/2025/11/02/Thoughts_on_OOP_to_Component-Based_to_ECS/
作者
SuHang
发布于
2025年11月2日
许可协议