359 lines
7.2 KiB
Markdown
359 lines
7.2 KiB
Markdown
# 组件系统
|
||
|
||
在 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 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。 |