组件系统
在 ECS 架构中,组件(Component)是数据和行为的载体。组件定义了实体具有的属性和功能,是 ECS 架构的核心构建块。
基本概念
组件是继承自 Component
抽象基类的具体类,用于:
- 存储实体的数据(如位置、速度、健康值等)
- 定义与数据相关的行为方法
- 提供生命周期回调钩子
- 支持序列化和调试
创建组件
基础组件定义
typescript
import { Component, ECSComponent } from '@esengine/ecs-framework';
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
@ECSComponent('Health')
class Health extends Component {
current: number;
max: number;
constructor(max: number = 100) {
super();
this.max = max;
this.current = max;
}
// 组件可以包含行为方法
takeDamage(damage: number): void {
this.current = Math.max(0, this.current - damage);
}
heal(amount: number): void {
this.current = Math.min(this.max, this.current + amount);
}
isDead(): boolean {
return this.current <= 0;
}
}
组件装饰器
必须使用 @ECSComponent
装饰器,这确保了:
- 组件在代码混淆后仍能正确识别
- 提供稳定的类型名称用于序列化和调试
- 框架能正确管理组件注册
typescript
// ✅ 正确的用法
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// ❌ 错误的用法 - 没有装饰器
class BadComponent extends Component {
// 这样定义的组件可能在生产环境出现问题
}
组件生命周期
组件提供了生命周期钩子,可以重写来执行特定的逻辑:
typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
private resource: SomeResource | null = null;
/**
* 组件被添加到实体时调用
* 用于初始化资源、建立引用等
*/
onAddedToEntity(): void {
console.log(`组件 ${this.constructor.name} 被添加到实体 ${this.entity.name}`);
this.resource = new SomeResource();
}
/**
* 组件从实体移除时调用
* 用于清理资源、断开引用等
*/
onRemovedFromEntity(): void {
console.log(`组件 ${this.constructor.name} 从实体 ${this.entity.name} 移除`);
if (this.resource) {
this.resource.cleanup();
this.resource = null;
}
}
}
访问实体
组件可以通过 this.entity
访问其所属的实体:
typescript
@ECSComponent('Damage')
class Damage extends Component {
damage: number;
constructor(damage: number) {
super();
this.damage = damage;
}
// 在组件方法中访问实体和其他组件
applyDamage(): void {
const health = this.entity.getComponent(Health);
if (health) {
health.takeDamage(this.damage);
// 如果生命值为0,销毁实体
if (health.isDead()) {
this.entity.destroy();
}
}
}
}
组件属性
每个组件都有一些内置属性:
typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
someData: string = "example";
showComponentInfo(): void {
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
console.log(`所属实体: ${this.entity.name}`); // 所属实体引用
}
}
复杂组件示例
状态机组件
typescript
enum EntityState {
Idle,
Moving,
Attacking,
Dead
}
@ECSComponent('StateMachine')
class StateMachine extends Component {
private _currentState: EntityState = EntityState.Idle;
private _previousState: EntityState = EntityState.Idle;
private _stateTimer: number = 0;
get currentState(): EntityState {
return this._currentState;
}
get previousState(): EntityState {
return this._previousState;
}
get stateTimer(): number {
return this._stateTimer;
}
changeState(newState: EntityState): void {
if (this._currentState !== newState) {
this._previousState = this._currentState;
this._currentState = newState;
this._stateTimer = 0;
}
}
updateTimer(deltaTime: number): void {
this._stateTimer += deltaTime;
}
isInState(state: EntityState): boolean {
return this._currentState === state;
}
}
配置数据组件
typescript
interface WeaponData {
damage: number;
range: number;
fireRate: number;
ammo: number;
}
@ECSComponent('WeaponConfig')
class WeaponConfig extends Component {
data: WeaponData;
constructor(weaponData: WeaponData) {
super();
this.data = { ...weaponData }; // 深拷贝避免共享引用
}
// 提供便捷的访问方法
getDamage(): number {
return this.data.damage;
}
canFire(): boolean {
return this.data.ammo > 0;
}
consumeAmmo(): boolean {
if (this.data.ammo > 0) {
this.data.ammo--;
return true;
}
return false;
}
}
最佳实践
1. 保持组件简单
typescript
// ✅ 好的组件设计 - 单一职责
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// ❌ 避免的组件设计 - 职责过多
@ECSComponent('GameObject')
class GameObject extends Component {
x: number;
y: number;
dx: number;
dy: number;
health: number;
damage: number;
sprite: string;
// 太多不相关的属性
}
2. 使用构造函数初始化
typescript
@ECSComponent('Transform')
class Transform extends Component {
x: number;
y: number;
rotation: number;
scale: number;
constructor(x = 0, y = 0, rotation = 0, scale = 1) {
super();
this.x = x;
this.y = y;
this.rotation = rotation;
this.scale = scale;
}
}
3. 明确的类型定义
typescript
interface InventoryItem {
id: string;
name: string;
quantity: number;
type: 'weapon' | 'consumable' | 'misc';
}
@ECSComponent('Inventory')
class Inventory extends Component {
items: InventoryItem[] = [];
maxSlots: number;
constructor(maxSlots: number = 20) {
super();
this.maxSlots = maxSlots;
}
addItem(item: InventoryItem): boolean {
if (this.items.length < this.maxSlots) {
this.items.push(item);
return true;
}
return false;
}
removeItem(itemId: string): InventoryItem | null {
const index = this.items.findIndex(item => item.id === itemId);
if (index !== -1) {
return this.items.splice(index, 1)[0];
}
return null;
}
}
4. 避免在组件中存储实体引用
typescript
// ❌ 避免:在组件中存储其他实体的引用
@ECSComponent('BadFollower')
class BadFollower extends Component {
target: Entity; // 直接引用可能导致内存泄漏
}
// ✅ 推荐:存储实体ID,通过场景查找
@ECSComponent('Follower')
class Follower extends Component {
targetId: number;
followDistance: number = 50;
constructor(targetId: number) {
super();
this.targetId = targetId;
}
getTarget(): Entity | null {
return this.entity.scene?.findEntityById(this.targetId) || null;
}
}
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。