关于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
从开发和维护的角度来看,组件式已经非常优秀了。
组件式的缺点
- CPU缓存未命中率高:由于组件数据在内存中是分散存储的,处理实体时需要频繁跳转内存地址,导致CPU缓存未命中率高,影响性能。
- 如果使用组件式,如果不进行组件进行统一管理,系统会变得混乱,难以维护。
主要讲一下第二点。
假设我们有两个entity:AvatarEntity和EnemyEntity。
1 | |
在上面的代码中会有一个问题,在严格的游戏中,理论上我们应该严格的控制更新顺序,比如先更新物理组件,再更新位置组件。
但在上面代码中,AvatarEntity和EnemyEntity都各自管理自己的组件更新顺序,这样就会导致更新顺序混乱出现的bug。
实体-组件-系统架构
为了解决组件式的缺点,我们引入实体-组件-系统(ECS)架构。
ECS架构将组件的管理和更新交给系统来处理,从而保证了组件的统一管理和更新顺序。
ECS的三个核心概念如下:
- 实体(Entity):作为纯粹的标识符,不保存逻辑,只负责关联组件。
- 组件(Component):仅承载数据,遵守“只做数据不做行为”的约束。
- 系统(System):针对特定组件组合进行遍历和更新,实现业务逻辑。
相比传统组件式开发,ECS能够带来以下直接收益:
- 更新顺序可控:所有系统由统一调度器串联执行,消除实体内部更新顺序不一致的问题。
- 数据更聚合:同类组件集中存储更能命中缓存,提高遍历效率。
- 易于并行化:系统之间仅共享只读数据或通过消息通信,可天然划分线程或任务。
统一调度示例
1 | |
该调度器集中控制系统的执行顺序,使得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/