文档及教程更新
This commit is contained in:
502
README.md
502
README.md
@@ -3,53 +3,45 @@
|
|||||||
[](https://badge.fury.io/js/%40esengine%2Fecs-framework)
|
[](https://badge.fury.io/js/%40esengine%2Fecs-framework)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
一个专业级的 TypeScript ECS(Entity-Component-System)框架,采用现代化架构设计,专为高性能游戏开发打造。
|
TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。
|
||||||
|
|
||||||
## ✨ 核心特性
|
> 🤔 **什么是 ECS?** 不熟悉 ECS 架构?建议先阅读 [ECS 架构基础](docs/concepts-explained.md#ecs-架构基础) 了解核心概念
|
||||||
|
|
||||||
- 🏗️ **现代化 ECS 架构** - 完整的实体组件系统,提供清晰的代码结构
|
## 特性
|
||||||
- 📡 **类型安全事件系统** - 增强的事件总线,支持异步事件、优先级、批处理和装饰器
|
|
||||||
- ⏰ **定时器管理系统** - 完整的定时器管理,支持延迟和重复任务
|
|
||||||
- 🔍 **智能查询系统** - 支持复杂的实体查询,流式API设计
|
|
||||||
- ⚡ **高性能优化** - 组件索引、Archetype系统、脏标记机制三重优化
|
|
||||||
- 🛠️ **开发者友好** - 完整的TypeScript支持,丰富的调试工具
|
|
||||||
- 📦 **轻量级设计** - 最小化依赖,适用于各种游戏引擎
|
|
||||||
|
|
||||||
## 📦 安装
|
- 🔧 **完整的 TypeScript 支持** - 强类型检查和代码提示
|
||||||
|
- 📡 **[类型安全事件系统](docs/concepts-explained.md#事件系统)** - 事件装饰器和异步事件处理
|
||||||
|
- 🔍 **[查询系统](docs/concepts-explained.md#实体管理)** - 流式 API 和智能缓存
|
||||||
|
- ⚡ **[性能优化](docs/concepts-explained.md#性能优化技术)** - 组件索引、Archetype 系统、脏标记
|
||||||
|
- 🎯 **[实体管理器](docs/concepts-explained.md#实体管理)** - 统一的实体生命周期管理
|
||||||
|
- 🧰 **调试工具** - 内置性能监控和调试信息
|
||||||
|
|
||||||
|
> 📖 **不熟悉这些概念?** 查看我们的 [技术概念详解](docs/concepts-explained.md) 了解它们的作用和应用场景
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @esengine/ecs-framework
|
npm install @esengine/ecs-framework
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 1. 基础设置
|
### 基础设置
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Core, CoreEvents, Scene } from '@esengine/ecs-framework';
|
import { Core, Scene, Entity, Component, EntitySystem } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 创建 Core 实例
|
// 创建核心实例
|
||||||
const core = Core.create(true); // 开启调试模式
|
const core = Core.create(true); // 调试模式
|
||||||
|
|
||||||
// 创建场景
|
// 创建场景
|
||||||
class GameScene extends Scene {
|
const scene = new Scene();
|
||||||
public initialize() {
|
Core.scene = scene;
|
||||||
// 场景初始化逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在游戏循环中更新框架
|
|
||||||
function gameLoop() {
|
|
||||||
Core.emitter.emit(CoreEvents.frameUpdated);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 创建实体和组件
|
### 定义组件
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Component, Entity } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 定义组件
|
|
||||||
class PositionComponent extends Component {
|
class PositionComponent extends Component {
|
||||||
constructor(public x: number = 0, public y: number = 0) {
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
super();
|
super();
|
||||||
@@ -62,18 +54,37 @@ class VelocityComponent extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建实体
|
class HealthComponent extends Component {
|
||||||
const entity = scene.createEntity("Player");
|
constructor(
|
||||||
entity.addComponent(new PositionComponent(100, 100));
|
public maxHealth: number = 100,
|
||||||
entity.addComponent(new VelocityComponent(10, 0));
|
public currentHealth: number = 100
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 创建处理系统
|
### 创建实体
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EntitySystem } from '@esengine/ecs-framework';
|
// 基础实体创建
|
||||||
|
const player = scene.createEntity("Player");
|
||||||
|
player.addComponent(new PositionComponent(100, 100));
|
||||||
|
player.addComponent(new VelocityComponent(5, 0));
|
||||||
|
player.addComponent(new HealthComponent(100, 100));
|
||||||
|
|
||||||
|
// 批量创建实体
|
||||||
|
const enemies = scene.createEntities(50, "Enemy");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
class MovementSystem extends EntitySystem {
|
class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public process(entities: Entity[]) {
|
public process(entities: Entity[]) {
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
const position = entity.getComponent(PositionComponent);
|
const position = entity.getComponent(PositionComponent);
|
||||||
@@ -91,236 +102,297 @@ class MovementSystem extends EntitySystem {
|
|||||||
scene.addEntityProcessor(new MovementSystem());
|
scene.addEntityProcessor(new MovementSystem());
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 实体查询和管理
|
### 游戏循环
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function gameLoop() {
|
||||||
|
// 更新场景
|
||||||
|
scene.update();
|
||||||
|
|
||||||
|
// 发送帧更新事件
|
||||||
|
Core.emitter.emit(CoreEvents.frameUpdated);
|
||||||
|
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameLoop();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实体管理器
|
||||||
|
|
||||||
|
EntityManager 提供了统一的实体管理接口:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EntityManager } from '@esengine/ecs-framework';
|
import { EntityManager } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 使用EntityManager进行高级查询
|
|
||||||
const entityManager = new EntityManager();
|
const entityManager = new EntityManager();
|
||||||
|
|
||||||
// 查询具有特定组件的实体
|
// 流式查询 API
|
||||||
const movingEntities = entityManager
|
const results = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll(PositionComponent, VelocityComponent)
|
.withAll(PositionComponent, VelocityComponent)
|
||||||
|
.withNone(HealthComponent)
|
||||||
|
.withTag(1)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// 查询带标签的实体
|
// 批量操作(使用Scene的方法)
|
||||||
const enemies = entityManager.getEntitiesByTag(1);
|
const bullets = scene.createEntities(100, "bullet");
|
||||||
|
|
||||||
// 批量创建实体
|
// 按标签查询
|
||||||
const bullets = entityManager.createEntities(100, "bullet");
|
const enemies = entityManager.getEntitiesByTag(2);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 事件系统
|
## 事件系统
|
||||||
|
|
||||||
|
### [基础事件](docs/concepts-explained.md#类型安全事件)
|
||||||
|
|
||||||
|
类型安全的事件系统,编译时检查事件名和数据类型。
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EventBus, ECSEventType, EventHandler } from '@esengine/ecs-framework';
|
import { EventBus, ECSEventType } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 获取事件总线
|
|
||||||
const eventBus = entityManager.eventBus;
|
const eventBus = entityManager.eventBus;
|
||||||
|
|
||||||
// 监听实体创建事件
|
// 监听预定义事件
|
||||||
eventBus.onEntityCreated((data) => {
|
eventBus.onEntityCreated((data) => {
|
||||||
console.log(`Entity created: ${data.entityName}`);
|
console.log(`实体创建: ${data.entityName}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听组件添加事件
|
|
||||||
eventBus.onComponentAdded((data) => {
|
eventBus.onComponentAdded((data) => {
|
||||||
console.log(`Component ${data.componentType} added to entity ${data.entityId}`);
|
console.log(`组件添加: ${data.componentType}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用装饰器自动注册事件监听器
|
|
||||||
class GameManager {
|
|
||||||
@EventHandler(ECSEventType.ENTITY_DESTROYED)
|
|
||||||
onEntityDestroyed(data) {
|
|
||||||
console.log('Entity destroyed:', data.entityName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义事件
|
// 自定义事件
|
||||||
eventBus.emit('player:levelup', { playerId: 123, newLevel: 5 });
|
eventBus.emit('player:death', { playerId: 123, reason: 'fall' });
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🆚 框架对比
|
### [事件装饰器](docs/concepts-explained.md#事件装饰器)
|
||||||
|
|
||||||
与其他 TypeScript ECS 框架相比,我们的优势:
|
使用装饰器语法自动注册事件监听器,减少样板代码。
|
||||||
|
|
||||||
| 特性 | @esengine/ecs-framework | bitecs | ecsy | Miniplex |
|
|
||||||
|------|-------------------------|-------|------|----------|
|
|
||||||
| **TypeScript 支持** | ✅ 原生支持 | ✅ 完整支持 | ⚠️ 部分支持 | ✅ 原生支持 |
|
|
||||||
| **事件系统** | ✅ 类型安全+装饰器 | ❌ 无内置事件系统 | ⚠️ 基础事件 | ✅ 响应式事件 |
|
|
||||||
| **查询系统** | ✅ 智能查询+流式API | ✅ 高性能 | ✅ 基础查询 | ✅ 响应式查询 |
|
|
||||||
| **性能优化** | ✅ 多层优化系统 | ✅ 高性能优化 | ⚠️ 基础优化 | ✅ React集成优化 |
|
|
||||||
| **实体管理器** | ✅ 统一管理接口 | ❌ 无统一接口 | ✅ 基础管理 | ✅ 响应式管理 |
|
|
||||||
| **组件索引** | ✅ 哈希+位图索引 | ✅ 原生支持 | ❌ 无索引系统 | ✅ 自动索引 |
|
|
||||||
| **Archetype系统** | ✅ 内置支持 | ✅ 内置支持 | ❌ 无Archetype | ❌ 无Archetype |
|
|
||||||
| **脏标记系统** | ✅ 细粒度追踪 | ⚠️ 基础支持 | ❌ 无脏标记 | ✅ React级追踪 |
|
|
||||||
| **批量操作** | ✅ 全面的批量API | ✅ 批量支持 | ⚠️ 有限支持 | ⚠️ 有限支持 |
|
|
||||||
| **游戏引擎集成** | ✅ 通用设计 | ✅ 通用设计 | ✅ 通用设计 | ⚠️ 主要针对React |
|
|
||||||
| **学习曲线** | 🟢 中等 | 🟡 较陡峭 | 🟢 简单 | 🟡 需要React知识 |
|
|
||||||
| **社区生态** | 🟡 成长中 | 🟢 活跃 | 🟡 稳定 | 🟡 小众但精品 |
|
|
||||||
|
|
||||||
### 为什么选择我们?
|
|
||||||
|
|
||||||
**相比 bitecs**:
|
|
||||||
- 更友好的 TypeScript API,无需手动管理内存
|
|
||||||
- 完整的实体管理器,开发体验更佳
|
|
||||||
- 内置类型安全事件系统,bitecs需要自己实现
|
|
||||||
- 多种索引系统可选,适应不同场景
|
|
||||||
|
|
||||||
**相比 ecsy**:
|
|
||||||
- 现代化的性能优化系统(组件索引、Archetype、脏标记)
|
|
||||||
- 更完整的 TypeScript 类型定义
|
|
||||||
- 增强的事件系统,支持装饰器和异步事件
|
|
||||||
- 活跃的维护和功能更新
|
|
||||||
|
|
||||||
**相比 Miniplex**:
|
|
||||||
- 不依赖 React 生态,可用于任何游戏引擎
|
|
||||||
- 专门针对游戏开发优化
|
|
||||||
- 更轻量级的核心设计
|
|
||||||
- 传统事件模式,更适合游戏开发习惯
|
|
||||||
|
|
||||||
## 📚 核心概念
|
|
||||||
|
|
||||||
### Entity(实体)
|
|
||||||
实体是游戏世界中的基本对象,可以挂载组件和运行系统。
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 创建实体
|
import { EventHandler, ECSEventType } from '@esengine/ecs-framework';
|
||||||
const entity = scene.createEntity("Player");
|
|
||||||
|
|
||||||
// 设置实体属性
|
class GameSystem {
|
||||||
entity.tag = 1;
|
@EventHandler(ECSEventType.ENTITY_DESTROYED)
|
||||||
entity.updateOrder = 0;
|
onEntityDestroyed(data: EntityDestroyedEventData) {
|
||||||
entity.enabled = true;
|
console.log('实体销毁:', data.entityName);
|
||||||
|
|
||||||
// 批量创建实体
|
|
||||||
const entities = scene.createEntities(100, "Enemy");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component(组件)
|
|
||||||
组件存储数据,定义实体的属性和状态。
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class HealthComponent extends Component {
|
|
||||||
public maxHealth: number = 100;
|
|
||||||
public currentHealth: number = 100;
|
|
||||||
|
|
||||||
public takeDamage(damage: number) {
|
|
||||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
|
||||||
if (this.currentHealth <= 0) {
|
|
||||||
this.entity.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 添加组件到实体
|
@EventHandler('player:levelup')
|
||||||
entity.addComponent(new HealthComponent());
|
onPlayerLevelUp(data: { playerId: number; newLevel: number }) {
|
||||||
```
|
console.log(`玩家 ${data.playerId} 升级到 ${data.newLevel} 级`);
|
||||||
|
|
||||||
### System(系统)
|
|
||||||
系统处理具有特定组件的实体集合,实现游戏逻辑。
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntitySystem, Entity } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class HealthSystem extends EntitySystem {
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
for (const entity of entities) {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (health && health.currentHealth <= 0) {
|
|
||||||
// 处理实体死亡逻辑
|
|
||||||
entity.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🧪 测试
|
## 性能优化
|
||||||
|
|
||||||
```bash
|
### [组件索引](docs/concepts-explained.md#组件索引系统)
|
||||||
# 运行所有测试
|
|
||||||
npm run test
|
|
||||||
|
|
||||||
# 性能基准测试
|
通过建立索引避免线性搜索,将查询复杂度从 O(n) 降低到 O(1)。
|
||||||
npm run benchmark
|
|
||||||
|
```typescript
|
||||||
|
// 使用Scene的查询系统进行组件索引
|
||||||
|
const querySystem = scene.querySystem;
|
||||||
|
|
||||||
|
// 查询具有特定组件的实体
|
||||||
|
const entitiesWithPosition = querySystem.queryAll(PositionComponent).entities;
|
||||||
|
const entitiesWithVelocity = querySystem.queryAll(VelocityComponent).entities;
|
||||||
|
|
||||||
|
// 性能统计
|
||||||
|
const stats = querySystem.getStats();
|
||||||
|
console.log('查询效率:', stats.hitRate);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 文档
|
**索引类型选择:**
|
||||||
|
- **哈希索引** - 适合稳定的、大量的组件(如位置、生命值)
|
||||||
|
- **位图索引** - 适合频繁变化的组件(如Buff、状态)
|
||||||
|
|
||||||
- [快速入门](docs/getting-started.md) - 从零开始学习框架使用
|
> 📋 详细选择指南参见 [索引类型选择指南](docs/concepts-explained.md#索引类型选择指南)
|
||||||
- [EntityManager 使用指南](docs/entity-manager-example.md) - 详细了解实体管理器的高级功能
|
|
||||||
- [事件系统使用指南](docs/event-system-example.md) - 学习类型安全事件系统的完整用法
|
|
||||||
- [性能优化指南](docs/performance-optimization.md) - 深入了解三大性能优化系统
|
|
||||||
- [核心概念](docs/core-concepts.md) - 深入了解 ECS 架构和设计原理
|
|
||||||
- [查询系统使用指南](docs/query-system-usage.md) - 学习高性能查询系统的详细用法
|
|
||||||
|
|
||||||
## 🔗 扩展库
|
### [Archetype 系统](docs/concepts-explained.md#archetype-系统)
|
||||||
|
|
||||||
- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、广度优先、Dijkstra、GOAP 算法
|
将具有相同组件组合的实体分组,减少查询时的组件检查开销。
|
||||||
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI 系统
|
|
||||||
|
|
||||||
## 🤝 贡献
|
```typescript
|
||||||
|
// 使用查询系统的Archetype功能
|
||||||
|
const querySystem = scene.querySystem;
|
||||||
|
|
||||||
欢迎提交 Issue 和 Pull Request!
|
// 查询统计
|
||||||
|
const stats = querySystem.getStats();
|
||||||
### 开发环境设置
|
console.log('缓存命中率:', stats.hitRate);
|
||||||
|
|
||||||
```bash
|
|
||||||
# 克隆项目
|
|
||||||
git clone https://github.com/esengine/ecs-framework.git
|
|
||||||
cd ecs-framework
|
|
||||||
|
|
||||||
# 运行基准测试
|
|
||||||
node benchmark.js
|
|
||||||
|
|
||||||
# 开发构建 (在source目录)
|
|
||||||
cd source && npm install && npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 构建要求
|
### [脏标记系统](docs/concepts-explained.md#脏标记系统)
|
||||||
|
|
||||||
|
追踪数据变化,只处理发生改变的实体,避免不必要的计算。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 脏标记通过组件系统自动管理
|
||||||
|
// 组件变化时会自动标记为脏数据
|
||||||
|
|
||||||
|
// 查询系统会自动处理脏标记优化
|
||||||
|
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💡 **不确定何时使用这些优化?** 查看 [性能优化建议](docs/concepts-explained.md#性能建议) 了解适用场景
|
||||||
|
|
||||||
|
## API 参考
|
||||||
|
|
||||||
|
### 核心类
|
||||||
|
|
||||||
|
| 类 | 描述 |
|
||||||
|
|---|---|
|
||||||
|
| `Core` | 框架核心管理类 |
|
||||||
|
| `Scene` | 场景容器,管理实体和系统 |
|
||||||
|
| `Entity` | 实体对象,包含组件集合 |
|
||||||
|
| `Component` | 组件基类 |
|
||||||
|
| `EntitySystem` | 系统基类 |
|
||||||
|
| `EntityManager` | 实体管理器 |
|
||||||
|
|
||||||
|
### 查询 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(...components) // 包含所有指定组件
|
||||||
|
.withAny(...components) // 包含任意指定组件
|
||||||
|
.withNone(...components) // 不包含指定组件
|
||||||
|
.withTag(tag) // 包含指定标签
|
||||||
|
.withoutTag(tag) // 不包含指定标签
|
||||||
|
.execute() // 执行查询
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enum ECSEventType {
|
||||||
|
ENTITY_CREATED = 'entity:created',
|
||||||
|
ENTITY_DESTROYED = 'entity:destroyed',
|
||||||
|
COMPONENT_ADDED = 'component:added',
|
||||||
|
COMPONENT_REMOVED = 'component:removed',
|
||||||
|
SYSTEM_ADDED = 'system:added',
|
||||||
|
SYSTEM_REMOVED = 'system:removed'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与其他框架对比
|
||||||
|
|
||||||
|
| 特性 | @esengine/ecs-framework | bitECS | Miniplex |
|
||||||
|
|------|-------------------------|--------|----------|
|
||||||
|
| TypeScript 支持 | ✅ 原生支持 | ✅ 完整支持 | ✅ 原生支持 |
|
||||||
|
| 事件系统 | ✅ 内置+装饰器 | ❌ 需自己实现 | ✅ 响应式 |
|
||||||
|
| 查询系统 | ✅ 流式 API | ✅ 函数式 | ✅ 响应式 |
|
||||||
|
| 实体管理器 | ✅ 统一接口 | ❌ 低级 API | ✅ 高级接口 |
|
||||||
|
| 性能优化 | ✅ 多重优化 | ✅ 极致性能 | ✅ React 优化 |
|
||||||
|
| 游戏引擎集成 | ✅ 通用设计 | ✅ 通用设计 | ⚠️ 主要 React |
|
||||||
|
|
||||||
|
**选择指南:**
|
||||||
|
- 选择本框架:需要完整的游戏开发工具链和中文社区支持
|
||||||
|
- 选择 bitECS:需要极致性能和最小化设计
|
||||||
|
- 选择 Miniplex:主要用于 React 应用开发
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ecs-framework/
|
||||||
|
├── src/
|
||||||
|
│ ├── ECS/ # ECS 核心系统
|
||||||
|
│ │ ├── Core/ # 核心管理器
|
||||||
|
│ │ ├── Systems/ # 系统类型
|
||||||
|
│ │ └── Utils/ # ECS 工具
|
||||||
|
│ ├── Types/ # TypeScript接口定义
|
||||||
|
│ └── Utils/ # 通用工具
|
||||||
|
├── docs/ # 文档
|
||||||
|
└── scripts/ # 构建脚本
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文档
|
||||||
|
|
||||||
|
### 🎯 新手入门
|
||||||
|
- **[📖 新手教程完整指南](docs/beginner-tutorials.md)** - 完整学习路径,从零开始 ⭐ **强烈推荐**
|
||||||
|
- [🚀 快速入门](docs/getting-started.md) - 详细的入门教程
|
||||||
|
- [🧠 技术概念详解](docs/concepts-explained.md) - 通俗易懂的技术概念解释 ⭐ **推荐新手阅读**
|
||||||
|
- [🎯 位掩码使用指南](docs/bitmask-guide.md) - 位掩码概念、原理和高级使用技巧
|
||||||
|
- [💡 使用场景示例](docs/use-cases.md) - 不同类型游戏的具体应用案例
|
||||||
|
- [🔧 框架类型系统](docs/concepts-explained.md#框架类型系统) - TypeScript接口设计和使用指南
|
||||||
|
|
||||||
|
### 📚 核心功能
|
||||||
|
- [🎭 实体管理指南](docs/entity-guide.md) - 实体的创建和使用方法
|
||||||
|
- [🧩 组件设计指南](docs/component-design-guide.md) - 如何设计高质量组件 ⭐ **设计必读**
|
||||||
|
- [⚙️ 系统详解指南](docs/system-guide.md) - 四种系统类型的详细使用
|
||||||
|
- [🎬 场景管理指南](docs/scene-management-guide.md) - 场景切换和数据管理
|
||||||
|
- [⏰ 定时器系统指南](docs/timer-guide.md) - 定时器的完整使用方法
|
||||||
|
|
||||||
|
### API 参考
|
||||||
|
- [核心 API 参考](docs/core-concepts.md) - 完整的 API 使用说明
|
||||||
|
- [实体基础指南](docs/entity-guide.md) - 实体的基本概念和操作
|
||||||
|
- [EntityManager 指南](docs/entity-manager-example.md) - 高性能查询和批量操作
|
||||||
|
- [事件系统指南](docs/event-system-example.md) - 事件系统完整用法
|
||||||
|
- [查询系统指南](docs/query-system-usage.md) - 查询系统使用方法
|
||||||
|
|
||||||
|
### 性能相关
|
||||||
|
- [性能优化指南](docs/performance-optimization.md) - 性能优化技术和策略
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 监听模式
|
||||||
|
npm run build:watch
|
||||||
|
|
||||||
|
# 清理构建文件
|
||||||
|
npm run clean
|
||||||
|
|
||||||
|
# 重新构建
|
||||||
|
npm run rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能监控
|
||||||
|
|
||||||
|
框架提供内置性能统计:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 场景统计
|
||||||
|
const sceneStats = scene.getStats();
|
||||||
|
console.log('性能统计:', {
|
||||||
|
实体数量: sceneStats.entityCount,
|
||||||
|
系统数量: sceneStats.processorCount
|
||||||
|
});
|
||||||
|
|
||||||
|
// 查询系统统计
|
||||||
|
const queryStats = scene.querySystem.getStats();
|
||||||
|
console.log('查询统计:', {
|
||||||
|
缓存命中率: queryStats.hitRate + '%',
|
||||||
|
查询次数: queryStats.queryCount
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展库
|
||||||
|
|
||||||
|
- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、BFS、Dijkstra 算法
|
||||||
|
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI
|
||||||
|
|
||||||
|
## 社区
|
||||||
|
|
||||||
|
- QQ 群:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
|
||||||
|
- GitHub:[提交 Issue](https://github.com/esengine/ecs-framework/issues)
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
欢迎提交 Pull Request 和 Issue!
|
||||||
|
|
||||||
|
### 开发要求
|
||||||
|
|
||||||
- Node.js >= 14.0.0
|
- Node.js >= 14.0.0
|
||||||
- TypeScript >= 4.0.0
|
- TypeScript >= 4.0.0
|
||||||
|
|
||||||
## 📄 许可证
|
## 许可证
|
||||||
|
|
||||||
本项目采用 [MIT](LICENSE) 许可证。
|
[MIT](LICENSE)
|
||||||
|
|
||||||
## 💬 交流群
|
|
||||||
|
|
||||||
加入 QQ 群讨论:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
|
|
||||||
|
|
||||||
### 🚀 核心性能指标
|
|
||||||
|
|
||||||
```bash
|
|
||||||
实体创建: 640,000+ 个/秒
|
|
||||||
组件查询: O(1) 复杂度(使用索引)
|
|
||||||
内存优化: 30-50% 减少分配
|
|
||||||
批量操作: 显著提升处理效率
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎯 性能优化技术
|
|
||||||
|
|
||||||
- **组件索引系统**: 哈希和位图双重索引,支持 O(1) 查询
|
|
||||||
- **Archetype 系统**: 按组件组合分组,减少查询开销
|
|
||||||
- **脏标记机制**: 细粒度变更追踪,避免不必要的计算
|
|
||||||
- **批量操作 API**: 减少函数调用开销,提升大规模操作效率
|
|
||||||
- **智能缓存**: 查询结果缓存和延迟清理机制
|
|
||||||
|
|
||||||
### 🔧 性能建议
|
|
||||||
|
|
||||||
1. **大规模场景**: 使用批量API和组件索引
|
|
||||||
2. **频繁查询**: 启用Archetype系统进行快速筛选
|
|
||||||
3. **实时游戏**: 利用脏标记减少无效更新
|
|
||||||
4. **移动端**: 建议实体数量控制在20,000以内
|
|
||||||
|
|
||||||
运行 `npm run benchmark` 查看在您的环境中的具体性能表现。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**ECS Framework** - 让游戏开发更简单、更高效!
|
|
||||||
BIN
docs/beginner-tutorials.md
Normal file
BIN
docs/beginner-tutorials.md
Normal file
Binary file not shown.
431
docs/bitmask-guide.md
Normal file
431
docs/bitmask-guide.md
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
# 位掩码使用指南
|
||||||
|
|
||||||
|
本文档详细解释ECS框架中位掩码的概念、原理和使用方法。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [什么是位掩码](#什么是位掩码)
|
||||||
|
2. [位掩码的优势](#位掩码的优势)
|
||||||
|
3. [基础使用方法](#基础使用方法)
|
||||||
|
4. [高级位掩码操作](#高级位掩码操作)
|
||||||
|
5. [实际应用场景](#实际应用场景)
|
||||||
|
6. [性能优化技巧](#性能优化技巧)
|
||||||
|
|
||||||
|
## 什么是位掩码
|
||||||
|
|
||||||
|
### 基本概念
|
||||||
|
|
||||||
|
位掩码(BitMask)是一种使用二进制位来表示状态或属性的技术。在ECS框架中,每个组件类型对应一个二进制位,实体的组件组合可以用一个数字来表示。
|
||||||
|
|
||||||
|
### 简单例子
|
||||||
|
|
||||||
|
假设我们有以下组件:
|
||||||
|
- PositionComponent → 位置 0 (二进制: 001)
|
||||||
|
- VelocityComponent → 位置 1 (二进制: 010)
|
||||||
|
- HealthComponent → 位置 2 (二进制: 100)
|
||||||
|
|
||||||
|
那么一个同时拥有Position和Health组件的实体,其位掩码就是:
|
||||||
|
```
|
||||||
|
001 (Position) + 100 (Health) = 101 (二进制) = 5 (十进制)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可视化理解
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 组件类型对应的位位置
|
||||||
|
PositionComponent → 位置0 → 2^0 = 1 → 二进制: 001
|
||||||
|
VelocityComponent → 位置1 → 2^1 = 2 → 二进制: 010
|
||||||
|
HealthComponent → 位置2 → 2^2 = 4 → 二进制: 100
|
||||||
|
RenderComponent → 位置3 → 2^3 = 8 → 二进制: 1000
|
||||||
|
|
||||||
|
// 实体的组件组合示例
|
||||||
|
实体A: Position + Velocity → 001 + 010 = 011 (二进制) = 3 (十进制)
|
||||||
|
实体B: Position + Health → 001 + 100 = 101 (二进制) = 5 (十进制)
|
||||||
|
实体C: Position + Velocity + Health → 001 + 010 + 100 = 111 (二进制) = 7 (十进制)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 位掩码的优势
|
||||||
|
|
||||||
|
### 1. 极快的查询速度
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 传统方式:需要遍历组件列表
|
||||||
|
function hasComponents(entity, componentTypes) {
|
||||||
|
for (const type of componentTypes) {
|
||||||
|
if (!entity.hasComponent(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 位掩码方式:一次位运算即可
|
||||||
|
function hasComponentsMask(entityMask, requiredMask) {
|
||||||
|
return (entityMask & requiredMask) === requiredMask;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 内存效率
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 一个bigint可以表示64个组件的组合状态
|
||||||
|
// 相比存储组件列表,内存使用量大大减少
|
||||||
|
|
||||||
|
const entity = scene.createEntity("Player");
|
||||||
|
entity.addComponent(new PositionComponent());
|
||||||
|
entity.addComponent(new HealthComponent());
|
||||||
|
|
||||||
|
// 获取位掩码(只是一个数字)
|
||||||
|
const mask = entity.componentMask; // bigint类型
|
||||||
|
console.log(`位掩码: ${mask}`); // 输出: 5 (二进制: 101)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 批量操作优化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 可以快速筛选大量实体
|
||||||
|
const entities = scene.getAllEntities();
|
||||||
|
const requiredMask = BigInt(0b101); // Position + Health
|
||||||
|
|
||||||
|
const filteredEntities = entities.filter(entity =>
|
||||||
|
(entity.componentMask & requiredMask) === requiredMask
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基础使用方法
|
||||||
|
|
||||||
|
### 获取实体的位掩码
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Scene, Entity, Component } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 创建组件
|
||||||
|
class PositionComponent extends Component {
|
||||||
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HealthComponent extends Component {
|
||||||
|
constructor(public maxHealth: number = 100) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建实体并添加组件
|
||||||
|
const scene = new Scene();
|
||||||
|
const entity = scene.createEntity("Player");
|
||||||
|
|
||||||
|
console.log(`初始位掩码: ${entity.componentMask}`); // 0
|
||||||
|
|
||||||
|
entity.addComponent(new PositionComponent(100, 200));
|
||||||
|
console.log(`添加Position后: ${entity.componentMask}`); // 可能是 1
|
||||||
|
|
||||||
|
entity.addComponent(new HealthComponent(100));
|
||||||
|
console.log(`添加Health后: ${entity.componentMask}`); // 可能是 5
|
||||||
|
|
||||||
|
// 查看二进制表示
|
||||||
|
console.log(`二进制表示: ${entity.componentMask.toString(2)}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动检查位掩码
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 检查实体是否拥有特定组件组合
|
||||||
|
function checkEntityComponents(entity: Entity) {
|
||||||
|
const mask = entity.componentMask;
|
||||||
|
|
||||||
|
// 将位掩码转换为二进制字符串查看
|
||||||
|
const binaryString = mask.toString(2).padStart(8, '0');
|
||||||
|
console.log(`实体组件状态: ${binaryString}`);
|
||||||
|
|
||||||
|
// 分析每一位
|
||||||
|
console.log(`位0 (Position): ${(mask & 1n) !== 0n ? '有' : '无'}`);
|
||||||
|
console.log(`位1 (Velocity): ${(mask & 2n) !== 0n ? '有' : '无'}`);
|
||||||
|
console.log(`位2 (Health): ${(mask & 4n) !== 0n ? '有' : '无'}`);
|
||||||
|
console.log(`位3 (Render): ${(mask & 8n) !== 0n ? '有' : '无'}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级位掩码操作
|
||||||
|
|
||||||
|
### 使用BitMaskOptimizer
|
||||||
|
|
||||||
|
框架提供了BitMaskOptimizer类来简化位掩码操作:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BitMaskOptimizer } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 获取优化器实例
|
||||||
|
const optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
|
||||||
|
// 注册组件类型(建议在游戏初始化时进行)
|
||||||
|
optimizer.registerComponentType('PositionComponent');
|
||||||
|
optimizer.registerComponentType('VelocityComponent');
|
||||||
|
optimizer.registerComponentType('HealthComponent');
|
||||||
|
optimizer.registerComponentType('RenderComponent');
|
||||||
|
|
||||||
|
// 创建单个组件的掩码
|
||||||
|
const positionMask = optimizer.createSingleComponentMask('PositionComponent');
|
||||||
|
console.log(`Position掩码: ${positionMask} (二进制: ${positionMask.toString(2)})`);
|
||||||
|
|
||||||
|
// 创建组合掩码
|
||||||
|
const movementMask = optimizer.createCombinedMask(['PositionComponent', 'VelocityComponent']);
|
||||||
|
console.log(`Movement掩码: ${movementMask} (二进制: ${movementMask.toString(2)})`);
|
||||||
|
|
||||||
|
// 检查实体是否匹配掩码
|
||||||
|
const entity = scene.createEntity("TestEntity");
|
||||||
|
entity.addComponent(new PositionComponent());
|
||||||
|
entity.addComponent(new VelocityComponent());
|
||||||
|
|
||||||
|
const hasMovementComponents = optimizer.maskContainsAllComponents(
|
||||||
|
entity.componentMask,
|
||||||
|
['PositionComponent', 'VelocityComponent']
|
||||||
|
);
|
||||||
|
console.log(`实体拥有移动组件: ${hasMovementComponents}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 位掩码分析工具
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 分析位掩码的实用函数
|
||||||
|
class MaskAnalyzer {
|
||||||
|
private optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
|
||||||
|
// 分析实体的组件组合
|
||||||
|
analyzeEntity(entity: Entity): void {
|
||||||
|
const mask = entity.componentMask;
|
||||||
|
const componentNames = this.optimizer.maskToComponentNames(mask);
|
||||||
|
const componentCount = this.optimizer.getComponentCount(mask);
|
||||||
|
|
||||||
|
console.log(`实体 ${entity.name} 分析:`);
|
||||||
|
console.log(`- 位掩码: ${mask} (二进制: ${mask.toString(2)})`);
|
||||||
|
console.log(`- 组件数量: ${componentCount}`);
|
||||||
|
console.log(`- 组件列表: ${componentNames.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较两个实体的组件差异
|
||||||
|
compareEntities(entityA: Entity, entityB: Entity): void {
|
||||||
|
const maskA = entityA.componentMask;
|
||||||
|
const maskB = entityB.componentMask;
|
||||||
|
|
||||||
|
const commonMask = maskA & maskB;
|
||||||
|
const onlyInA = maskA & ~maskB;
|
||||||
|
const onlyInB = maskB & ~maskA;
|
||||||
|
|
||||||
|
console.log(`实体比较:`);
|
||||||
|
console.log(`- 共同组件: ${this.optimizer.maskToComponentNames(commonMask).join(', ')}`);
|
||||||
|
console.log(`- 仅在A中: ${this.optimizer.maskToComponentNames(onlyInA).join(', ')}`);
|
||||||
|
console.log(`- 仅在B中: ${this.optimizer.maskToComponentNames(onlyInB).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找具有特定组件组合的实体
|
||||||
|
findEntitiesWithMask(entities: Entity[], requiredComponents: string[]): Entity[] {
|
||||||
|
const requiredMask = this.optimizer.createCombinedMask(requiredComponents);
|
||||||
|
|
||||||
|
return entities.filter(entity =>
|
||||||
|
(entity.componentMask & requiredMask) === requiredMask
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
const analyzer = new MaskAnalyzer();
|
||||||
|
analyzer.analyzeEntity(entity);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实际应用场景
|
||||||
|
|
||||||
|
### 1. 高性能实体查询
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameSystem {
|
||||||
|
private optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
private movementMask: bigint;
|
||||||
|
private combatMask: bigint;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 预计算常用掩码
|
||||||
|
this.movementMask = this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'VelocityComponent'
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.combatMask = this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'HealthComponent', 'WeaponComponent'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快速查找移动实体
|
||||||
|
findMovingEntities(entities: Entity[]): Entity[] {
|
||||||
|
return entities.filter(entity =>
|
||||||
|
(entity.componentMask & this.movementMask) === this.movementMask
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快速查找战斗单位
|
||||||
|
findCombatUnits(entities: Entity[]): Entity[] {
|
||||||
|
return entities.filter(entity =>
|
||||||
|
(entity.componentMask & this.combatMask) === this.combatMask
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 实体分类和管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class EntityClassifier {
|
||||||
|
private optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
|
||||||
|
// 定义实体类型掩码
|
||||||
|
private readonly ENTITY_TYPES = {
|
||||||
|
PLAYER: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'HealthComponent', 'InputComponent'
|
||||||
|
]),
|
||||||
|
ENEMY: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'HealthComponent', 'AIComponent'
|
||||||
|
]),
|
||||||
|
PROJECTILE: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'VelocityComponent', 'DamageComponent'
|
||||||
|
]),
|
||||||
|
PICKUP: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'PickupComponent'
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据组件组合判断实体类型
|
||||||
|
classifyEntity(entity: Entity): string {
|
||||||
|
const mask = entity.componentMask;
|
||||||
|
|
||||||
|
for (const [type, typeMask] of Object.entries(this.ENTITY_TYPES)) {
|
||||||
|
if ((mask & typeMask) === typeMask) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量分类实体
|
||||||
|
classifyEntities(entities: Entity[]): Map<string, Entity[]> {
|
||||||
|
const classified = new Map<string, Entity[]>();
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
const type = this.classifyEntity(entity);
|
||||||
|
if (!classified.has(type)) {
|
||||||
|
classified.set(type, []);
|
||||||
|
}
|
||||||
|
classified.get(type)!.push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return classified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化技巧
|
||||||
|
|
||||||
|
### 1. 预计算常用掩码
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MaskCache {
|
||||||
|
private optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
|
||||||
|
// 预计算游戏中常用的组件组合
|
||||||
|
public readonly COMMON_MASKS = {
|
||||||
|
RENDERABLE: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'RenderComponent'
|
||||||
|
]),
|
||||||
|
MOVABLE: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'VelocityComponent'
|
||||||
|
]),
|
||||||
|
LIVING: this.optimizer.createCombinedMask([
|
||||||
|
'HealthComponent'
|
||||||
|
]),
|
||||||
|
INTERACTIVE: this.optimizer.createCombinedMask([
|
||||||
|
'PositionComponent', 'ColliderComponent'
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 预计算常用组合以提升性能
|
||||||
|
this.optimizer.precomputeCommonMasks([
|
||||||
|
['PositionComponent', 'RenderComponent'],
|
||||||
|
['PositionComponent', 'VelocityComponent'],
|
||||||
|
['PositionComponent', 'HealthComponent', 'WeaponComponent']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 位掩码调试工具
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 位掩码调试工具
|
||||||
|
class MaskDebugger {
|
||||||
|
private optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
|
||||||
|
// 可视化位掩码
|
||||||
|
visualizeMask(mask: bigint, maxBits: number = 16): string {
|
||||||
|
const binary = mask.toString(2).padStart(maxBits, '0');
|
||||||
|
const components = this.optimizer.maskToComponentNames(mask);
|
||||||
|
|
||||||
|
let visualization = `位掩码: ${mask} (二进制: ${binary})\n`;
|
||||||
|
visualization += `组件: ${components.join(', ')}\n`;
|
||||||
|
visualization += `可视化: `;
|
||||||
|
|
||||||
|
for (let i = maxBits - 1; i >= 0; i--) {
|
||||||
|
const bit = (mask & (1n << BigInt(i))) !== 0n ? '1' : '0';
|
||||||
|
visualization += bit;
|
||||||
|
if (i % 4 === 0 && i > 0) visualization += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return visualization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 组件注册
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在游戏初始化时注册所有组件类型
|
||||||
|
function initializeComponentTypes() {
|
||||||
|
const optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
|
||||||
|
// 按使用频率注册(常用的组件分配较小的位位置)
|
||||||
|
optimizer.registerComponentType('PositionComponent'); // 位置0
|
||||||
|
optimizer.registerComponentType('VelocityComponent'); // 位置1
|
||||||
|
optimizer.registerComponentType('HealthComponent'); // 位置2
|
||||||
|
optimizer.registerComponentType('RenderComponent'); // 位置3
|
||||||
|
// ... 其他组件
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 掩码缓存管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 定期清理掩码缓存以避免内存泄漏
|
||||||
|
setInterval(() => {
|
||||||
|
const optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
const stats = optimizer.getCacheStats();
|
||||||
|
|
||||||
|
// 如果缓存过大,清理一部分
|
||||||
|
if (stats.size > 1000) {
|
||||||
|
optimizer.clearCache();
|
||||||
|
console.log('位掩码缓存已清理');
|
||||||
|
}
|
||||||
|
}, 60000); // 每分钟检查一次
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
位掩码是ECS框架中的核心优化技术,它提供了:
|
||||||
|
|
||||||
|
1. **极快的查询速度** - 位运算比遍历快数百倍
|
||||||
|
2. **内存效率** - 用一个数字表示复杂的组件组合
|
||||||
|
3. **批量操作优化** - 可以快速处理大量实体
|
||||||
|
4. **灵活的查询构建** - 支持复杂的组件组合查询
|
||||||
|
|
||||||
|
通过理解和正确使用位掩码,可以显著提升游戏的性能表现。记住要在游戏初始化时注册组件类型,预计算常用掩码,并合理管理缓存。
|
||||||
695
docs/component-design-guide.md
Normal file
695
docs/component-design-guide.md
Normal file
@@ -0,0 +1,695 @@
|
|||||||
|
# 组件设计最佳实践指南
|
||||||
|
|
||||||
|
组件是ECS架构的核心,设计良好的组件是构建高质量游戏的基础。本指南将教你如何设计出清晰、高效、可维护的组件。
|
||||||
|
|
||||||
|
## 组件设计原则
|
||||||
|
|
||||||
|
### 1. 数据为主,逻辑为辅
|
||||||
|
|
||||||
|
**核心理念:** 组件主要存储数据,复杂逻辑放在系统中处理。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的设计:主要是数据
|
||||||
|
class HealthComponent extends Component {
|
||||||
|
public maxHealth: number;
|
||||||
|
public currentHealth: number;
|
||||||
|
public regenRate: number = 0;
|
||||||
|
public lastDamageTime: number = 0;
|
||||||
|
|
||||||
|
constructor(maxHealth: number = 100) {
|
||||||
|
super();
|
||||||
|
this.maxHealth = maxHealth;
|
||||||
|
this.currentHealth = maxHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的辅助方法是可以的
|
||||||
|
isDead(): boolean {
|
||||||
|
return this.currentHealth <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHealthPercentage(): number {
|
||||||
|
return this.currentHealth / this.maxHealth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 不好的设计:包含太多逻辑
|
||||||
|
class BadHealthComponent extends Component {
|
||||||
|
public maxHealth: number;
|
||||||
|
public currentHealth: number;
|
||||||
|
|
||||||
|
takeDamage(damage: number) {
|
||||||
|
this.currentHealth -= damage;
|
||||||
|
|
||||||
|
// 这些逻辑应该在系统中处理
|
||||||
|
if (this.currentHealth <= 0) {
|
||||||
|
this.entity.destroy(); // 销毁逻辑
|
||||||
|
this.playDeathSound(); // 音效逻辑
|
||||||
|
this.createDeathEffect(); // 特效逻辑
|
||||||
|
this.updatePlayerScore(100); // 分数逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 单一职责原则
|
||||||
|
|
||||||
|
每个组件只负责一个方面的数据。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的设计:单一职责
|
||||||
|
class PositionComponent extends Component {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VelocityComponent extends Component {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public maxSpeed: number = 100;
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RotationComponent extends Component {
|
||||||
|
public angle: number = 0;
|
||||||
|
public angularVelocity: number = 0;
|
||||||
|
|
||||||
|
constructor(angle: number = 0) {
|
||||||
|
super();
|
||||||
|
this.angle = angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 不好的设计:职责混乱
|
||||||
|
class TransformComponent extends Component {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public velocityX: number = 0;
|
||||||
|
public velocityY: number = 0;
|
||||||
|
public angle: number = 0;
|
||||||
|
public scale: number = 1;
|
||||||
|
public health: number = 100; // 和变换无关
|
||||||
|
public ammo: number = 30; // 和变换无关
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 组合优于继承
|
||||||
|
|
||||||
|
使用多个小组件组合,而不是大而全的组件继承。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的设计:组合方式
|
||||||
|
class Player {
|
||||||
|
constructor(scene: Scene) {
|
||||||
|
const player = scene.createEntity("Player");
|
||||||
|
|
||||||
|
// 通过组合不同组件实现功能
|
||||||
|
player.addComponent(new PositionComponent(100, 100));
|
||||||
|
player.addComponent(new VelocityComponent());
|
||||||
|
player.addComponent(new HealthComponent(100));
|
||||||
|
player.addComponent(new PlayerInputComponent());
|
||||||
|
player.addComponent(new WeaponComponent());
|
||||||
|
player.addComponent(new InventoryComponent());
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建不同类型的实体很容易
|
||||||
|
class Enemy {
|
||||||
|
constructor(scene: Scene) {
|
||||||
|
const enemy = scene.createEntity("Enemy");
|
||||||
|
|
||||||
|
// 复用相同的组件,但组合不同
|
||||||
|
enemy.addComponent(new PositionComponent(200, 200));
|
||||||
|
enemy.addComponent(new VelocityComponent());
|
||||||
|
enemy.addComponent(new HealthComponent(50));
|
||||||
|
enemy.addComponent(new AIComponent()); // 不同:AI而不是玩家输入
|
||||||
|
enemy.addComponent(new WeaponComponent()); // 相同:都有武器
|
||||||
|
// 没有库存组件
|
||||||
|
|
||||||
|
return enemy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 不好的设计:继承方式
|
||||||
|
class GameObject {
|
||||||
|
public x: number;
|
||||||
|
public y: number;
|
||||||
|
public health: number;
|
||||||
|
// ... 很多属性
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerGameObject extends GameObject {
|
||||||
|
public input: InputData;
|
||||||
|
public inventory: Item[];
|
||||||
|
// 强制继承了不需要的属性
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnemyGameObject extends GameObject {
|
||||||
|
public ai: AIData;
|
||||||
|
// 继承了不需要的库存等属性
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见组件类型和设计
|
||||||
|
|
||||||
|
### 1. 数据组件(Data Components)
|
||||||
|
|
||||||
|
纯数据存储,没有或很少有方法。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 位置信息
|
||||||
|
class PositionComponent extends Component {
|
||||||
|
public x: number;
|
||||||
|
public y: number;
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的辅助方法
|
||||||
|
distanceTo(other: PositionComponent): number {
|
||||||
|
const dx = this.x - other.x;
|
||||||
|
const dy = this.y - other.y;
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(x: number, y: number) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计信息
|
||||||
|
class StatsComponent extends Component {
|
||||||
|
public strength: number = 10;
|
||||||
|
public agility: number = 10;
|
||||||
|
public intelligence: number = 10;
|
||||||
|
public vitality: number = 10;
|
||||||
|
|
||||||
|
// 计算派生属性
|
||||||
|
getMaxHealth(): number {
|
||||||
|
return this.vitality * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDamage(): number {
|
||||||
|
return this.strength * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMoveSpeed(): number {
|
||||||
|
return this.agility * 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染信息
|
||||||
|
class SpriteComponent extends Component {
|
||||||
|
public textureName: string;
|
||||||
|
public width: number;
|
||||||
|
public height: number;
|
||||||
|
public tint: number = 0xFFFFFF;
|
||||||
|
public alpha: number = 1.0;
|
||||||
|
public visible: boolean = true;
|
||||||
|
|
||||||
|
constructor(textureName: string, width: number = 0, height: number = 0) {
|
||||||
|
super();
|
||||||
|
this.textureName = textureName;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 标记组件(Tag Components)
|
||||||
|
|
||||||
|
用于标识实体状态或类型的空组件。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 标记组件通常不包含数据
|
||||||
|
class PlayerComponent extends Component {
|
||||||
|
// 空组件,仅用于标记这是玩家实体
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnemyComponent extends Component {
|
||||||
|
// 空组件,仅用于标记这是敌人实体
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeadComponent extends Component {
|
||||||
|
// 标记实体已死亡
|
||||||
|
public deathTime: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.deathTime = Time.totalTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvincibleComponent extends Component {
|
||||||
|
// 标记实体无敌状态
|
||||||
|
public duration: number;
|
||||||
|
|
||||||
|
constructor(duration: number = 2.0) {
|
||||||
|
super();
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用标记组件进行查询
|
||||||
|
class GameSystem {
|
||||||
|
updatePlayers() {
|
||||||
|
// 只处理玩家实体
|
||||||
|
const players = this.scene.findEntitiesWithComponent(PlayerComponent);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEnemies() {
|
||||||
|
// 只处理敌人实体
|
||||||
|
const enemies = this.scene.findEntitiesWithComponent(EnemyComponent);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 行为组件(Behavior Components)
|
||||||
|
|
||||||
|
包含简单行为逻辑的组件。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class WeaponComponent extends Component {
|
||||||
|
public damage: number;
|
||||||
|
public fireRate: number;
|
||||||
|
public ammo: number;
|
||||||
|
public maxAmmo: number;
|
||||||
|
public lastFireTime: number = 0;
|
||||||
|
|
||||||
|
constructor(damage: number = 10, fireRate: number = 0.5) {
|
||||||
|
super();
|
||||||
|
this.damage = damage;
|
||||||
|
this.fireRate = fireRate;
|
||||||
|
this.maxAmmo = 30;
|
||||||
|
this.ammo = this.maxAmmo;
|
||||||
|
}
|
||||||
|
|
||||||
|
canFire(): boolean {
|
||||||
|
return this.ammo > 0 &&
|
||||||
|
Time.totalTime - this.lastFireTime >= this.fireRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
fire(): boolean {
|
||||||
|
if (this.canFire()) {
|
||||||
|
this.ammo--;
|
||||||
|
this.lastFireTime = Time.totalTime;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
this.ammo = this.maxAmmo;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAmmoPercentage(): number {
|
||||||
|
return this.ammo / this.maxAmmo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InventoryComponent extends Component {
|
||||||
|
private items: Map<string, number> = new Map();
|
||||||
|
public maxCapacity: number = 20;
|
||||||
|
|
||||||
|
addItem(itemType: string, quantity: number = 1): boolean {
|
||||||
|
if (this.getTotalItems() + quantity > this.maxCapacity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = this.items.get(itemType) || 0;
|
||||||
|
this.items.set(itemType, current + quantity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(itemType: string, quantity: number = 1): boolean {
|
||||||
|
const current = this.items.get(itemType) || 0;
|
||||||
|
if (current < quantity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAmount = current - quantity;
|
||||||
|
if (newAmount === 0) {
|
||||||
|
this.items.delete(itemType);
|
||||||
|
} else {
|
||||||
|
this.items.set(itemType, newAmount);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasItem(itemType: string, quantity: number = 1): boolean {
|
||||||
|
const current = this.items.get(itemType) || 0;
|
||||||
|
return current >= quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalItems(): number {
|
||||||
|
let total = 0;
|
||||||
|
this.items.forEach(quantity => total += quantity);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItems(): Map<string, number> {
|
||||||
|
return new Map(this.items); // 返回副本
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件通信和依赖
|
||||||
|
|
||||||
|
### 1. 组件间通信
|
||||||
|
|
||||||
|
组件间不应直接通信,通过系统或事件系统进行通信。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的设计:通过事件通信
|
||||||
|
class HealthComponent extends Component {
|
||||||
|
public currentHealth: number;
|
||||||
|
public maxHealth: number;
|
||||||
|
|
||||||
|
takeDamage(damage: number) {
|
||||||
|
this.currentHealth -= damage;
|
||||||
|
|
||||||
|
// 发送事件,让其他系统响应
|
||||||
|
Core.emitter.emit('health:damaged', {
|
||||||
|
entity: this.entity,
|
||||||
|
damage: damage,
|
||||||
|
remainingHealth: this.currentHealth
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.currentHealth <= 0) {
|
||||||
|
Core.emitter.emit('health:died', {
|
||||||
|
entity: this.entity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他组件响应事件
|
||||||
|
class AnimationComponent extends Component {
|
||||||
|
onAddedToEntity() {
|
||||||
|
super.onAddedToEntity();
|
||||||
|
|
||||||
|
// 监听受伤事件
|
||||||
|
Core.emitter.addObserver('health:damaged', this.onDamaged, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemovedFromEntity() {
|
||||||
|
Core.emitter.removeObserver('health:damaged', this.onDamaged, this);
|
||||||
|
super.onRemovedFromEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDamaged(data: any) {
|
||||||
|
if (data.entity === this.entity) {
|
||||||
|
this.playHurtAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 不好的设计:直接依赖其他组件
|
||||||
|
class BadHealthComponent extends Component {
|
||||||
|
takeDamage(damage: number) {
|
||||||
|
this.currentHealth -= damage;
|
||||||
|
|
||||||
|
// 直接操作其他组件
|
||||||
|
const animation = this.entity.getComponent(AnimationComponent);
|
||||||
|
if (animation) {
|
||||||
|
animation.playHurtAnimation(); // 紧耦合
|
||||||
|
}
|
||||||
|
|
||||||
|
const sound = this.entity.getComponent(SoundComponent);
|
||||||
|
if (sound) {
|
||||||
|
sound.playHurtSound(); // 紧耦合
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 可选依赖
|
||||||
|
|
||||||
|
有时组件需要其他组件配合工作,但应该优雅处理缺失的情况。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MovementComponent extends Component {
|
||||||
|
public speed: number = 100;
|
||||||
|
|
||||||
|
update() {
|
||||||
|
// 可选依赖:输入组件
|
||||||
|
const input = this.entity.getComponent(InputComponent);
|
||||||
|
const velocity = this.entity.getComponent(VelocityComponent);
|
||||||
|
|
||||||
|
if (input && velocity) {
|
||||||
|
// 根据输入设置速度
|
||||||
|
velocity.x = input.horizontal * this.speed;
|
||||||
|
velocity.y = input.vertical * this.speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选依赖:AI组件
|
||||||
|
const ai = this.entity.getComponent(AIComponent);
|
||||||
|
if (ai && velocity && !input) {
|
||||||
|
// AI控制移动(如果没有输入)
|
||||||
|
velocity.x = ai.moveDirection.x * this.speed;
|
||||||
|
velocity.y = ai.moveDirection.y * this.speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件性能优化
|
||||||
|
|
||||||
|
### 1. 对象池优化
|
||||||
|
|
||||||
|
对于频繁创建/销毁的组件,使用对象池。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class PooledBulletComponent extends Component {
|
||||||
|
public damage: number = 10;
|
||||||
|
public speed: number = 200;
|
||||||
|
public direction: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
|
public lifetime: number = 5.0;
|
||||||
|
private currentLifetime: number = 0;
|
||||||
|
|
||||||
|
// 重置组件状态,用于对象池
|
||||||
|
reset() {
|
||||||
|
this.damage = 10;
|
||||||
|
this.speed = 200;
|
||||||
|
this.direction.set(0, 0);
|
||||||
|
this.lifetime = 5.0;
|
||||||
|
this.currentLifetime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置子弹
|
||||||
|
configure(damage: number, speed: number, direction: { x: number; y: number }) {
|
||||||
|
this.damage = damage;
|
||||||
|
this.speed = speed;
|
||||||
|
this.direction = direction.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.currentLifetime += Time.deltaTime;
|
||||||
|
|
||||||
|
if (this.currentLifetime >= this.lifetime) {
|
||||||
|
// 生命周期结束,回收到对象池
|
||||||
|
BulletPool.release(this.entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对象池管理
|
||||||
|
class BulletPool {
|
||||||
|
private static pool: Entity[] = [];
|
||||||
|
|
||||||
|
static get(): Entity {
|
||||||
|
if (this.pool.length > 0) {
|
||||||
|
const bullet = this.pool.pop()!;
|
||||||
|
bullet.enabled = true;
|
||||||
|
return bullet;
|
||||||
|
} else {
|
||||||
|
return this.createBullet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static release(bullet: Entity) {
|
||||||
|
bullet.enabled = false;
|
||||||
|
bullet.getComponent(PooledBulletComponent)?.reset();
|
||||||
|
this.pool.push(bullet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createBullet(): Entity {
|
||||||
|
const bullet = Core.scene.createEntity("Bullet");
|
||||||
|
bullet.addComponent(new PooledBulletComponent());
|
||||||
|
bullet.addComponent(new PositionComponent());
|
||||||
|
bullet.addComponent(new VelocityComponent());
|
||||||
|
return bullet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据紧凑性
|
||||||
|
|
||||||
|
保持组件数据紧凑,避免不必要的对象分配。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的设计:紧凑的数据结构
|
||||||
|
class ParticleComponent extends Component {
|
||||||
|
// 使用基本类型,避免对象分配
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public velocityX: number = 0;
|
||||||
|
public velocityY: number = 0;
|
||||||
|
public life: number = 1.0;
|
||||||
|
public maxLife: number = 1.0;
|
||||||
|
public size: number = 1.0;
|
||||||
|
public color: number = 0xFFFFFF;
|
||||||
|
|
||||||
|
// 计算属性,避免存储冗余数据
|
||||||
|
get alpha(): number {
|
||||||
|
return this.life / this.maxLife;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 不好的设计:过多对象分配
|
||||||
|
class BadParticleComponent extends Component {
|
||||||
|
public position: { x: number; y: number } = { x: 0, y: 0 }; // 对象分配
|
||||||
|
public velocity: { x: number; y: number } = { x: 0, y: 0 }; // 对象分配
|
||||||
|
public color: Color = new Color(); // 对象分配
|
||||||
|
public transform: Transform = new Transform(); // 对象分配
|
||||||
|
|
||||||
|
// 冗余数据
|
||||||
|
public alpha: number = 1.0;
|
||||||
|
public life: number = 1.0;
|
||||||
|
public maxLife: number = 1.0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件调试和测试
|
||||||
|
|
||||||
|
### 1. 调试友好的组件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class DebugFriendlyComponent extends Component {
|
||||||
|
public someValue: number = 0;
|
||||||
|
private debugName: string;
|
||||||
|
|
||||||
|
constructor(debugName: string = "Unknown") {
|
||||||
|
super();
|
||||||
|
this.debugName = debugName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供有用的调试信息
|
||||||
|
toString(): string {
|
||||||
|
return `${this.constructor.name}(${this.debugName}): value=${this.someValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证组件状态
|
||||||
|
validate(): boolean {
|
||||||
|
if (this.someValue < 0) {
|
||||||
|
console.warn(`${this} has invalid value: ${this.someValue}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取调试信息
|
||||||
|
getDebugInfo(): any {
|
||||||
|
return {
|
||||||
|
name: this.debugName,
|
||||||
|
value: this.someValue,
|
||||||
|
entityId: this.entity?.id,
|
||||||
|
isValid: this.validate()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 单元测试
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 组件测试示例
|
||||||
|
describe('HealthComponent', () => {
|
||||||
|
let healthComponent: HealthComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
healthComponent = new HealthComponent(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('初始状态正确', () => {
|
||||||
|
expect(healthComponent.currentHealth).toBe(100);
|
||||||
|
expect(healthComponent.maxHealth).toBe(100);
|
||||||
|
expect(healthComponent.isDead()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('受伤功能正确', () => {
|
||||||
|
healthComponent.takeDamage(30);
|
||||||
|
expect(healthComponent.currentHealth).toBe(70);
|
||||||
|
expect(healthComponent.getHealthPercentage()).toBe(0.7);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('死亡检测正确', () => {
|
||||||
|
healthComponent.takeDamage(100);
|
||||||
|
expect(healthComponent.isDead()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题和最佳实践
|
||||||
|
|
||||||
|
### Q: 组件应该有多大?
|
||||||
|
|
||||||
|
A: 组件应该尽可能小和专注。如果一个组件有超过10个字段,考虑拆分。
|
||||||
|
|
||||||
|
### Q: 组件可以包含方法吗?
|
||||||
|
|
||||||
|
A: 可以,但应该是简单的辅助方法。复杂逻辑应该在系统中处理。
|
||||||
|
|
||||||
|
### Q: 如何处理组件之间的依赖?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 优先使用组合而不是依赖
|
||||||
|
2. 通过事件系统通信
|
||||||
|
3. 在系统中处理组件间的协调
|
||||||
|
|
||||||
|
### Q: 什么时候使用继承?
|
||||||
|
|
||||||
|
A: 很少使用。只在有明确的"是一个"关系时使用,如:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
abstract class ColliderComponent extends Component {
|
||||||
|
abstract checkCollision(other: ColliderComponent): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircleColliderComponent extends ColliderComponent {
|
||||||
|
public radius: number;
|
||||||
|
|
||||||
|
checkCollision(other: ColliderComponent): boolean {
|
||||||
|
// 圆形碰撞检测
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoxColliderComponent extends ColliderComponent {
|
||||||
|
public width: number;
|
||||||
|
public height: number;
|
||||||
|
|
||||||
|
checkCollision(other: ColliderComponent): boolean {
|
||||||
|
// 方形碰撞检测
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
遵循这些原则,你就能设计出高质量、易维护的组件系统!
|
||||||
665
docs/concepts-explained.md
Normal file
665
docs/concepts-explained.md
Normal file
@@ -0,0 +1,665 @@
|
|||||||
|
# 技术概念详解
|
||||||
|
|
||||||
|
本文档用通俗易懂的语言解释ECS框架中的关键技术概念,帮助开发者理解这些技术的作用和应用场景。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- [ECS 架构基础](#ecs-架构基础)
|
||||||
|
- [性能优化技术](#性能优化技术)
|
||||||
|
- [事件系统](#事件系统)
|
||||||
|
- [实体管理](#实体管理)
|
||||||
|
|
||||||
|
## ECS 架构基础
|
||||||
|
|
||||||
|
### 什么是 ECS?
|
||||||
|
|
||||||
|
ECS (Entity-Component-System) 是一种编程架构模式,将游戏对象分解为三个独立的部分:
|
||||||
|
|
||||||
|
**传统面向对象方式:**
|
||||||
|
```typescript
|
||||||
|
// 传统继承方式 - 问题很多
|
||||||
|
class GameObject {
|
||||||
|
x: number; y: number;
|
||||||
|
render() { ... }
|
||||||
|
update() { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Player extends GameObject {
|
||||||
|
health: number;
|
||||||
|
shoot() { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Enemy extends Player { // 敌人需要射击但不需要玩家控制?
|
||||||
|
ai() { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**ECS 方式:**
|
||||||
|
```typescript
|
||||||
|
// 数据和逻辑分离,灵活组合
|
||||||
|
const player = createEntity()
|
||||||
|
.add(PositionComponent) // 位置数据
|
||||||
|
.add(HealthComponent) // 生命值数据
|
||||||
|
.add(PlayerInputComponent) // 玩家输入标记
|
||||||
|
|
||||||
|
const enemy = createEntity()
|
||||||
|
.add(PositionComponent) // 复用位置数据
|
||||||
|
.add(HealthComponent) // 复用生命值数据
|
||||||
|
.add(AIComponent) // AI标记
|
||||||
|
|
||||||
|
// 系统处理具有特定组件的实体
|
||||||
|
MovementSystem.process([PositionComponent, VelocityComponent]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### ECS 的优势
|
||||||
|
|
||||||
|
1. **灵活组合** - 像搭积木一样组装功能
|
||||||
|
2. **代码复用** - 组件可以在不同实体间复用
|
||||||
|
3. **性能优化** - 数据连续存储,缓存友好
|
||||||
|
4. **并行处理** - 系统间相互独立,可以并行执行
|
||||||
|
5. **易于测试** - 组件和系统可以独立测试
|
||||||
|
|
||||||
|
### 实际应用场景
|
||||||
|
|
||||||
|
**游戏开发中的例子:**
|
||||||
|
- **RPG游戏**:玩家、NPC、怪物都有位置和生命值,但只有玩家有输入组件
|
||||||
|
- **射击游戏**:子弹、玩家、敌人都有位置和碰撞体,但行为完全不同
|
||||||
|
- **策略游戏**:建筑、单位、资源都是实体,通过不同组件组合实现功能
|
||||||
|
|
||||||
|
## 性能优化技术
|
||||||
|
|
||||||
|
### 组件索引系统
|
||||||
|
|
||||||
|
**问题:** 没有索引时,查找组件需要遍历所有实体
|
||||||
|
```typescript
|
||||||
|
// 慢的方式:线性搜索 O(n)
|
||||||
|
function findEntitiesWithHealth() {
|
||||||
|
const result = [];
|
||||||
|
for (const entity of allEntities) { // 遍历10万个实体
|
||||||
|
if (entity.hasComponent(HealthComponent)) {
|
||||||
|
result.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:** 建立索引,直接访问
|
||||||
|
```typescript
|
||||||
|
// 快的方式:索引查找 O(1)
|
||||||
|
const healthIndex = componentIndex.get(HealthComponent);
|
||||||
|
const entitiesWithHealth = healthIndex.getEntities(); // 直接获取
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用场景:**
|
||||||
|
- 频繁查询特定组件的实体
|
||||||
|
- 大规模实体场景(数千到数万个实体)
|
||||||
|
- 实时游戏中的系统更新
|
||||||
|
|
||||||
|
### 索引类型选择指南
|
||||||
|
|
||||||
|
框架提供两种索引类型,选择合适的类型对性能至关重要:
|
||||||
|
|
||||||
|
#### 🔸 哈希索引 (Hash Index)
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 实体数量较多(> 1000个)
|
||||||
|
- 组件数据变化不频繁
|
||||||
|
- 需要快速查找特定实体
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- 查询速度极快 O(1)
|
||||||
|
- 内存使用相对较少
|
||||||
|
- 适合大量实体
|
||||||
|
|
||||||
|
**缺点:**
|
||||||
|
- 添加/删除组件时有额外开销
|
||||||
|
- 不适合频繁变化的组件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 适合哈希索引的组件
|
||||||
|
componentIndex.setIndexType(PositionComponent, 'hash'); // 位置变化不频繁
|
||||||
|
componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件稳定
|
||||||
|
componentIndex.setIndexType(PlayerComponent, 'hash'); // 玩家标记组件
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 🔹 位图索引 (Bitmap Index)
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 组件频繁添加/删除
|
||||||
|
- 实体数量适中(< 10000个)
|
||||||
|
- 需要批量操作
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- 添加/删除组件极快
|
||||||
|
- 批量查询效率高
|
||||||
|
- 内存访问模式好
|
||||||
|
|
||||||
|
**缺点:**
|
||||||
|
- 随实体数量增长,内存占用增加
|
||||||
|
- 稀疏数据时效率降低
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 适合位图索引的组件
|
||||||
|
componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff经常添加删除
|
||||||
|
componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件
|
||||||
|
componentIndex.setIndexType(StateComponent, 'bitmap'); // 状态组件变化频繁
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 📊 选择决策表
|
||||||
|
|
||||||
|
| 考虑因素 | 哈希索引 (Hash) | 位图索引 (Bitmap) |
|
||||||
|
|---------|----------------|-------------------|
|
||||||
|
| **实体数量** | > 1,000 | < 10,000 |
|
||||||
|
| **组件变化频率** | 低频变化 | 高频变化 |
|
||||||
|
| **查询频率** | 高频查询 | 中等查询 |
|
||||||
|
| **内存使用** | 较少 | 随实体数增加 |
|
||||||
|
| **批量操作** | 一般 | 优秀 |
|
||||||
|
|
||||||
|
#### 🤔 快速决策流程
|
||||||
|
|
||||||
|
**第一步:判断组件变化频率**
|
||||||
|
- 组件经常添加/删除? → 选择 **位图索引**
|
||||||
|
- 组件相对稳定? → 继续第二步
|
||||||
|
|
||||||
|
**第二步:判断实体数量**
|
||||||
|
- 实体数量 > 1000? → 选择 **哈希索引**
|
||||||
|
- 实体数量 < 1000? → 选择 **位图索引**
|
||||||
|
|
||||||
|
**第三步:特殊情况**
|
||||||
|
- 需要频繁批量操作? → 选择 **位图索引**
|
||||||
|
- 内存使用很重要? → 选择 **哈希索引**
|
||||||
|
|
||||||
|
#### 🎮 实际游戏中的选择示例
|
||||||
|
|
||||||
|
**射击游戏:**
|
||||||
|
```typescript
|
||||||
|
// 稳定组件用哈希索引
|
||||||
|
componentIndex.setIndexType(PositionComponent, 'hash'); // 实体位置稳定存在
|
||||||
|
componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件持续存在
|
||||||
|
componentIndex.setIndexType(WeaponComponent, 'hash'); // 武器组件不常变化
|
||||||
|
|
||||||
|
// 变化组件用位图索引
|
||||||
|
componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff频繁添加删除
|
||||||
|
componentIndex.setIndexType(ReloadingComponent, 'bitmap'); // 装弹状态临时组件
|
||||||
|
```
|
||||||
|
|
||||||
|
**策略游戏:**
|
||||||
|
```typescript
|
||||||
|
// 大量单位,核心组件用哈希
|
||||||
|
componentIndex.setIndexType(UnitComponent, 'hash'); // 单位类型稳定
|
||||||
|
componentIndex.setIndexType(OwnerComponent, 'hash'); // 所属玩家稳定
|
||||||
|
|
||||||
|
// 状态组件用位图
|
||||||
|
componentIndex.setIndexType(SelectedComponent, 'bitmap'); // 选中状态常变化
|
||||||
|
componentIndex.setIndexType(MovingComponent, 'bitmap'); // 移动状态变化
|
||||||
|
componentIndex.setIndexType(AttackingComponent, 'bitmap'); // 攻击状态临时
|
||||||
|
```
|
||||||
|
|
||||||
|
**RPG游戏:**
|
||||||
|
```typescript
|
||||||
|
// 角色核心属性用哈希
|
||||||
|
componentIndex.setIndexType(StatsComponent, 'hash'); // 属性组件稳定
|
||||||
|
componentIndex.setIndexType(InventoryComponent, 'hash'); // 背包组件稳定
|
||||||
|
componentIndex.setIndexType(LevelComponent, 'hash'); // 等级组件稳定
|
||||||
|
|
||||||
|
// 临时状态用位图
|
||||||
|
componentIndex.setIndexType(StatusEffectComponent, 'bitmap'); // 状态效果变化
|
||||||
|
componentIndex.setIndexType(QuestComponent, 'bitmap'); // 任务状态变化
|
||||||
|
componentIndex.setIndexType(CombatComponent, 'bitmap'); // 战斗状态临时
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 常见选择错误
|
||||||
|
|
||||||
|
**错误示例1:大量实体使用位图索引**
|
||||||
|
```typescript
|
||||||
|
// ❌ 错误:10万个单位用位图索引,内存爆炸
|
||||||
|
const entityCount = 100000;
|
||||||
|
componentIndex.setIndexType(UnitComponent, 'bitmap'); // 内存占用过大!
|
||||||
|
|
||||||
|
// ✅ 正确:大量实体用哈希索引
|
||||||
|
componentIndex.setIndexType(UnitComponent, 'hash');
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误示例2:频繁变化组件用哈希索引**
|
||||||
|
```typescript
|
||||||
|
// ❌ 错误:Buff频繁添加删除,哈希索引效率低
|
||||||
|
componentIndex.setIndexType(BuffComponent, 'hash'); // 添加删除慢!
|
||||||
|
|
||||||
|
// ✅ 正确:变化频繁的组件用位图索引
|
||||||
|
componentIndex.setIndexType(BuffComponent, 'bitmap');
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误示例3:不考虑实际使用场景**
|
||||||
|
```typescript
|
||||||
|
// ❌ 错误:所有组件都用同一种索引
|
||||||
|
componentIndex.setIndexType(PositionComponent, 'hash');
|
||||||
|
componentIndex.setIndexType(BuffComponent, 'hash'); // 应该用bitmap
|
||||||
|
componentIndex.setIndexType(TemporaryComponent, 'hash'); // 应该用bitmap
|
||||||
|
|
||||||
|
// ✅ 正确:根据组件特性选择
|
||||||
|
componentIndex.setIndexType(PositionComponent, 'hash'); // 稳定组件
|
||||||
|
componentIndex.setIndexType(BuffComponent, 'bitmap'); // 变化组件
|
||||||
|
componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件
|
||||||
|
```
|
||||||
|
|
||||||
|
### Archetype 系统
|
||||||
|
|
||||||
|
**什么是 Archetype?**
|
||||||
|
Archetype(原型)是具有相同组件组合的实体分组。
|
||||||
|
|
||||||
|
**没有 Archetype 的问题:**
|
||||||
|
```typescript
|
||||||
|
// 每次都要检查每个实体的组件组合
|
||||||
|
for (const entity of allEntities) {
|
||||||
|
if (entity.has(Position) && entity.has(Velocity) && !entity.has(Frozen)) {
|
||||||
|
// 处理移动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Archetype 的解决方案:**
|
||||||
|
```typescript
|
||||||
|
// 实体按组件组合自动分组
|
||||||
|
const movableArchetype = [Position, Velocity, !Frozen];
|
||||||
|
const movableEntities = archetypeSystem.getEntities(movableArchetype);
|
||||||
|
// 直接处理,无需逐个检查
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用场景:**
|
||||||
|
- 大量实体的游戏(RTS、MMO)
|
||||||
|
- 频繁的实体查询操作
|
||||||
|
- 批量处理相同类型的实体
|
||||||
|
|
||||||
|
### 脏标记系统
|
||||||
|
|
||||||
|
**什么是脏标记?**
|
||||||
|
脏标记(Dirty Tracking)追踪哪些数据发生了变化,避免处理未变化的数据。
|
||||||
|
|
||||||
|
**没有脏标记的问题:**
|
||||||
|
```typescript
|
||||||
|
// 每帧都重新计算所有实体,即使它们没有移动
|
||||||
|
function renderSystem() {
|
||||||
|
for (const entity of entities) {
|
||||||
|
updateRenderPosition(entity); // 浪费计算
|
||||||
|
updateRenderRotation(entity); // 浪费计算
|
||||||
|
updateRenderScale(entity); // 浪费计算
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**脏标记的解决方案:**
|
||||||
|
```typescript
|
||||||
|
// 只处理发生变化的实体
|
||||||
|
function renderSystem() {
|
||||||
|
const dirtyEntities = dirtyTracking.getDirtyEntities();
|
||||||
|
for (const entity of dirtyEntities) {
|
||||||
|
if (dirtyTracking.isDirty(entity, PositionComponent)) {
|
||||||
|
updateRenderPosition(entity); // 只在需要时计算
|
||||||
|
}
|
||||||
|
if (dirtyTracking.isDirty(entity, RotationComponent)) {
|
||||||
|
updateRenderRotation(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dirtyTracking.clearDirtyFlags();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用场景:**
|
||||||
|
- 渲染系统优化(只更新变化的物体)
|
||||||
|
- 物理系统优化(只计算移动的物体)
|
||||||
|
- UI更新优化(只刷新变化的界面元素)
|
||||||
|
- 网络同步优化(只发送变化的数据)
|
||||||
|
|
||||||
|
**实际例子:**
|
||||||
|
```typescript
|
||||||
|
// 游戏中的应用
|
||||||
|
class MovementSystem {
|
||||||
|
process() {
|
||||||
|
// 玩家移动时标记为脏
|
||||||
|
if (playerInput.moved) {
|
||||||
|
dirtyTracking.markDirty(player, PositionComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静止的敌人不会被标记为脏,渲染系统会跳过它们
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 事件系统
|
||||||
|
|
||||||
|
### 类型安全事件
|
||||||
|
|
||||||
|
**传统事件的问题:**
|
||||||
|
```typescript
|
||||||
|
// 类型不安全,容易出错
|
||||||
|
eventEmitter.emit('player_died', playerData);
|
||||||
|
eventEmitter.on('player_dead', handler); // 事件名拼写错误!
|
||||||
|
```
|
||||||
|
|
||||||
|
**类型安全事件的解决方案:**
|
||||||
|
```typescript
|
||||||
|
// 编译时检查,避免错误
|
||||||
|
enum GameEvents {
|
||||||
|
PLAYER_DIED = 'player:died',
|
||||||
|
LEVEL_COMPLETED = 'level:completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.emit(GameEvents.PLAYER_DIED, { playerId: 123 });
|
||||||
|
eventBus.on(GameEvents.PLAYER_DIED, (data) => {
|
||||||
|
// data 类型自动推断
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件装饰器
|
||||||
|
|
||||||
|
**什么是装饰器?**
|
||||||
|
装饰器让你用简单的语法自动注册事件监听器。
|
||||||
|
|
||||||
|
**传统方式:**
|
||||||
|
```typescript
|
||||||
|
class GameManager {
|
||||||
|
constructor() {
|
||||||
|
// 手动注册事件
|
||||||
|
eventBus.on('entity:created', this.onEntityCreated.bind(this));
|
||||||
|
eventBus.on('entity:destroyed', this.onEntityDestroyed.bind(this));
|
||||||
|
eventBus.on('component:added', this.onComponentAdded.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntityCreated(data) { ... }
|
||||||
|
onEntityDestroyed(data) { ... }
|
||||||
|
onComponentAdded(data) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**装饰器方式:**
|
||||||
|
```typescript
|
||||||
|
class GameManager {
|
||||||
|
@EventHandler('entity:created')
|
||||||
|
onEntityCreated(data) { ... } // 自动注册
|
||||||
|
|
||||||
|
@EventHandler('entity:destroyed')
|
||||||
|
onEntityDestroyed(data) { ... } // 自动注册
|
||||||
|
|
||||||
|
@EventHandler('component:added')
|
||||||
|
onComponentAdded(data) { ... } // 自动注册
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用场景:**
|
||||||
|
- 游戏状态管理
|
||||||
|
- UI更新响应
|
||||||
|
- 音效播放触发
|
||||||
|
- 成就系统检查
|
||||||
|
|
||||||
|
## 实体管理
|
||||||
|
|
||||||
|
### 实体生命周期
|
||||||
|
|
||||||
|
**创建实体的不同方式:**
|
||||||
|
```typescript
|
||||||
|
// 单个创建 - 适用于重要实体
|
||||||
|
const player = scene.createEntity("Player");
|
||||||
|
|
||||||
|
// 批量创建 - 适用于大量相似实体
|
||||||
|
const bullets = scene.createEntities(100, "Bullet");
|
||||||
|
|
||||||
|
// 延迟创建 - 避免性能峰值
|
||||||
|
// 分批创建大量实体以避免单帧卡顿
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const batch = scene.createEntities(10, "Enemy");
|
||||||
|
// 配置批次实体...
|
||||||
|
}, i * 16); // 每16ms创建一批
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询系统
|
||||||
|
|
||||||
|
**流式API的优势:**
|
||||||
|
```typescript
|
||||||
|
// 传统方式:复杂的条件判断
|
||||||
|
const result = [];
|
||||||
|
for (const entity of entities) {
|
||||||
|
if (entity.has(Position) &&
|
||||||
|
entity.has(Velocity) &&
|
||||||
|
!entity.has(Frozen) &&
|
||||||
|
entity.tag === EntityTag.ENEMY) {
|
||||||
|
result.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流式API:清晰表达意图
|
||||||
|
const result = entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(Position, Velocity)
|
||||||
|
.withNone(Frozen)
|
||||||
|
.withTag(EntityTag.ENEMY)
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量操作
|
||||||
|
|
||||||
|
**为什么需要批量操作?**
|
||||||
|
```typescript
|
||||||
|
// 慢的方式:逐个处理
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
const bullet = createEntity();
|
||||||
|
bullet.addComponent(new PositionComponent());
|
||||||
|
bullet.addComponent(new VelocityComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快的方式:批量处理
|
||||||
|
const bullets = scene.createEntities(1000, "Bullet");
|
||||||
|
bullets.forEach(bullet => {
|
||||||
|
bullet.addComponent(new PositionComponent());
|
||||||
|
bullet.addComponent(new VelocityComponent());
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用场景:**
|
||||||
|
- 生成大量子弹/粒子
|
||||||
|
- 加载关卡时创建大量实体
|
||||||
|
- 清理场景时删除大量实体
|
||||||
|
|
||||||
|
## 性能建议
|
||||||
|
|
||||||
|
### 什么时候使用这些优化?
|
||||||
|
|
||||||
|
| 实体数量 | 推荐配置 | 说明 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| < 1,000 | 默认配置 | 简单场景,不需要特殊优化 |
|
||||||
|
| 1,000 - 10,000 | 启用组件索引 | 中等规模,索引提升查询速度 |
|
||||||
|
| 10,000 - 50,000 | 启用Archetype | 大规模场景,分组优化 |
|
||||||
|
| > 50,000 | 全部优化 | 超大规模,需要所有优化技术 |
|
||||||
|
|
||||||
|
### 常见使用误区
|
||||||
|
|
||||||
|
**错误:过度优化**
|
||||||
|
```typescript
|
||||||
|
// 不要在小项目中使用所有优化
|
||||||
|
const entityManager = new EntityManager();
|
||||||
|
entityManager.enableAllOptimizations(); // 小项目不需要
|
||||||
|
```
|
||||||
|
|
||||||
|
**正确:按需优化**
|
||||||
|
```typescript
|
||||||
|
// 根据实际需求选择优化
|
||||||
|
if (entityCount > 10000) {
|
||||||
|
entityManager.enableArchetypeSystem();
|
||||||
|
}
|
||||||
|
if (hasFrequentQueries) {
|
||||||
|
entityManager.enableComponentIndex();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
这些技术概念可能看起来复杂,但它们解决的都是实际开发中的具体问题:
|
||||||
|
|
||||||
|
1. **ECS架构** - 让代码更灵活、可维护
|
||||||
|
2. **组件索引** - 让查询更快速
|
||||||
|
3. **Archetype系统** - 让批量操作更高效
|
||||||
|
4. **脏标记系统** - 让更新更智能
|
||||||
|
5. **事件系统** - 让组件间通信更安全
|
||||||
|
6. **实体管理** - 让大规模场景成为可能
|
||||||
|
|
||||||
|
从简单的场景开始,随着项目复杂度增加,逐步引入这些优化技术。
|
||||||
|
|
||||||
|
## 框架类型系统
|
||||||
|
|
||||||
|
### TypeScript接口设计
|
||||||
|
|
||||||
|
ECS框架采用了精简的TypeScript接口设计,提供类型安全保障的同时保持实现的灵活性。
|
||||||
|
|
||||||
|
#### 核心接口
|
||||||
|
|
||||||
|
**IComponent接口**
|
||||||
|
```typescript
|
||||||
|
interface IComponent {
|
||||||
|
readonly id: number;
|
||||||
|
enabled: boolean;
|
||||||
|
updateOrder: number;
|
||||||
|
|
||||||
|
onAddedToEntity(): void;
|
||||||
|
onRemovedFromEntity(): void;
|
||||||
|
onEnabled(): void;
|
||||||
|
onDisabled(): void;
|
||||||
|
update(): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 定义所有组件的基本契约
|
||||||
|
- Component基类实现此接口
|
||||||
|
- 确保组件生命周期方法的一致性
|
||||||
|
|
||||||
|
**ISystemBase接口**
|
||||||
|
```typescript
|
||||||
|
interface ISystemBase {
|
||||||
|
readonly systemName: string;
|
||||||
|
readonly entities: readonly any[];
|
||||||
|
updateOrder: number;
|
||||||
|
enabled: boolean;
|
||||||
|
|
||||||
|
initialize(): void;
|
||||||
|
update(): void;
|
||||||
|
lateUpdate?(): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 为EntitySystem类提供类型约束
|
||||||
|
- 定义系统的核心执行方法
|
||||||
|
- 支持可选的延迟更新
|
||||||
|
|
||||||
|
**IEventBus接口**
|
||||||
|
```typescript
|
||||||
|
interface IEventBus {
|
||||||
|
emit<T>(eventType: string, data: T): void;
|
||||||
|
emitAsync<T>(eventType: string, data: T): Promise<void>;
|
||||||
|
on<T>(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string;
|
||||||
|
// ... 其他事件方法
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 提供类型安全的事件系统契约
|
||||||
|
- 支持同步和异步事件处理
|
||||||
|
- EventBus类完整实现此接口
|
||||||
|
|
||||||
|
#### 事件数据接口
|
||||||
|
|
||||||
|
**事件数据层次结构**
|
||||||
|
```typescript
|
||||||
|
// 基础事件数据
|
||||||
|
interface IEventData {
|
||||||
|
timestamp: number;
|
||||||
|
source?: string;
|
||||||
|
eventId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实体相关事件
|
||||||
|
interface IEntityEventData extends IEventData {
|
||||||
|
entityId: number;
|
||||||
|
entityName?: string;
|
||||||
|
entityTag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件相关事件
|
||||||
|
interface IComponentEventData extends IEntityEventData {
|
||||||
|
componentType: string;
|
||||||
|
component?: IComponent;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 清晰的继承层次
|
||||||
|
- 类型安全的事件数据传递
|
||||||
|
- 便于事件处理器的实现
|
||||||
|
|
||||||
|
#### 类型别名
|
||||||
|
|
||||||
|
**ComponentType<T>**
|
||||||
|
```typescript
|
||||||
|
type ComponentType<T extends IComponent = IComponent> = new (...args: any[]) => T;
|
||||||
|
```
|
||||||
|
- 用于类型安全的组件操作
|
||||||
|
- 支持泛型约束
|
||||||
|
- 广泛用于实体和查询系统
|
||||||
|
|
||||||
|
### 设计原则
|
||||||
|
|
||||||
|
#### 1. 接口简化原则
|
||||||
|
- 只保留实际使用的接口
|
||||||
|
- 移除了未使用的复杂接口(如IEntityManager、IEntityQueryBuilder等)
|
||||||
|
- 减少认知负担,提高开发效率
|
||||||
|
|
||||||
|
#### 2. 实现灵活性原则
|
||||||
|
- 接口作为类型约束而非强制实现
|
||||||
|
- 允许具体类有更丰富的实现
|
||||||
|
- 保持向后兼容性
|
||||||
|
|
||||||
|
#### 3. 类型安全原则
|
||||||
|
- 编译时类型检查
|
||||||
|
- 泛型支持提供精确的类型推断
|
||||||
|
- 事件系统的完整类型安全
|
||||||
|
|
||||||
|
### 使用指南
|
||||||
|
|
||||||
|
#### 在项目中使用接口
|
||||||
|
```typescript
|
||||||
|
// 作为类型约束
|
||||||
|
function processComponent<T extends IComponent>(component: T) {
|
||||||
|
if (component.enabled) {
|
||||||
|
component.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作为参数类型
|
||||||
|
function registerSystem(system: ISystemBase) {
|
||||||
|
scene.addEntityProcessor(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作为泛型约束
|
||||||
|
function getComponent<T extends IComponent>(type: ComponentType<T>): T | null {
|
||||||
|
return entity.getComponent(type);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 扩展框架接口
|
||||||
|
```typescript
|
||||||
|
// 如果需要扩展组件接口
|
||||||
|
interface IAdvancedComponent extends IComponent {
|
||||||
|
priority: number;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvancedComponent extends Component implements IAdvancedComponent {
|
||||||
|
public priority: number = 0;
|
||||||
|
public category: string = "default";
|
||||||
|
|
||||||
|
// 实现基础接口方法
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 接口维护
|
||||||
|
|
||||||
|
当前的接口设计已经过精心清理,包含:
|
||||||
|
- **12个核心接口** - 涵盖组件、系统、事件等核心概念
|
||||||
|
- **0个冗余接口** - 移除了所有未使用的接口定义
|
||||||
|
- **完整的类型覆盖** - 为所有主要功能提供类型支持
|
||||||
|
|
||||||
|
这种设计确保了框架的类型安全性,同时保持了代码的简洁性和可维护性。
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
# 核心概念
|
# 核心 API 参考
|
||||||
|
|
||||||
ECS Framework 基于 Entity-Component-System 架构模式,这是一种高度模块化和可扩展的游戏开发架构。本文档将详细介绍框架的核心概念。
|
本文档详细介绍 ECS Framework 的核心 API 和使用方法。
|
||||||
|
|
||||||
|
> 🤔 **不熟悉ECS概念?** 建议先阅读 [技术概念详解](concepts-explained.md) 了解ECS架构基础和性能优化原理
|
||||||
|
|
||||||
## ECS 架构概述
|
## ECS 架构概述
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ entities.forEach(entity => {
|
|||||||
scene.querySystem.clearCache(); // 手动清理缓存
|
scene.querySystem.clearCache(); // 手动清理缓存
|
||||||
|
|
||||||
// 获取性能统计
|
// 获取性能统计
|
||||||
const stats = scene.getPerformanceStats();
|
const stats = scene.getStats();
|
||||||
console.log(`实体数量: ${stats.entityCount}`);
|
console.log(`实体数量: ${stats.entityCount}`);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -101,20 +103,8 @@ console.log(`实体数量: ${stats.entityCount}`);
|
|||||||
### 实体的基本属性
|
### 实体的基本属性
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Vector2 } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
const entity = scene.createEntity("MyEntity");
|
const entity = scene.createEntity("MyEntity");
|
||||||
|
|
||||||
// 位置
|
|
||||||
entity.position = new Vector2(100, 200);
|
|
||||||
entity.position = entity.position.add(new Vector2(10, 0));
|
|
||||||
|
|
||||||
// 旋转(弧度)
|
|
||||||
entity.rotation = Math.PI / 4;
|
|
||||||
|
|
||||||
// 缩放
|
|
||||||
entity.scale = new Vector2(2, 2);
|
|
||||||
|
|
||||||
// 标签(用于分类)
|
// 标签(用于分类)
|
||||||
entity.tag = 1;
|
entity.tag = 1;
|
||||||
|
|
||||||
@@ -126,6 +116,22 @@ entity.active = true;
|
|||||||
|
|
||||||
// 更新顺序
|
// 更新顺序
|
||||||
entity.updateOrder = 10;
|
entity.updateOrder = 10;
|
||||||
|
|
||||||
|
// 注意:框架专注于ECS架构,不提供Transform相关功能
|
||||||
|
// 位置、旋转、缩放等Transform功能需要通过组件实现
|
||||||
|
class TransformComponent extends Component {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public rotation: number = 0;
|
||||||
|
public scaleX: number = 1;
|
||||||
|
public scaleY: number = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Transform组件
|
||||||
|
const transform = entity.addComponent(new TransformComponent());
|
||||||
|
transform.x = 100;
|
||||||
|
transform.y = 200;
|
||||||
|
transform.rotation = Math.PI / 4;
|
||||||
```
|
```
|
||||||
|
|
||||||
### 实体层级关系
|
### 实体层级关系
|
||||||
@@ -278,20 +284,24 @@ class BulletComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册组件池
|
// 注册组件池
|
||||||
ComponentPoolManager.getInstance().registerPool(BulletComponent, 1000);
|
ComponentPoolManager.getInstance().registerPool(
|
||||||
|
'BulletComponent',
|
||||||
|
() => new BulletComponent(),
|
||||||
|
(bullet) => bullet.reset(),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
// 使用对象池获取组件
|
// 使用对象池获取组件
|
||||||
const bullet = ComponentPoolManager.getInstance().getComponent(BulletComponent);
|
const bullet = ComponentPoolManager.getInstance().acquireComponent('BulletComponent');
|
||||||
entity.addComponent(bullet);
|
if (bullet) {
|
||||||
|
entity.addComponent(bullet);
|
||||||
|
}
|
||||||
|
|
||||||
// 释放组件回对象池
|
// 释放组件回对象池
|
||||||
ComponentPoolManager.getInstance().releaseComponent(bullet);
|
ComponentPoolManager.getInstance().releaseComponent('BulletComponent', bullet);
|
||||||
|
|
||||||
// 预热组件池
|
// 预热所有组件池
|
||||||
ComponentPoolManager.getInstance().preWarmPools({
|
ComponentPoolManager.getInstance().prewarmAll(100);
|
||||||
BulletComponent: 1000,
|
|
||||||
EffectComponent: 500
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取池统计
|
// 获取池统计
|
||||||
const stats = ComponentPoolManager.getInstance().getPoolStats();
|
const stats = ComponentPoolManager.getInstance().getPoolStats();
|
||||||
@@ -305,7 +315,9 @@ console.log('组件池统计:', stats);
|
|||||||
### 场景生命周期
|
### 场景生命周期
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class GameScene extends es.Scene {
|
import { Scene } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
public initialize() {
|
public initialize() {
|
||||||
// 场景初始化,创建实体和系统
|
// 场景初始化,创建实体和系统
|
||||||
this.setupEntities();
|
this.setupEntities();
|
||||||
@@ -365,8 +377,14 @@ console.log("系统数量:", stats.processorCount);
|
|||||||
最常用的系统类型,处理实体集合:
|
最常用的系统类型,处理实体集合:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class MovementSystem extends es.EntitySystem {
|
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||||||
protected process(entities: es.Entity[]) {
|
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(MovementComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]) {
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
const movement = entity.getComponent(MovementComponent);
|
const movement = entity.getComponent(MovementComponent);
|
||||||
if (movement) {
|
if (movement) {
|
||||||
@@ -382,12 +400,26 @@ class MovementSystem extends es.EntitySystem {
|
|||||||
定期处理的系统:
|
定期处理的系统:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class HealthRegenerationSystem extends es.ProcessingSystem {
|
import { ProcessingSystem, Time, Matcher } from '@esengine/ecs-framework';
|
||||||
protected process(entities: es.Entity[]) {
|
|
||||||
for (const entity of entities) {
|
class HealthRegenerationSystem extends ProcessingSystem {
|
||||||
const health = entity.getComponent(HealthComponent);
|
constructor() {
|
||||||
|
super(Matcher.empty().all(HealthComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public processSystem() {
|
||||||
|
// ProcessingSystem不处理具体实体,而是执行全局逻辑
|
||||||
|
// 如果需要处理实体,应该使用EntitySystem
|
||||||
|
this.regenerateAllPlayerHealth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private regenerateAllPlayerHealth() {
|
||||||
|
// 通过场景查找所有玩家实体并恢复生命值
|
||||||
|
const players = this.scene.findEntitiesByTag(PlayerTag);
|
||||||
|
for (const player of players) {
|
||||||
|
const health = player.getComponent(HealthComponent);
|
||||||
if (health && health.currentHealth < health.maxHealth) {
|
if (health && health.currentHealth < health.maxHealth) {
|
||||||
health.currentHealth += 10 * es.Time.deltaTime;
|
health.currentHealth += 10 * Time.deltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,12 +431,15 @@ class HealthRegenerationSystem extends es.ProcessingSystem {
|
|||||||
按时间间隔执行的系统:
|
按时间间隔执行的系统:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class SpawnSystem extends es.IntervalSystem {
|
import { IntervalSystem, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class SpawnSystem extends IntervalSystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(3.0); // 每3秒执行一次
|
// IntervalSystem需要Matcher和间隔时间
|
||||||
|
super(Matcher.empty(), 3.0); // 每3秒执行一次
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processSystem() {
|
protected process(entities: Entity[]) {
|
||||||
// 生成敌人
|
// 生成敌人
|
||||||
const enemy = this.scene.createEntity("Enemy");
|
const enemy = this.scene.createEntity("Enemy");
|
||||||
enemy.addComponent(new EnemyComponent());
|
enemy.addComponent(new EnemyComponent());
|
||||||
@@ -417,7 +452,13 @@ class SpawnSystem extends es.IntervalSystem {
|
|||||||
被动系统,不自动处理实体:
|
被动系统,不自动处理实体:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class CollisionSystem extends es.PassiveSystem {
|
import { PassiveSystem, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class CollisionSystem extends PassiveSystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty());
|
||||||
|
}
|
||||||
|
|
||||||
public checkCollisions() {
|
public checkCollisions() {
|
||||||
// 手动调用的碰撞检测逻辑
|
// 手动调用的碰撞检测逻辑
|
||||||
}
|
}
|
||||||
@@ -429,54 +470,32 @@ class CollisionSystem extends es.PassiveSystem {
|
|||||||
时间管理工具类,提供游戏时间相关功能:
|
时间管理工具类,提供游戏时间相关功能:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
import { Time } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 获取时间信息
|
// 获取时间信息
|
||||||
console.log("帧时间:", es.Time.deltaTime);
|
console.log("帧时间:", Time.deltaTime);
|
||||||
console.log("总时间:", es.Time.totalTime);
|
console.log("总时间:", Time.totalTime);
|
||||||
console.log("帧数:", es.Time.frameCount);
|
console.log("帧数:", Time.frameCount);
|
||||||
console.log("时间缩放:", es.Time.timeScale);
|
console.log("时间缩放:", Time.timeScale);
|
||||||
|
|
||||||
// 设置时间缩放(慢动作效果)
|
// 设置时间缩放(慢动作效果)
|
||||||
es.Time.timeScale = 0.5;
|
Time.timeScale = 0.5;
|
||||||
|
|
||||||
// 检查时间间隔
|
// 检查时间间隔
|
||||||
if (es.Time.checkEvery(1.0, lastCheckTime)) {
|
if (Time.checkEvery(1.0, lastCheckTime)) {
|
||||||
// 每秒执行一次
|
// 每秒执行一次
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Vector2(二维向量)
|
|
||||||
|
|
||||||
二维向量类,提供数学运算:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建向量
|
|
||||||
const vec1 = new es.Vector2(10, 20);
|
|
||||||
const vec2 = es.Vector2.zero;
|
|
||||||
const vec3 = es.Vector2.one;
|
|
||||||
|
|
||||||
// 向量运算
|
|
||||||
const sum = vec1.add(vec2);
|
|
||||||
const diff = vec1.subtract(vec2);
|
|
||||||
const scaled = vec1.multiply(2);
|
|
||||||
const normalized = vec1.normalize();
|
|
||||||
|
|
||||||
// 向量属性
|
|
||||||
console.log("长度:", vec1.length);
|
|
||||||
console.log("长度平方:", vec1.lengthSquared);
|
|
||||||
|
|
||||||
// 静态方法
|
|
||||||
const distance = es.Vector2.distance(vec1, vec2);
|
|
||||||
const lerped = es.Vector2.lerp(vec1, vec2, 0.5);
|
|
||||||
const fromAngle = es.Vector2.fromAngle(Math.PI / 4);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控
|
## 性能监控
|
||||||
|
|
||||||
框架内置性能监控工具:
|
框架内置性能监控工具:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
import { PerformanceMonitor } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 获取性能监控实例
|
// 获取性能监控实例
|
||||||
const monitor = es.PerformanceMonitor.instance;
|
const monitor = PerformanceMonitor.instance;
|
||||||
|
|
||||||
// 查看性能数据
|
// 查看性能数据
|
||||||
console.log("平均FPS:", monitor.averageFPS);
|
console.log("平均FPS:", monitor.averageFPS);
|
||||||
@@ -493,22 +512,46 @@ monitor.reset();
|
|||||||
内存管理优化工具:
|
内存管理优化工具:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 创建对象池
|
import { Pool, IPoolable } from '@esengine/ecs-framework';
|
||||||
class BulletPool extends es.Pool<Bullet> {
|
|
||||||
protected createObject(): Bullet {
|
// 定义可池化的对象(需要实现IPoolable接口)
|
||||||
return new Bullet();
|
class Bullet implements IPoolable {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public speed: number = 0;
|
||||||
|
|
||||||
|
// 重置对象状态,准备重用
|
||||||
|
public reset(): void {
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
this.speed = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bulletPool = new BulletPool();
|
// 创建对象池
|
||||||
|
const bulletPool = new Pool<Bullet>(() => new Bullet(), 100);
|
||||||
|
|
||||||
|
// 预热对象池
|
||||||
|
bulletPool.warmUp(20);
|
||||||
|
|
||||||
// 使用对象池
|
// 使用对象池
|
||||||
const bullet = bulletPool.obtain();
|
const bullet = bulletPool.obtain();
|
||||||
// 使用bullet...
|
bullet.x = 100;
|
||||||
|
bullet.y = 200;
|
||||||
|
bullet.speed = 500;
|
||||||
|
|
||||||
|
// 使用完后归还到池中
|
||||||
bulletPool.free(bullet);
|
bulletPool.free(bullet);
|
||||||
|
|
||||||
|
// 查看池统计信息
|
||||||
|
console.log(bulletPool.getStats());
|
||||||
|
|
||||||
// 清空对象池
|
// 清空对象池
|
||||||
bulletPool.clear();
|
bulletPool.clear();
|
||||||
|
|
||||||
|
// 使用静态方法(自动管理池)
|
||||||
|
const bullet2 = Pool.obtain(Bullet);
|
||||||
|
Pool.free(Bullet, bullet2);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 最佳实践
|
## 最佳实践
|
||||||
@@ -539,57 +582,18 @@ bulletPool.clear();
|
|||||||
|
|
||||||
## 高级性能优化功能
|
## 高级性能优化功能
|
||||||
|
|
||||||
### 位掩码优化器
|
### 查询系统优化
|
||||||
|
|
||||||
位掩码优化器可以预计算和缓存常用的组件掩码,提升查询性能。
|
框架内部已集成查询优化,无需手动配置。查询系统会自动使用最优的算法:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { BitMaskOptimizer } from '@esengine/ecs-framework';
|
// 查询系统会自动优化这些操作
|
||||||
|
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||||
|
const renderableEntities = scene.querySystem.queryAll(PositionComponent, RenderComponent);
|
||||||
|
|
||||||
const optimizer = BitMaskOptimizer.getInstance();
|
// 获取查询统计信息
|
||||||
|
const queryStats = scene.querySystem.getStats();
|
||||||
// 注册组件类型
|
console.log('查询统计:', queryStats);
|
||||||
optimizer.registerComponentType(PositionComponent);
|
|
||||||
optimizer.registerComponentType(VelocityComponent);
|
|
||||||
optimizer.registerComponentType(RenderComponent);
|
|
||||||
|
|
||||||
// 预计算常用掩码组合
|
|
||||||
optimizer.precomputeCommonMasks();
|
|
||||||
|
|
||||||
// 获取优化的掩码
|
|
||||||
const positionMask = optimizer.getComponentMask(PositionComponent);
|
|
||||||
const movementMask = optimizer.getCombinedMask([PositionComponent, VelocityComponent]);
|
|
||||||
|
|
||||||
// 掩码操作
|
|
||||||
const hasBothComponents = optimizer.hasAllComponents(entityMask, movementMask);
|
|
||||||
const hasAnyComponent = optimizer.hasAnyComponent(entityMask, movementMask);
|
|
||||||
|
|
||||||
// 获取掩码分析
|
|
||||||
const analysis = optimizer.analyzeMask(entityMask);
|
|
||||||
console.log('掩码包含的组件类型:', analysis.componentTypes);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 延迟索引更新器
|
|
||||||
|
|
||||||
批量更新索引可以显著提升大规模实体操作的性能。
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { IndexUpdateBatcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
const batcher = new IndexUpdateBatcher((updates) => {
|
|
||||||
// 处理批量更新
|
|
||||||
console.log(`批量处理 ${updates.length} 个索引更新`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 配置批量大小和延迟
|
|
||||||
batcher.configure(100, 16); // 批量大小100,延迟16ms
|
|
||||||
|
|
||||||
// 添加更新任务
|
|
||||||
batcher.addUpdate("add", entity, componentMask);
|
|
||||||
batcher.addUpdate("remove", entity, componentMask);
|
|
||||||
|
|
||||||
// 强制刷新
|
|
||||||
batcher.flush();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 批量操作API
|
### 批量操作API
|
||||||
@@ -598,14 +602,8 @@ batcher.flush();
|
|||||||
// 批量创建实体 - 最高性能
|
// 批量创建实体 - 最高性能
|
||||||
const entities = scene.createEntities(10000, "Bullets");
|
const entities = scene.createEntities(10000, "Bullets");
|
||||||
|
|
||||||
// 延迟缓存清理
|
|
||||||
entities.forEach(entity => {
|
|
||||||
scene.addEntity(entity, false); // 延迟清理
|
|
||||||
});
|
|
||||||
scene.querySystem.clearCache(); // 手动清理
|
|
||||||
|
|
||||||
// 批量查询优化
|
// 批量查询优化
|
||||||
const movingEntities = scene.getEntitiesWithComponents([PositionComponent, VelocityComponent]);
|
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities;
|
||||||
```
|
```
|
||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# 实体使用指南
|
# 实体基础指南
|
||||||
|
|
||||||
本指南详细介绍 ECS Framework 中实体(Entity)的所有功能和使用方法。
|
本指南介绍实体(Entity)的基本概念和基础使用方法。
|
||||||
|
|
||||||
|
> 📖 **需要高级实体管理?** 请参考 [EntityManager 指南](entity-manager-example.md) 了解高性能查询和批量操作
|
||||||
|
|
||||||
## 实体概述
|
## 实体概述
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ if (entity.hasComponent(HealthComponent)) {
|
|||||||
|
|
||||||
// 检查组件掩码(高性能)
|
// 检查组件掩码(高性能)
|
||||||
const mask = entity.componentMask;
|
const mask = entity.componentMask;
|
||||||
console.log(`组件掩码: ${mask.toString(2)}`);
|
console.log(`组件掩码: ${mask.toString(2)}`); // 二进制表示
|
||||||
```
|
```
|
||||||
|
|
||||||
### 移除组件
|
### 移除组件
|
||||||
@@ -295,80 +297,20 @@ if (entity.isDestroyed) {
|
|||||||
// 4. 从场景中移除
|
// 4. 从场景中移除
|
||||||
```
|
```
|
||||||
|
|
||||||
## 性能优化
|
# 高级特性请参考其他指南
|
||||||
|
|
||||||
### 组件缓存
|
> 📚 **更多功能:**
|
||||||
|
> - **高性能查询和批量操作** → [EntityManager 指南](entity-manager-example.md)
|
||||||
|
> - **性能优化技术** → [性能优化指南](performance-optimization.md)
|
||||||
|
> - **组件索引和缓存** → [技术概念详解](concepts-explained.md)
|
||||||
|
|
||||||
```typescript
|
## 基础最佳实践
|
||||||
// 预热组件缓存(提高后续访问性能)
|
|
||||||
entity.warmUpComponentCache();
|
|
||||||
|
|
||||||
// 清理组件缓存
|
|
||||||
entity.cleanupComponentCache();
|
|
||||||
|
|
||||||
// 获取缓存统计信息
|
|
||||||
const cacheStats = entity.getComponentCacheStats();
|
|
||||||
console.log(`缓存命中率: ${cacheStats.cacheStats.hitRate}`);
|
|
||||||
console.log(`组件访问统计:`, cacheStats.accessStats);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量操作
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 批量添加组件(比单个添加更高效)
|
|
||||||
const components = entity.addComponents([
|
|
||||||
new PositionComponent(0, 0),
|
|
||||||
new VelocityComponent(50, 0),
|
|
||||||
new HealthComponent(100)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 批量移除组件
|
|
||||||
const removed = entity.removeComponentsByTypes([
|
|
||||||
HealthComponent,
|
|
||||||
VelocityComponent
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 调试和监控
|
|
||||||
|
|
||||||
### 调试信息
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取详细的调试信息
|
|
||||||
const debugInfo = entity.getDebugInfo();
|
|
||||||
console.log("实体调试信息:", debugInfo);
|
|
||||||
|
|
||||||
// 调试信息包含:
|
|
||||||
// - 基本属性(名称、ID、状态等)
|
|
||||||
// - 组件信息(数量、类型、掩码等)
|
|
||||||
// - 层次结构信息(父子关系、深度等)
|
|
||||||
// - 性能统计(缓存命中率、访问统计等)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实体比较
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 比较两个实体的优先级
|
|
||||||
const result = entity1.compareTo(entity2);
|
|
||||||
if (result < 0) {
|
|
||||||
// entity1 优先级更高
|
|
||||||
} else if (result > 0) {
|
|
||||||
// entity2 优先级更高
|
|
||||||
} else {
|
|
||||||
// 优先级相同
|
|
||||||
}
|
|
||||||
|
|
||||||
// 实体的字符串表示
|
|
||||||
console.log(entity.toString()); // "Entity[Player:1]"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 合理使用标签
|
### 1. 合理使用标签
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 定义标签常量
|
// 定义标签常量
|
||||||
const Tags = {
|
const EntityTags = {
|
||||||
PLAYER: 1,
|
PLAYER: 1,
|
||||||
ENEMY: 2,
|
ENEMY: 2,
|
||||||
PROJECTILE: 3,
|
PROJECTILE: 3,
|
||||||
@@ -376,70 +318,42 @@ const Tags = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// 使用标签进行分类
|
// 使用标签进行分类
|
||||||
player.tag = Tags.PLAYER;
|
player.tag = EntityTags.PLAYER;
|
||||||
enemy.tag = Tags.ENEMY;
|
enemy.tag = EntityTags.ENEMY;
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 优化更新顺序
|
### 2. 正确的销毁处理
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 设置合理的更新顺序
|
|
||||||
player.updateOrder = 0; // 玩家最先更新
|
|
||||||
enemy.updateOrder = 1; // 敌人其次
|
|
||||||
projectile.updateOrder = 2; // 投射物最后
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 合理使用层次结构
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建复合实体
|
|
||||||
const tank = scene.createEntity("Tank");
|
|
||||||
const turret = scene.createEntity("Turret");
|
|
||||||
const barrel = scene.createEntity("Barrel");
|
|
||||||
|
|
||||||
// 建立层次关系
|
|
||||||
tank.addChild(turret);
|
|
||||||
turret.addChild(barrel);
|
|
||||||
|
|
||||||
// 这样可以通过控制父实体来影响整个层次结构
|
|
||||||
tank.active = false; // 整个坦克都会被停用
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 组件缓存优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 对于频繁访问的组件,预热缓存
|
|
||||||
entity.warmUpComponentCache();
|
|
||||||
|
|
||||||
// 定期清理不常用的缓存
|
|
||||||
setInterval(() => {
|
|
||||||
entity.cleanupComponentCache();
|
|
||||||
}, 5000);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 避免内存泄漏
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 确保正确销毁实体
|
// 确保正确销毁实体
|
||||||
|
if (!entity.isDestroyed) {
|
||||||
|
entity.destroy(); // 自动移除组件和层次关系
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查实体状态
|
||||||
if (entity.isDestroyed) {
|
if (entity.isDestroyed) {
|
||||||
return; // 避免操作已销毁的实体
|
return; // 避免操作已销毁的实体
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
// 在适当的时候销毁不需要的实体
|
### 3. 组件生命周期
|
||||||
if (enemy.getComponent(HealthComponent)?.isDead()) {
|
|
||||||
enemy.destroy();
|
```typescript
|
||||||
|
// 正确添加组件
|
||||||
|
const health = entity.addComponent(new HealthComponent(100));
|
||||||
|
|
||||||
|
// 安全获取组件
|
||||||
|
const healthComp = entity.getComponent(HealthComponent);
|
||||||
|
if (healthComp && healthComp.currentHealth <= 0) {
|
||||||
|
entity.destroy();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 常见问题
|
## 常见问题
|
||||||
|
|
||||||
### Q: 实体可以在不同场景间移动吗?
|
### Q: 实体如何实现位置、旋转等变换?
|
||||||
|
|
||||||
A: 不可以。实体与场景紧密绑定,如果需要在场景间传递数据,应该序列化实体的组件数据,然后在新场景中重新创建。
|
A: 通过添加相应的组件:
|
||||||
|
|
||||||
### Q: 如何实现实体的位置、旋转、缩放?
|
|
||||||
|
|
||||||
A: 框架本身不提供这些属性,需要通过组件来实现:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class TransformComponent extends Component {
|
class TransformComponent extends Component {
|
||||||
@@ -448,35 +362,9 @@ class TransformComponent extends Component {
|
|||||||
public scale = { x: 1, y: 1 };
|
public scale = { x: 1, y: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const transform = entity.addComponent(new TransformComponent());
|
entity.addComponent(new TransformComponent());
|
||||||
transform.position.x = 100;
|
|
||||||
transform.rotation = Math.PI / 4;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q: 实体的更新顺序如何影响性能?
|
### Q: 实体可以在场景间移动吗?
|
||||||
|
|
||||||
A: 更新顺序主要影响游戏逻辑的执行顺序,对性能影响较小。但合理的更新顺序可以避免一些逻辑问题,比如确保输入处理在移动之前执行。
|
A: 不可以。实体与场景绑定,需要在新场景中重新创建。
|
||||||
|
|
||||||
### Q: 如何处理大量实体的性能问题?
|
|
||||||
|
|
||||||
A:
|
|
||||||
1. 使用对象池重用实体
|
|
||||||
2. 合理使用组件缓存
|
|
||||||
3. 避免不必要的组件查询
|
|
||||||
4. 使用批量操作
|
|
||||||
5. 定期清理销毁的实体
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 使用对象池
|
|
||||||
class EntityPool extends Pool<Entity> {
|
|
||||||
protected createObject(): Entity {
|
|
||||||
return scene.createEntity("PooledEntity");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected resetObject(entity: Entity): void {
|
|
||||||
entity.removeAllComponents();
|
|
||||||
entity.active = true;
|
|
||||||
entity.enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,421 +1,370 @@
|
|||||||
# EntityManager 使用指南
|
# EntityManager 使用指南
|
||||||
|
|
||||||
EntityManager 是 ECS Framework 的核心管理系统,提供统一的实体管理、高性能查询和自动优化功能。
|
本文档详细介绍 EntityManager 的使用方法和最佳实践。
|
||||||
|
|
||||||
## 快速开始
|
## 目录
|
||||||
|
|
||||||
### 创建实体管理器
|
1. [基础用法](#基础用法)
|
||||||
|
2. [查询系统](#查询系统)
|
||||||
|
3. [实体管理](#实体管理)
|
||||||
|
4. [性能监控](#性能监控)
|
||||||
|
5. [最佳实践](#最佳实践)
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
### 创建 EntityManager
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EntityManager, Scene } from '@esengine/ecs-framework';
|
import { EntityManager, Scene } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 通常在游戏管理器中创建
|
// 创建场景和实体管理器
|
||||||
const scene = new Scene();
|
const scene = new Scene();
|
||||||
const entityManager = new EntityManager(scene);
|
const entityManager = new EntityManager();
|
||||||
```
|
|
||||||
|
|
||||||
### 基础实体操作
|
// 批量创建实体(使用Scene方法)
|
||||||
|
const enemies = scene.createEntities(50, "Enemy");
|
||||||
|
|
||||||
```typescript
|
// 为实体添加组件
|
||||||
// 创建单个实体
|
|
||||||
const player = entityManager.createEntity("Player");
|
|
||||||
player.addComponent(new PositionComponent(100, 100));
|
|
||||||
player.addComponent(new HealthComponent(100));
|
|
||||||
player.tag = "player";
|
|
||||||
|
|
||||||
// 批量创建实体
|
|
||||||
const enemies = entityManager.createEntities(50, "Enemy");
|
|
||||||
enemies.forEach((enemy, index) => {
|
enemies.forEach((enemy, index) => {
|
||||||
enemy.addComponent(new PositionComponent(
|
enemy.addComponent(new PositionComponent(
|
||||||
Math.random() * 800,
|
Math.random() * 800,
|
||||||
Math.random() * 600
|
Math.random() * 600
|
||||||
));
|
));
|
||||||
enemy.addComponent(new HealthComponent(30));
|
enemy.addComponent(new HealthComponent(100));
|
||||||
enemy.tag = "enemy";
|
enemy.addComponent(new VelocityComponent(
|
||||||
|
(Math.random() - 0.5) * 100,
|
||||||
|
(Math.random() - 0.5) * 100
|
||||||
|
));
|
||||||
|
enemy.tag = 2; // 敌人标签
|
||||||
});
|
});
|
||||||
|
|
||||||
// 销毁实体
|
|
||||||
entityManager.destroyEntity(player);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 高性能查询系统
|
## 查询系统
|
||||||
|
|
||||||
EntityManager 提供多种查询方式,自动选择最优的查询策略。
|
|
||||||
|
|
||||||
### 基础查询
|
### 基础查询
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 通过ID查询(O(1))
|
// 按组件类型查询
|
||||||
const entity = entityManager.getEntity(123);
|
|
||||||
|
|
||||||
// 通过名称查询(O(1) 哈希查找)
|
|
||||||
const player = entityManager.getEntityByName("Player");
|
|
||||||
|
|
||||||
// 通过标签查询(O(1) 索引查找)
|
|
||||||
const enemies = entityManager.getEntitiesByTag("enemy");
|
|
||||||
|
|
||||||
// 组件查询(使用O(1)组件索引)
|
|
||||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||||
|
|
||||||
// 多组件查询(使用Archetype优化)
|
// 按标签查询
|
||||||
const movingEntities = entityManager.getEntitiesWithComponents([
|
const enemies = entityManager.getEntitiesByTag(2);
|
||||||
PositionComponent,
|
const players = entityManager.getEntitiesByTag(1);
|
||||||
VelocityComponent
|
|
||||||
]);
|
// 按名称查询
|
||||||
|
const boss = entityManager.getEntityByName("BossEnemy");
|
||||||
|
|
||||||
|
// 获取所有实体
|
||||||
|
const allEntities = entityManager.getAllEntities();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 流式查询API
|
### 流式查询 API
|
||||||
|
|
||||||
EntityManager 提供强大的流式查询构建器:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 基础查询构建
|
// 复杂查询条件
|
||||||
const results = entityManager
|
const movingEnemies = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll([PositionComponent, HealthComponent]) // 必须包含这些组件
|
.withAll(PositionComponent, VelocityComponent, HealthComponent)
|
||||||
.withoutTag("dead") // 不能有死亡标签
|
.withTag(2) // 敌人标签
|
||||||
.active(true) // 必须激活
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// 复杂条件查询
|
// 查询活跃的玩家
|
||||||
const livingEnemies = entityManager
|
const activePlayers = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll([PositionComponent, HealthComponent])
|
.withAll(PositionComponent)
|
||||||
.withTag("enemy")
|
.withTag(1) // 玩家标签
|
||||||
.withoutTag("dead")
|
.active() // 只查询活跃实体
|
||||||
.where(entity => {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
return health && health.currentHealth > 0;
|
|
||||||
})
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// 查询变体
|
// 排除特定组件的实体
|
||||||
const firstEnemy = entityManager
|
const nonCombatEntities = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withTag("enemy")
|
.withAll(PositionComponent)
|
||||||
.first(); // 获取第一个匹配
|
.without(WeaponComponent, HealthComponent)
|
||||||
|
|
||||||
const enemyCount = entityManager
|
|
||||||
.query()
|
|
||||||
.withTag("enemy")
|
|
||||||
.count(); // 获取数量
|
|
||||||
|
|
||||||
// 直接处理查询结果
|
|
||||||
entityManager
|
|
||||||
.query()
|
|
||||||
.withAll([HealthComponent])
|
|
||||||
.forEach(entity => {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (health.currentHealth <= 0) {
|
|
||||||
entity.addTag("dead");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 高级查询选项
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 组合条件查询
|
|
||||||
const combatUnits = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll([PositionComponent, HealthComponent]) // AND条件
|
|
||||||
.withAny([WeaponComponent, MagicComponent]) // OR条件
|
|
||||||
.without([DeadComponent]) // NOT条件
|
|
||||||
.withTag("combatant")
|
|
||||||
.withoutTag("peaceful")
|
|
||||||
.active(true)
|
|
||||||
.enabled(true)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// 使用自定义过滤器
|
// 自定义条件查询
|
||||||
const nearbyEnemies = entityManager
|
const nearbyEnemies = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll([PositionComponent])
|
.withAll(PositionComponent)
|
||||||
.withTag("enemy")
|
.withTag(2)
|
||||||
.where(entity => {
|
.where(entity => {
|
||||||
const pos = entity.getComponent(PositionComponent);
|
const pos = entity.getComponent(PositionComponent);
|
||||||
const distance = Math.sqrt(
|
return pos && Math.abs(pos.x - playerX) < 100;
|
||||||
Math.pow(pos.x - playerPos.x, 2) +
|
|
||||||
Math.pow(pos.y - playerPos.y, 2)
|
|
||||||
);
|
|
||||||
return distance < 100; // 距离玩家100像素内
|
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
```
|
```
|
||||||
|
|
||||||
## 批量操作
|
## 实体管理
|
||||||
|
|
||||||
EntityManager 提供高效的批量操作方法:
|
### 创建和销毁实体
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 遍历所有实体
|
// 创建单个实体
|
||||||
entityManager.forEachEntity(entity => {
|
const player = entityManager.createEntity("Player");
|
||||||
// 处理每个实体
|
player.addComponent(new PositionComponent(400, 300));
|
||||||
if (entity.position.x < 0) {
|
player.addComponent(new HealthComponent(100));
|
||||||
entity.position.x = 0;
|
player.tag = 1;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 遍历特定组件的实体
|
// 销毁实体
|
||||||
entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => {
|
entityManager.destroyEntity(player);
|
||||||
if (health.currentHealth <= 0) {
|
|
||||||
entity.addTag("dead");
|
|
||||||
entity.enabled = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 批量创建并配置实体
|
// 按名称销毁
|
||||||
const bullets = entityManager.createEntities(100, "Bullet", (bullet, index) => {
|
entityManager.destroyEntity("Enemy_1");
|
||||||
bullet.addComponent(new PositionComponent(
|
|
||||||
100 + index * 10,
|
// 按ID销毁
|
||||||
100
|
entityManager.destroyEntity(123);
|
||||||
));
|
|
||||||
bullet.addComponent(new VelocityComponent(0, -200));
|
|
||||||
bullet.tag = "projectile";
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 性能优化系统
|
### 实体查找
|
||||||
|
|
||||||
EntityManager 内置了三个性能优化系统:
|
|
||||||
|
|
||||||
### 1. 组件索引系统
|
|
||||||
|
|
||||||
自动为组件查询提供O(1)性能:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取组件索引统计
|
// 按ID查找
|
||||||
const componentIndex = entityManager.getComponentIndex();
|
const entity = entityManager.getEntity(123);
|
||||||
const stats = componentIndex.getPerformanceStats();
|
|
||||||
|
|
||||||
console.log('组件索引统计:', {
|
// 按名称查找
|
||||||
totalQueries: stats.totalQueries,
|
const player = entityManager.getEntityByName("Player");
|
||||||
indexHits: stats.indexHits,
|
|
||||||
hitRate: (stats.indexHits / stats.totalQueries * 100).toFixed(2) + '%'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 手动优化(通常自动进行)
|
// 检查实体是否存在
|
||||||
componentIndex.optimize();
|
if (entity && !entity.isDestroyed) {
|
||||||
|
// 实体有效
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Archetype系统
|
## 性能监控
|
||||||
|
|
||||||
按组件组合分组实体,优化批量查询:
|
### 基础统计
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取Archetype统计
|
// 获取实体数量
|
||||||
const archetypeSystem = entityManager.getArchetypeSystem();
|
console.log('总实体数:', entityManager.entityCount);
|
||||||
const archetypeStats = archetypeSystem.getStatistics();
|
console.log('活跃实体数:', entityManager.activeEntityCount);
|
||||||
|
|
||||||
console.log('Archetype统计:', {
|
// 获取场景统计
|
||||||
totalArchetypes: archetypeStats.totalArchetypes,
|
const sceneStats = scene.getStats();
|
||||||
totalEntities: archetypeStats.totalEntities,
|
console.log('场景统计:', {
|
||||||
queryCacheSize: archetypeStats.queryCacheSize
|
实体数量: sceneStats.entityCount,
|
||||||
|
系统数量: sceneStats.processorCount
|
||||||
});
|
});
|
||||||
|
|
||||||
// 查看所有原型
|
// 获取查询系统统计
|
||||||
console.log('当前原型:', archetypeSystem.getAllArchetypes());
|
const queryStats = scene.querySystem.getStats();
|
||||||
|
console.log('查询统计:', queryStats);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 脏标记系统
|
## 最佳实践
|
||||||
|
|
||||||
追踪实体变更,避免不必要的更新:
|
### 1. 高效查询
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取脏标记统计
|
// ✅ 好的做法:缓存查询结果
|
||||||
const dirtyTracking = entityManager.getDirtyTrackingSystem();
|
class CombatSystem extends EntitySystem {
|
||||||
const dirtyStats = dirtyTracking.getPerformanceStats();
|
private cachedEnemies: Entity[] = [];
|
||||||
|
private lastUpdateFrame = 0;
|
||||||
console.log('脏标记统计:', {
|
|
||||||
totalMarks: dirtyStats.totalMarks,
|
|
||||||
batchesProcessed: dirtyStats.batchesProcessed,
|
|
||||||
listenersNotified: dirtyStats.listenersNotified
|
|
||||||
});
|
|
||||||
|
|
||||||
// 手动处理脏标记
|
|
||||||
dirtyTracking.processDirtyMarks();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实体管理器统计
|
|
||||||
|
|
||||||
获取EntityManager的综合性能数据:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const stats = entityManager.getStatistics();
|
|
||||||
|
|
||||||
console.log('EntityManager统计:', {
|
|
||||||
// 基础统计
|
|
||||||
entityCount: stats.entityCount,
|
|
||||||
activeEntityCount: stats.activeEntityCount,
|
|
||||||
|
|
||||||
// 查询统计
|
|
||||||
totalQueries: stats.totalQueries,
|
|
||||||
indexHits: stats.indexHits,
|
|
||||||
archetypeHits: stats.archetypeHits,
|
|
||||||
|
|
||||||
// 性能指标
|
|
||||||
averageQueryTime: stats.averageQueryTime,
|
|
||||||
hitRate: (stats.indexHits / stats.totalQueries * 100).toFixed(2) + '%'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 系统优化和清理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 手动触发优化
|
|
||||||
entityManager.optimize();
|
|
||||||
|
|
||||||
// 内存清理
|
|
||||||
entityManager.cleanup();
|
|
||||||
|
|
||||||
// 压缩数据结构
|
|
||||||
entityManager.compact();
|
|
||||||
|
|
||||||
// 获取内存使用情况
|
|
||||||
const memoryStats = entityManager.getMemoryUsage();
|
|
||||||
console.log('内存使用:', {
|
|
||||||
entityIndexSize: memoryStats.entityIndex,
|
|
||||||
componentIndexSize: memoryStats.componentIndex,
|
|
||||||
archetypeSize: memoryStats.archetype
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实际使用案例
|
|
||||||
|
|
||||||
### 游戏系统集成
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
private entityManager: EntityManager;
|
|
||||||
|
|
||||||
constructor(scene: Scene) {
|
|
||||||
super();
|
|
||||||
this.entityManager = new EntityManager(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
protected process(entities: Entity[]): void {
|
||||||
// 使用高效查询获取移动实体
|
// 每5帧更新一次缓存
|
||||||
const movingEntities = this.entityManager
|
if (Time.frameCount - this.lastUpdateFrame > 5) {
|
||||||
.query()
|
this.cachedEnemies = this.entityManager
|
||||||
.withAll([PositionComponent, VelocityComponent])
|
.query()
|
||||||
.active(true)
|
.withAll(PositionComponent, HealthComponent)
|
||||||
.execute();
|
.withTag(2)
|
||||||
|
.execute();
|
||||||
|
this.lastUpdateFrame = Time.frameCount;
|
||||||
|
}
|
||||||
|
|
||||||
// 批量处理
|
// 使用缓存的结果
|
||||||
movingEntities.forEach(entity => {
|
this.cachedEnemies.forEach(enemy => {
|
||||||
const position = entity.getComponent(PositionComponent);
|
// 处理敌人逻辑
|
||||||
const velocity = entity.getComponent(VelocityComponent);
|
|
||||||
|
|
||||||
position.x += velocity.dx * Time.deltaTime;
|
|
||||||
position.y += velocity.dy * Time.deltaTime;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 复杂查询示例
|
### 2. 批量操作
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 战斗系统:查找攻击范围内的敌人
|
// ✅ 好的做法:批量创建和配置
|
||||||
class CombatSystem {
|
function createBulletWave(count: number): Entity[] {
|
||||||
private entityManager: EntityManager;
|
// 使用Scene的批量创建
|
||||||
|
const bullets = scene.createEntities(count, "Bullet");
|
||||||
|
|
||||||
|
// 批量配置组件
|
||||||
|
bullets.forEach((bullet, index) => {
|
||||||
|
const angle = (index / count) * Math.PI * 2;
|
||||||
|
bullet.addComponent(new PositionComponent(400, 300));
|
||||||
|
bullet.addComponent(new VelocityComponent(
|
||||||
|
Math.cos(angle) * 200,
|
||||||
|
Math.sin(angle) * 200
|
||||||
|
));
|
||||||
|
bullet.addComponent(new BulletComponent());
|
||||||
|
bullet.tag = 3; // 子弹标签
|
||||||
|
});
|
||||||
|
|
||||||
|
return bullets;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 内存管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的做法:及时清理无用实体
|
||||||
|
class CleanupSystem extends EntitySystem {
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
// 清理超出边界的子弹
|
||||||
|
const bullets = this.entityManager.getEntitiesByTag(3);
|
||||||
|
bullets.forEach(bullet => {
|
||||||
|
const pos = bullet.getComponent(PositionComponent);
|
||||||
|
if (pos && (pos.x < -100 || pos.x > 900 || pos.y < -100 || pos.y > 700)) {
|
||||||
|
this.entityManager.destroyEntity(bullet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理死亡的敌人
|
||||||
|
const deadEnemies = this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(HealthComponent)
|
||||||
|
.withTag(2)
|
||||||
|
.where(entity => {
|
||||||
|
const health = entity.getComponent(HealthComponent);
|
||||||
|
return health && health.currentHealth <= 0;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
deadEnemies.forEach(enemy => {
|
||||||
|
this.entityManager.destroyEntity(enemy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 查询优化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的做法:使用合适的查询方法
|
||||||
|
class GameSystem extends EntitySystem {
|
||||||
findTargetsInRange(attacker: Entity, range: number): Entity[] {
|
findTargetsInRange(attacker: Entity, range: number): Entity[] {
|
||||||
const attackerPos = attacker.getComponent(PositionComponent);
|
const attackerPos = attacker.getComponent(PositionComponent);
|
||||||
if (!attackerPos) return [];
|
if (!attackerPos) return [];
|
||||||
|
|
||||||
|
// 先按标签快速筛选,再按距离过滤
|
||||||
return this.entityManager
|
return this.entityManager
|
||||||
.query()
|
.getEntitiesByTag(2) // 敌人标签
|
||||||
.withAll([PositionComponent, HealthComponent])
|
.filter(enemy => {
|
||||||
.withTag("enemy")
|
const enemyPos = enemy.getComponent(PositionComponent);
|
||||||
.withoutTag("dead")
|
if (!enemyPos) return false;
|
||||||
.where(entity => {
|
|
||||||
const pos = entity.getComponent(PositionComponent);
|
|
||||||
const distance = Math.sqrt(
|
const distance = Math.sqrt(
|
||||||
Math.pow(pos.x - attackerPos.x, 2) +
|
Math.pow(attackerPos.x - enemyPos.x, 2) +
|
||||||
Math.pow(pos.y - attackerPos.y, 2)
|
Math.pow(attackerPos.y - enemyPos.y, 2)
|
||||||
);
|
);
|
||||||
return distance <= range;
|
return distance <= range;
|
||||||
})
|
});
|
||||||
.execute();
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
EntityManager,
|
||||||
|
Scene,
|
||||||
|
Entity,
|
||||||
|
Component,
|
||||||
|
EntitySystem,
|
||||||
|
Matcher
|
||||||
|
} from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 组件定义
|
||||||
|
class PositionComponent extends Component {
|
||||||
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HealthComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public maxHealth: number = 100,
|
||||||
|
public currentHealth: number = 100
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏管理器
|
||||||
|
class GameManager {
|
||||||
|
private scene: Scene;
|
||||||
|
private entityManager: EntityManager;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene = new Scene();
|
||||||
|
this.entityManager = new EntityManager();
|
||||||
|
this.setupGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优化版本:使用空间分区(如果实现了的话)
|
private setupGame(): void {
|
||||||
findTargetsInRangeOptimized(attacker: Entity, range: number): Entity[] {
|
// 创建玩家
|
||||||
// 首先通过空间查询缩小范围
|
const player = this.entityManager.createEntity("Player");
|
||||||
const nearbyEntities = this.spatialIndex.queryRange(
|
player.addComponent(new PositionComponent(400, 300));
|
||||||
attackerPos.x - range,
|
player.addComponent(new HealthComponent(100));
|
||||||
attackerPos.y - range,
|
player.tag = 1;
|
||||||
attackerPos.x + range,
|
|
||||||
attackerPos.y + range
|
|
||||||
);
|
|
||||||
|
|
||||||
// 然后使用EntityManager进行精确过滤
|
// 创建敌人
|
||||||
return this.entityManager
|
const enemies = this.scene.createEntities(10, "Enemy");
|
||||||
.query()
|
enemies.forEach(enemy => {
|
||||||
.withAll([HealthComponent])
|
enemy.addComponent(new PositionComponent(
|
||||||
.withTag("enemy")
|
Math.random() * 800,
|
||||||
.withoutTag("dead")
|
Math.random() * 600
|
||||||
.where(entity => nearbyEntities.includes(entity))
|
));
|
||||||
.execute();
|
enemy.addComponent(new HealthComponent(50));
|
||||||
|
enemy.tag = 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加系统
|
||||||
|
this.scene.addEntityProcessor(new HealthSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(): void {
|
||||||
|
this.scene.update();
|
||||||
|
|
||||||
|
// 输出统计信息
|
||||||
|
console.log('实体数量:', this.entityManager.entityCount);
|
||||||
|
console.log('活跃实体数:', this.entityManager.activeEntityCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
## 性能建议
|
// 生命值系统
|
||||||
|
class HealthSystem extends EntitySystem {
|
||||||
### 查询优化
|
constructor() {
|
||||||
|
super(Matcher.empty().all(HealthComponent));
|
||||||
1. **利用索引**: 优先使用组件查询和标签查询,它们具有O(1)性能
|
}
|
||||||
2. **减少自定义过滤**: `where()`条件虽然灵活,但会降低性能
|
|
||||||
3. **缓存查询结果**: 对于不经常变化的查询结果,考虑缓存
|
protected process(entities: Entity[]): void {
|
||||||
|
const healthEntities = this.scene.querySystem.queryAll(HealthComponent);
|
||||||
```typescript
|
|
||||||
// ✅ 推荐:使用索引查询
|
healthEntities.entities.forEach(entity => {
|
||||||
const enemies = entityManager.getEntitiesByTag("enemy");
|
const health = entity.getComponent(HealthComponent);
|
||||||
|
if (health && health.currentHealth <= 0) {
|
||||||
// ⚠️ 谨慎:自定义过滤
|
console.log(`实体 ${entity.name} 死亡`);
|
||||||
const enemies = entityManager
|
entity.destroy();
|
||||||
.query()
|
}
|
||||||
.where(entity => entity.name.includes("Enemy"))
|
});
|
||||||
.execute();
|
}
|
||||||
```
|
|
||||||
|
|
||||||
### 批量操作优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ 推荐:批量创建
|
|
||||||
const bullets = entityManager.createEntities(100, "Bullet");
|
|
||||||
|
|
||||||
// ❌ 避免:循环单独创建
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
entityManager.createEntity("Bullet");
|
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
### 内存管理
|
// 启动游戏
|
||||||
|
const game = new GameManager();
|
||||||
```typescript
|
setInterval(() => game.update(), 16); // 60 FPS
|
||||||
// 定期清理
|
|
||||||
setInterval(() => {
|
|
||||||
entityManager.cleanup();
|
|
||||||
}, 30000); // 每30秒清理一次
|
|
||||||
|
|
||||||
// 监控性能
|
|
||||||
const stats = entityManager.getStatistics();
|
|
||||||
if (stats.indexHits / stats.totalQueries < 0.8) {
|
|
||||||
console.warn('查询命中率较低,考虑优化查询策略');
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
EntityManager 提供了:
|
EntityManager 提供了强大的实体管理功能:
|
||||||
|
|
||||||
- **统一接口**: 所有实体操作通过一个管理器完成
|
- **创建管理**:`createEntity()`, `destroyEntity()`
|
||||||
- **自动优化**: 内置三个性能优化系统
|
- **查询功能**:`getEntitiesWithComponent()`, `getEntitiesByTag()`, `query()`
|
||||||
- **灵活查询**: 从简单的ID查找到复杂的条件查询
|
- **实体查找**:`getEntity()`, `getEntityByName()`
|
||||||
- **性能监控**: 完整的统计和诊断信息
|
- **统计信息**:`entityCount`, `activeEntityCount`
|
||||||
- **批量操作**: 高效的批量处理能力
|
|
||||||
|
|
||||||
通过合理使用EntityManager,您可以构建高性能、可维护的ECS游戏系统。
|
通过合理使用这些API,可以构建高性能的游戏系统。记住要及时清理无用实体,缓存频繁查询的结果,并使用合适的查询方法来优化性能。
|
||||||
@@ -6,15 +6,18 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
ecs-framework/
|
ecs-framework/
|
||||||
├── source/
|
├── src/ # 源代码
|
||||||
│ ├── src/ # 源代码
|
│ ├── ECS/ # ECS核心系统
|
||||||
│ │ ├── ECS/ # ECS核心系统
|
│ │ ├── Core/ # 核心管理器
|
||||||
│ │ ├── Types/ # 类型定义
|
│ │ ├── Systems/ # 系统类型
|
||||||
│ │ ├── Utils/ # 工具类
|
│ │ ├── Utils/ # ECS工具类
|
||||||
│ │ └── Testing/ # 测试文件
|
│ │ └── Components/# 组件类型
|
||||||
│ ├── scripts/ # 构建脚本
|
│ ├── Types/ # TypeScript接口定义(精简版)
|
||||||
│ └── tsconfig.json # TypeScript配置
|
│ └── Utils/ # 通用工具类
|
||||||
└── docs/ # 文档
|
├── docs/ # 文档
|
||||||
|
├── scripts/ # 构建脚本
|
||||||
|
├── bin/ # 编译输出
|
||||||
|
└── dist/ # 发布版本
|
||||||
```
|
```
|
||||||
|
|
||||||
## 安装和使用
|
## 安装和使用
|
||||||
@@ -31,14 +34,17 @@ npm install @esengine/ecs-framework
|
|||||||
# 克隆项目
|
# 克隆项目
|
||||||
git clone https://github.com/esengine/ecs-framework.git
|
git clone https://github.com/esengine/ecs-framework.git
|
||||||
|
|
||||||
# 进入源码目录
|
# 进入项目目录
|
||||||
cd ecs-framework/source
|
cd ecs-framework
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# 编译TypeScript
|
# 编译TypeScript
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
# 或者使用监听模式开发
|
||||||
|
npm run build:watch
|
||||||
```
|
```
|
||||||
|
|
||||||
## 基础设置
|
## 基础设置
|
||||||
@@ -80,22 +86,7 @@ class GameManager {
|
|||||||
Core.scene = this.scene;
|
Core.scene = this.scene;
|
||||||
|
|
||||||
// 初始化实体管理器
|
// 初始化实体管理器
|
||||||
this.entityManager = new EntityManager(this.scene);
|
this.entityManager = new EntityManager();
|
||||||
|
|
||||||
// 初始化性能优化
|
|
||||||
this.setupPerformanceOptimizations();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupPerformanceOptimizations() {
|
|
||||||
// 启用组件索引(自动优化查询性能)
|
|
||||||
// EntityManager内部已自动启用
|
|
||||||
|
|
||||||
// 可选:手动配置优化系统
|
|
||||||
const componentIndex = this.entityManager.getComponentIndex();
|
|
||||||
const archetypeSystem = this.entityManager.getArchetypeSystem();
|
|
||||||
const dirtyTracking = this.entityManager.getDirtyTrackingSystem();
|
|
||||||
|
|
||||||
// 优化系统会自动工作,通常无需手动配置
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(deltaTime: number): void {
|
public update(deltaTime: number): void {
|
||||||
@@ -216,7 +207,7 @@ class HealthComponent extends Component {
|
|||||||
|
|
||||||
EntityManager 是框架的核心功能,提供统一的实体管理和高性能查询接口。
|
EntityManager 是框架的核心功能,提供统一的实体管理和高性能查询接口。
|
||||||
|
|
||||||
### 1. 基础用法
|
### 基础实体操作
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取EntityManager实例(在GameManager中已创建)
|
// 获取EntityManager实例(在GameManager中已创建)
|
||||||
@@ -224,37 +215,22 @@ const entityManager = gameManager.getEntityManager();
|
|||||||
|
|
||||||
// 创建单个实体
|
// 创建单个实体
|
||||||
const player = entityManager.createEntity("Player");
|
const player = entityManager.createEntity("Player");
|
||||||
player.addComponent(new PositionComponent(100, 100));
|
|
||||||
player.addComponent(new VelocityComponent(50, 0));
|
|
||||||
|
|
||||||
// 批量创建实体
|
// 批量创建实体(使用Scene方法)
|
||||||
const enemies = entityManager.createEntities(50, "Enemy");
|
const enemies = scene.createEntities(50, "Enemy");
|
||||||
enemies.forEach((enemy, index) => {
|
|
||||||
enemy.addComponent(new PositionComponent(
|
|
||||||
Math.random() * 800,
|
|
||||||
Math.random() * 600
|
|
||||||
));
|
|
||||||
enemy.addComponent(new HealthComponent(30));
|
|
||||||
enemy.tag = "enemy";
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 高性能查询
|
// 查询操作
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 流式查询API - 支持复杂查询条件
|
|
||||||
const movingEntities = entityManager
|
const movingEntities = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll([PositionComponent, VelocityComponent])
|
.withAll(PositionComponent, VelocityComponent)
|
||||||
.withoutTag("dead")
|
.withNone(HealthComponent)
|
||||||
.active(true)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// 快速组件查询(使用O(1)索引)
|
// 组件查询
|
||||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||||
|
|
||||||
// 标签查询
|
// 标签查询
|
||||||
const allEnemies = entityManager.getEntitiesByTag("enemy");
|
const allEnemies = entityManager.getEntitiesByTag(2);
|
||||||
|
|
||||||
// 名称查询
|
// 名称查询
|
||||||
const specificEnemy = entityManager.getEntityByName("BossEnemy");
|
const specificEnemy = entityManager.getEntityByName("BossEnemy");
|
||||||
@@ -262,59 +238,41 @@ const specificEnemy = entityManager.getEntityByName("BossEnemy");
|
|||||||
// 复合查询
|
// 复合查询
|
||||||
const livingEnemies = entityManager
|
const livingEnemies = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll([PositionComponent, HealthComponent])
|
.withAll(HealthComponent)
|
||||||
.withTag("enemy")
|
.withTag(2)
|
||||||
.withoutTag("dead")
|
|
||||||
.where(entity => {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
return health && health.currentHealth > 0;
|
|
||||||
})
|
|
||||||
.execute();
|
.execute();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 批量操作
|
### 实体遍历
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 批量处理实体
|
// 遍历所有实体
|
||||||
entityManager.forEachEntity(entity => {
|
const allEntities = entityManager.getAllEntities();
|
||||||
// 处理所有实体
|
allEntities.forEach(entity => {
|
||||||
if (entity.tag === "bullet" && entity.position.y < 0) {
|
console.log(`实体: ${entity.name}, ID: ${entity.id}`);
|
||||||
entity.destroy();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 批量处理特定组件的实体
|
// 遍历特定组件的实体
|
||||||
entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => {
|
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||||
if (health.currentHealth <= 0) {
|
healthEntities.forEach(entity => {
|
||||||
entity.addTag("dead");
|
const health = entity.getComponent(HealthComponent);
|
||||||
entity.enabled = false;
|
console.log(`${entity.name} 生命值: ${health.currentHealth}/${health.maxHealth}`);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取统计信息
|
|
||||||
const stats = entityManager.getStatistics();
|
|
||||||
console.log(`总实体数: ${stats.entityCount}`);
|
|
||||||
console.log(`索引命中率: ${stats.indexHits}/${stats.totalQueries}`);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 性能优化功能
|
### 性能监控
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取性能优化系统
|
// 获取场景统计
|
||||||
const componentIndex = entityManager.getComponentIndex();
|
const sceneStats = scene.getStats();
|
||||||
const archetypeSystem = entityManager.getArchetypeSystem();
|
console.log('实体数量:', sceneStats.entityCount);
|
||||||
const dirtyTracking = entityManager.getDirtyTrackingSystem();
|
console.log('系统数量:', sceneStats.processorCount);
|
||||||
|
|
||||||
// 查看性能统计
|
// 获取查询系统统计
|
||||||
console.log('组件索引统计:', componentIndex.getPerformanceStats());
|
const queryStats = scene.querySystem.getStats();
|
||||||
console.log('Archetype统计:', archetypeSystem.getStatistics());
|
console.log('查询统计:', queryStats);
|
||||||
console.log('脏标记统计:', dirtyTracking.getPerformanceStats());
|
|
||||||
|
|
||||||
// 手动优化(通常自动进行)
|
|
||||||
entityManager.optimize();
|
|
||||||
|
|
||||||
// 内存清理
|
|
||||||
entityManager.cleanup();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 创建系统
|
## 创建系统
|
||||||
@@ -322,45 +280,40 @@ entityManager.cleanup();
|
|||||||
系统处理具有特定组件的实体集合,实现游戏逻辑。
|
系统处理具有特定组件的实体集合,实现游戏逻辑。
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EntitySystem, Entity } from '@esengine/ecs-framework';
|
import { EntitySystem, Entity, Matcher, EntityManager } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
class MovementSystem extends EntitySystem {
|
class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||||
|
}
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
protected process(entities: Entity[]): void {
|
||||||
// 使用EntityManager进行高效查询
|
// 使用Scene的查询系统进行高效查询
|
||||||
const entityManager = new EntityManager(this.scene);
|
const movingEntities = this.scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||||
const movingEntities = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll([PositionComponent, VelocityComponent])
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
movingEntities.forEach(entity => {
|
movingEntities.entities.forEach(entity => {
|
||||||
const position = entity.getComponent(PositionComponent);
|
const position = entity.getComponent(PositionComponent);
|
||||||
const velocity = entity.getComponent(VelocityComponent);
|
const velocity = entity.getComponent(VelocityComponent);
|
||||||
|
|
||||||
if (position && velocity) {
|
if (position && velocity) {
|
||||||
// 更新位置
|
position.x += velocity.dx * Time.deltaTime;
|
||||||
position.x += velocity.x * 0.016; // 假设60FPS
|
position.y += velocity.dy * Time.deltaTime;
|
||||||
position.y += velocity.y * 0.016;
|
|
||||||
|
|
||||||
// 边界检查
|
|
||||||
if (position.x < 0 || position.x > 800) {
|
|
||||||
velocity.x = -velocity.x;
|
|
||||||
}
|
|
||||||
if (position.y < 0 || position.y > 600) {
|
|
||||||
velocity.y = -velocity.y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HealthSystem extends EntitySystem {
|
class HealthSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(HealthComponent));
|
||||||
|
}
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
protected process(entities: Entity[]): void {
|
||||||
const entityManager = new EntityManager(this.scene);
|
const healthEntities = this.scene.querySystem.queryAll(HealthComponent);
|
||||||
|
|
||||||
// 查找所有有生命值的实体
|
healthEntities.entities.forEach(entity => {
|
||||||
entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => {
|
const health = entity.getComponent(HealthComponent);
|
||||||
if (health.isDead()) {
|
if (health && health.currentHealth <= 0) {
|
||||||
entity.destroy();
|
entity.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -392,15 +345,14 @@ Core.emitter.removeObserver(CoreEvents.frameUpdated, this.onFrameUpdate);
|
|||||||
### 性能监控
|
### 性能监控
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取EntityManager性能统计
|
// 获取场景性能统计
|
||||||
const stats = entityManager.getStatistics();
|
const sceneStats = scene.getStats();
|
||||||
console.log(`总实体数: ${stats.entityCount}`);
|
console.log(`总实体数: ${sceneStats.entityCount}`);
|
||||||
console.log(`索引命中率: ${stats.indexHits}/${stats.totalQueries}`);
|
console.log(`系统数量: ${sceneStats.processorCount}`);
|
||||||
|
|
||||||
// 获取各优化系统的统计
|
// 获取查询系统统计
|
||||||
console.log('组件索引:', entityManager.getComponentIndex().getPerformanceStats());
|
const queryStats = scene.querySystem.getStats();
|
||||||
console.log('Archetype:', entityManager.getArchetypeSystem().getStatistics());
|
console.log('查询统计:', queryStats);
|
||||||
console.log('脏标记:', entityManager.getDirtyTrackingSystem().getPerformanceStats());
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 简单示例
|
## 简单示例
|
||||||
@@ -429,7 +381,7 @@ class SimpleGame {
|
|||||||
this.scene.name = "GameScene";
|
this.scene.name = "GameScene";
|
||||||
Core.scene = this.scene;
|
Core.scene = this.scene;
|
||||||
|
|
||||||
this.entityManager = new EntityManager(this.scene);
|
this.entityManager = new EntityManager();
|
||||||
this.setupSystems();
|
this.setupSystems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +409,7 @@ class SimpleGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createEnemies(count: number): Entity[] {
|
private createEnemies(count: number): Entity[] {
|
||||||
const enemies = this.entityManager.createEntities(count, "Enemy");
|
const enemies = this.scene.createEntities(count, "Enemy");
|
||||||
|
|
||||||
enemies.forEach((enemy, index) => {
|
enemies.forEach((enemy, index) => {
|
||||||
enemy.addComponent(new PositionComponent(
|
enemy.addComponent(new PositionComponent(
|
||||||
@@ -493,7 +445,7 @@ game.start();
|
|||||||
## 性能优化建议
|
## 性能优化建议
|
||||||
|
|
||||||
### 1. 大规模实体处理
|
### 1. 大规模实体处理
|
||||||
- 使用 `EntityManager.createEntities()` 批量创建实体
|
- 使用 `scene.createEntities()` 批量创建实体
|
||||||
- 利用组件索引系统进行高效查询
|
- 利用组件索引系统进行高效查询
|
||||||
- 启用Archetype系统减少查询遍历
|
- 启用Archetype系统减少查询遍历
|
||||||
|
|
||||||
@@ -503,7 +455,7 @@ game.start();
|
|||||||
- 利用脏标记系统避免不必要的更新
|
- 利用脏标记系统避免不必要的更新
|
||||||
|
|
||||||
### 3. 性能监控
|
### 3. 性能监控
|
||||||
- 定期检查 `EntityManager.getStatistics()` 获取性能数据
|
- 定期检查 `scene.getStats()` 获取性能数据
|
||||||
- 监控组件索引命中率
|
- 监控组件索引命中率
|
||||||
- 使用框架提供的性能统计功能
|
- 使用框架提供的性能统计功能
|
||||||
|
|
||||||
|
|||||||
@@ -1,497 +1,425 @@
|
|||||||
# 性能优化指南
|
# 性能优化指南
|
||||||
|
|
||||||
ECS Framework 提供了多层性能优化系统,确保在各种规模的游戏中都能提供卓越的性能表现。
|
本文档介绍ECS框架的性能优化技术和最佳实践。
|
||||||
|
|
||||||
## 性能优化架构
|
## 目录
|
||||||
|
|
||||||
### 三大核心优化系统
|
1. [查询系统优化](#查询系统优化)
|
||||||
|
2. [实体管理优化](#实体管理优化)
|
||||||
|
3. [组件设计优化](#组件设计优化)
|
||||||
|
4. [系统设计优化](#系统设计优化)
|
||||||
|
5. [内存管理](#内存管理)
|
||||||
|
6. [性能监控](#性能监控)
|
||||||
|
|
||||||
1. **组件索引系统 (ComponentIndex)** - 提供 O(1) 组件查询性能
|
## 查询系统优化
|
||||||
2. **Archetype系统** - 按组件组合分组实体,减少查询遍历
|
|
||||||
3. **脏标记系统 (DirtyTracking)** - 细粒度变更追踪,避免不必要更新
|
|
||||||
|
|
||||||
这三个系统协同工作,为不同场景提供最优的性能表现。
|
### 使用高效的查询方法
|
||||||
|
|
||||||
## 性能基准
|
|
||||||
|
|
||||||
### 核心操作性能
|
|
||||||
|
|
||||||
```
|
|
||||||
实体创建: 640,000+ 个/秒
|
|
||||||
组件查询: O(1) 复杂度(使用索引)
|
|
||||||
内存优化: 30-50% 减少分配
|
|
||||||
批量操作: 显著提升处理效率
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查询性能对比
|
|
||||||
|
|
||||||
| 查询类型 | 传统方式 | 使用索引 | 性能提升 |
|
|
||||||
|----------|----------|----------|----------|
|
|
||||||
| 单组件查询 | O(n) | O(1) | 1000x+ |
|
|
||||||
| 多组件查询 | O(n*m) | O(k) | 100x+ |
|
|
||||||
| 标签查询 | O(n) | O(1) | 1000x+ |
|
|
||||||
| 复合查询 | O(n*m*k) | O(min(k1,k2)) | 500x+ |
|
|
||||||
|
|
||||||
*n=实体数量, m=组件种类, k=匹配实体数量*
|
|
||||||
|
|
||||||
## 组件索引系统
|
|
||||||
|
|
||||||
### 索引类型选择
|
|
||||||
|
|
||||||
框架提供两种索引实现:
|
|
||||||
|
|
||||||
#### 哈希索引 (HashComponentIndex)
|
|
||||||
- **适用场景**: 通用查询,平衡的读写性能
|
|
||||||
- **优势**: O(1) 查询,较低内存开销
|
|
||||||
- **缺点**: 哈希冲突时性能下降
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 自动选择最优索引类型
|
// ✅ 推荐:使用标签查询(快速)
|
||||||
const componentIndex = entityManager.getComponentIndex();
|
const enemies = entityManager.getEntitiesByTag(2);
|
||||||
|
|
||||||
// 手动配置哈希索引
|
// ✅ 推荐:使用组件查询
|
||||||
componentIndex.setIndexType(HealthComponent, 'hash');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 位图索引 (BitmapComponentIndex)
|
|
||||||
- **适用场景**: 大规模实体,频繁的组合查询
|
|
||||||
- **优势**: 超快的 AND/OR 操作,空间压缩
|
|
||||||
- **缺点**: 更新成本较高,内存开销随实体数量增长
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 配置位图索引用于大规模查询
|
|
||||||
componentIndex.setIndexType(PositionComponent, 'bitmap');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 智能索引管理
|
|
||||||
|
|
||||||
ComponentIndexManager 会根据使用模式自动优化:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取索引性能统计
|
|
||||||
const stats = componentIndex.getPerformanceStats();
|
|
||||||
console.log('索引性能:', {
|
|
||||||
queriesPerSecond: stats.queriesPerSecond,
|
|
||||||
hitRate: stats.hitRate,
|
|
||||||
indexType: stats.recommendedType
|
|
||||||
});
|
|
||||||
|
|
||||||
// 自动优化索引类型
|
|
||||||
componentIndex.optimize(); // 根据使用模式切换索引类型
|
|
||||||
```
|
|
||||||
|
|
||||||
## Archetype系统优化
|
|
||||||
|
|
||||||
### 原型分组策略
|
|
||||||
|
|
||||||
Archetype系统将实体按组件组合分组,实现快速批量操作:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取Archetype统计
|
|
||||||
const archetypeSystem = entityManager.getArchetypeSystem();
|
|
||||||
const stats = archetypeSystem.getStatistics();
|
|
||||||
|
|
||||||
console.log('Archetype优化:', {
|
|
||||||
totalArchetypes: stats.totalArchetypes, // 原型数量
|
|
||||||
avgEntitiesPerArchetype: stats.averageEntitiesPerArchetype,
|
|
||||||
queryCacheHits: stats.queryCacheHits // 缓存命中次数
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查询缓存机制
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 启用查询缓存(默认开启)
|
|
||||||
archetypeSystem.enableQueryCache(true);
|
|
||||||
|
|
||||||
// 缓存大小限制(避免内存泄漏)
|
|
||||||
archetypeSystem.setMaxCacheSize(1000);
|
|
||||||
|
|
||||||
// 清理过期缓存
|
|
||||||
archetypeSystem.cleanCache();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 最佳实践
|
|
||||||
|
|
||||||
1. **组件设计**: 避免创建过多单独的原型
|
|
||||||
2. **批量操作**: 利用原型批量处理相同组件组合的实体
|
|
||||||
3. **缓存管理**: 定期清理查询缓存
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ 好的设计:复用组件组合
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
process() {
|
|
||||||
// 一次查询处理所有移动实体
|
|
||||||
const movingEntities = this.entityManager
|
|
||||||
.query()
|
|
||||||
.withAll([PositionComponent, VelocityComponent])
|
|
||||||
.execute(); // 利用Archetype快速获取
|
|
||||||
|
|
||||||
// 批量处理
|
|
||||||
movingEntities.forEach(entity => {
|
|
||||||
// 更新逻辑
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ 避免:频繁查询不同组合
|
|
||||||
class BadSystem extends EntitySystem {
|
|
||||||
process() {
|
|
||||||
// 多次小查询,无法充分利用Archetype
|
|
||||||
const players = this.queryPlayers();
|
|
||||||
const enemies = this.queryEnemies();
|
|
||||||
const bullets = this.queryBullets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 脏标记系统优化
|
|
||||||
|
|
||||||
### 脏标记类型
|
|
||||||
|
|
||||||
系统提供细粒度的脏标记追踪:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
enum DirtyType {
|
|
||||||
COMPONENT_ADDED, // 组件添加
|
|
||||||
COMPONENT_REMOVED, // 组件移除
|
|
||||||
COMPONENT_MODIFIED, // 组件修改
|
|
||||||
ENTITY_ENABLED, // 实体启用
|
|
||||||
ENTITY_DISABLED, // 实体禁用
|
|
||||||
TAG_ADDED, // 标签添加
|
|
||||||
TAG_REMOVED // 标签移除
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量处理配置
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const dirtyTracking = entityManager.getDirtyTrackingSystem();
|
|
||||||
|
|
||||||
// 配置批量处理参数
|
|
||||||
dirtyTracking.configure({
|
|
||||||
batchSize: 100, // 每批处理100个脏标记
|
|
||||||
timeSliceMs: 16, // 每帧最多处理16ms
|
|
||||||
processingInterval: 1 // 每帧处理一次
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听脏标记事件
|
|
||||||
dirtyTracking.addListener(DirtyType.COMPONENT_MODIFIED, (entity, component) => {
|
|
||||||
// 响应组件修改
|
|
||||||
this.invalidateRenderCache(entity);
|
|
||||||
}, { priority: 10 });
|
|
||||||
```
|
|
||||||
|
|
||||||
### 性能监控
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const dirtyStats = dirtyTracking.getPerformanceStats();
|
|
||||||
console.log('脏标记性能:', {
|
|
||||||
totalMarks: dirtyStats.totalMarks,
|
|
||||||
batchesProcessed: dirtyStats.batchesProcessed,
|
|
||||||
averageBatchTime: dirtyStats.averageBatchTime,
|
|
||||||
queueSize: dirtyStats.currentQueueSize
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 查询优化策略
|
|
||||||
|
|
||||||
### 查询层次选择
|
|
||||||
|
|
||||||
根据查询复杂度选择最优方法:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 1. 简单查询:直接使用索引
|
|
||||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||||
|
|
||||||
// 2. 双组件查询:使用Archetype
|
// ✅ 推荐:使用Scene的查询系统
|
||||||
const movingEntities = entityManager.getEntitiesWithComponents([
|
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||||
PositionComponent,
|
|
||||||
VelocityComponent
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 3. 复杂查询:组合使用
|
// ⚠️ 谨慎:自定义条件查询(较慢)
|
||||||
const combatants = entityManager
|
const nearbyEnemies = entityManager
|
||||||
.query()
|
.query()
|
||||||
.withAll([PositionComponent, HealthComponent]) // Archetype预筛选
|
.withAll(PositionComponent)
|
||||||
.withTag("combat") // 索引过滤
|
.where(entity => {
|
||||||
.where(entity => { // 自定义精确过滤
|
const pos = entity.getComponent(PositionComponent);
|
||||||
const health = entity.getComponent(HealthComponent);
|
return pos && Math.abs(pos.x - playerX) < 100;
|
||||||
return health.currentHealth > health.maxHealth * 0.3;
|
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 查询缓存策略
|
### 查询结果缓存
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class CombatSystem extends EntitySystem {
|
class OptimizedCombatSystem extends EntitySystem {
|
||||||
private cachedEnemies: Entity[] = [];
|
private cachedEnemies: Entity[] = [];
|
||||||
private lastEnemyCacheUpdate = 0;
|
private lastCacheUpdate = 0;
|
||||||
|
private cacheInterval = 5; // 每5帧更新一次
|
||||||
|
|
||||||
process() {
|
protected process(entities: Entity[]): void {
|
||||||
const currentTime = performance.now();
|
// 缓存查询结果
|
||||||
|
if (Time.frameCount - this.lastCacheUpdate >= this.cacheInterval) {
|
||||||
// 每200ms更新一次敌人缓存
|
this.cachedEnemies = this.entityManager.getEntitiesByTag(2);
|
||||||
if (currentTime - this.lastEnemyCacheUpdate > 200) {
|
this.lastCacheUpdate = Time.frameCount;
|
||||||
this.cachedEnemies = this.entityManager
|
|
||||||
.getEntitiesByTag("enemy");
|
|
||||||
this.lastEnemyCacheUpdate = currentTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用缓存的结果
|
// 使用缓存的结果
|
||||||
this.processCombat(this.cachedEnemies);
|
this.cachedEnemies.forEach(enemy => {
|
||||||
|
this.processEnemy(enemy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private processEnemy(enemy: Entity): void {
|
||||||
|
// 处理敌人逻辑
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 内存优化
|
## 实体管理优化
|
||||||
|
|
||||||
### 内存使用监控
|
### 批量创建实体
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 获取各系统内存使用情况
|
// ✅ 推荐:使用Scene的批量创建
|
||||||
const memoryStats = entityManager.getMemoryUsage();
|
function createEnemyWave(count: number): Entity[] {
|
||||||
console.log('内存使用情况:', {
|
const enemies = scene.createEntities(count, "Enemy");
|
||||||
entityIndex: memoryStats.entityIndex, // 实体索引
|
|
||||||
componentIndex: memoryStats.componentIndex, // 组件索引
|
|
||||||
archetype: memoryStats.archetype, // 原型系统
|
|
||||||
dirtyTracking: memoryStats.dirtyTracking, // 脏标记
|
|
||||||
total: memoryStats.total
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 内存清理策略
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 定期内存清理
|
|
||||||
setInterval(() => {
|
|
||||||
entityManager.cleanup(); // 清理无效引用
|
|
||||||
entityManager.compact(); // 压缩数据结构
|
|
||||||
}, 30000); // 每30秒清理一次
|
|
||||||
|
|
||||||
// 游戏场景切换时的深度清理
|
|
||||||
function switchScene() {
|
|
||||||
entityManager.destroyAllEntities();
|
|
||||||
entityManager.cleanup();
|
|
||||||
entityManager.compact();
|
|
||||||
|
|
||||||
// 重置优化系统
|
// 批量配置组件
|
||||||
entityManager.getComponentIndex().reset();
|
enemies.forEach((enemy, index) => {
|
||||||
entityManager.getArchetypeSystem().clearCache();
|
enemy.addComponent(new PositionComponent(
|
||||||
entityManager.getDirtyTrackingSystem().clear();
|
Math.random() * 800,
|
||||||
|
Math.random() * 600
|
||||||
|
));
|
||||||
|
enemy.addComponent(new HealthComponent(100));
|
||||||
|
enemy.addComponent(new AIComponent());
|
||||||
|
enemy.tag = 2; // 敌人标签
|
||||||
|
});
|
||||||
|
|
||||||
|
return enemies;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 避免:循环单独创建
|
||||||
|
function createEnemyWaveSlow(count: number): Entity[] {
|
||||||
|
const enemies: Entity[] = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const enemy = entityManager.createEntity(`Enemy_${i}`);
|
||||||
|
enemy.addComponent(new PositionComponent());
|
||||||
|
enemy.addComponent(new HealthComponent());
|
||||||
|
enemies.push(enemy);
|
||||||
|
}
|
||||||
|
return enemies;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 实战优化案例
|
### 实体复用策略
|
||||||
|
|
||||||
### 大规模射击游戏优化
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class BulletSystem extends EntitySystem {
|
// 使用简单的实体复用策略
|
||||||
private bulletPool: Entity[] = [];
|
class EntityReusableManager {
|
||||||
private maxBullets = 1000;
|
private inactiveEntities: Entity[] = [];
|
||||||
|
private scene: Scene;
|
||||||
|
|
||||||
constructor(entityManager: EntityManager) {
|
constructor(scene: Scene) {
|
||||||
super();
|
this.scene = scene;
|
||||||
this.prewarmBulletPool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private prewarmBulletPool() {
|
// 预创建实体
|
||||||
// 预创建子弹池
|
preCreateEntities(count: number, entityName: string): void {
|
||||||
this.bulletPool = this.entityManager.createEntities(
|
const entities = this.scene.createEntities(count, entityName);
|
||||||
this.maxBullets,
|
entities.forEach(entity => {
|
||||||
"Bullet"
|
entity.enabled = false; // 禁用但不销毁
|
||||||
|
this.inactiveEntities.push(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取可复用实体
|
||||||
|
getReusableEntity(): Entity | null {
|
||||||
|
if (this.inactiveEntities.length > 0) {
|
||||||
|
const entity = this.inactiveEntities.pop()!;
|
||||||
|
entity.enabled = true;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回收实体
|
||||||
|
recycleEntity(entity: Entity): void {
|
||||||
|
entity.enabled = false;
|
||||||
|
entity.removeAllComponents();
|
||||||
|
this.inactiveEntities.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件设计优化
|
||||||
|
|
||||||
|
### 数据局部性优化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 推荐:紧凑的数据结构
|
||||||
|
class OptimizedPositionComponent extends Component {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public z: number = 0;
|
||||||
|
|
||||||
|
// 避免对象分配
|
||||||
|
public setPosition(x: number, y: number, z: number = 0): void {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 避免:过多对象分配
|
||||||
|
class SlowPositionComponent extends Component {
|
||||||
|
public position: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 };
|
||||||
|
public velocity: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 };
|
||||||
|
public acceleration: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 组件池化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 使用框架内置的组件池
|
||||||
|
ComponentPoolManager.getInstance().registerPool(
|
||||||
|
'BulletComponent',
|
||||||
|
() => new BulletComponent(),
|
||||||
|
(bullet) => bullet.reset(),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
|
// 获取组件
|
||||||
|
const bullet = ComponentPoolManager.getInstance().acquireComponent('BulletComponent');
|
||||||
|
if (bullet) {
|
||||||
|
entity.addComponent(bullet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 释放组件
|
||||||
|
ComponentPoolManager.getInstance().releaseComponent('BulletComponent', bullet);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统设计优化
|
||||||
|
|
||||||
|
### 系统更新顺序优化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class OptimizedGameManager {
|
||||||
|
private scene: Scene;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene = new Scene();
|
||||||
|
this.setupSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSystems(): void {
|
||||||
|
// 按依赖关系排序系统
|
||||||
|
this.scene.addEntityProcessor(new InputSystem()).updateOrder = 10;
|
||||||
|
this.scene.addEntityProcessor(new MovementSystem()).updateOrder = 20;
|
||||||
|
this.scene.addEntityProcessor(new CollisionSystem()).updateOrder = 30;
|
||||||
|
this.scene.addEntityProcessor(new RenderSystem()).updateOrder = 40;
|
||||||
|
this.scene.addEntityProcessor(new CleanupSystem()).updateOrder = 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 时间分片处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TimeSlicedAISystem extends EntitySystem {
|
||||||
|
private aiEntities: Entity[] = [];
|
||||||
|
private currentIndex = 0;
|
||||||
|
private entitiesPerFrame = 10;
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
// 获取所有AI实体
|
||||||
|
if (this.aiEntities.length === 0) {
|
||||||
|
this.aiEntities = this.entityManager.getEntitiesByTag(3); // AI标签
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每帧只处理部分实体
|
||||||
|
const endIndex = Math.min(
|
||||||
|
this.currentIndex + this.entitiesPerFrame,
|
||||||
|
this.aiEntities.length
|
||||||
);
|
);
|
||||||
|
|
||||||
// 初始化为非激活状态
|
for (let i = this.currentIndex; i < endIndex; i++) {
|
||||||
this.bulletPool.forEach(bullet => {
|
this.processAI(this.aiEntities[i]);
|
||||||
bullet.enabled = false;
|
}
|
||||||
|
|
||||||
|
// 更新索引
|
||||||
|
this.currentIndex = endIndex;
|
||||||
|
if (this.currentIndex >= this.aiEntities.length) {
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.aiEntities = []; // 重新获取实体列表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processAI(entity: Entity): void {
|
||||||
|
// AI处理逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内存管理
|
||||||
|
|
||||||
|
### 及时清理无用实体
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class CleanupSystem extends EntitySystem {
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
// 清理超出边界的子弹
|
||||||
|
const bullets = this.entityManager.getEntitiesByTag(4); // 子弹标签
|
||||||
|
bullets.forEach(bullet => {
|
||||||
|
const pos = bullet.getComponent(PositionComponent);
|
||||||
|
if (pos && this.isOutOfBounds(pos)) {
|
||||||
|
this.entityManager.destroyEntity(bullet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理死亡的实体
|
||||||
|
const deadEntities = this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(HealthComponent)
|
||||||
|
.where(entity => {
|
||||||
|
const health = entity.getComponent(HealthComponent);
|
||||||
|
return health && health.currentHealth <= 0;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
deadEntities.forEach(entity => {
|
||||||
|
this.entityManager.destroyEntity(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private isOutOfBounds(pos: PositionComponent): boolean {
|
||||||
|
return pos.x < -100 || pos.x > 900 || pos.y < -100 || pos.y > 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实体复用管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameEntityManager {
|
||||||
|
private bulletManager: EntityReusableManager;
|
||||||
|
private effectManager: EntityReusableManager;
|
||||||
|
|
||||||
|
constructor(scene: Scene) {
|
||||||
|
this.bulletManager = new EntityReusableManager(scene);
|
||||||
|
this.effectManager = new EntityReusableManager(scene);
|
||||||
|
|
||||||
|
// 预创建实体
|
||||||
|
this.bulletManager.preCreateEntities(100, "Bullet");
|
||||||
|
this.effectManager.preCreateEntities(50, "Effect");
|
||||||
|
}
|
||||||
|
|
||||||
|
createBullet(): Entity | null {
|
||||||
|
const bullet = this.bulletManager.getReusableEntity();
|
||||||
|
if (bullet) {
|
||||||
|
bullet.addComponent(new BulletComponent());
|
||||||
bullet.addComponent(new PositionComponent());
|
bullet.addComponent(new PositionComponent());
|
||||||
bullet.addComponent(new VelocityComponent());
|
bullet.addComponent(new VelocityComponent());
|
||||||
bullet.addComponent(new BulletComponent());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public spawnBullet(x: number, y: number, vx: number, vy: number): Entity | null {
|
|
||||||
// 从池中获取非激活子弹(使用索引快速查询)
|
|
||||||
const availableBullet = this.entityManager
|
|
||||||
.query()
|
|
||||||
.withAll([BulletComponent])
|
|
||||||
.active(false)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (availableBullet) {
|
|
||||||
// 重用现有子弹
|
|
||||||
const pos = availableBullet.getComponent(PositionComponent);
|
|
||||||
const vel = availableBullet.getComponent(VelocityComponent);
|
|
||||||
|
|
||||||
pos.x = x; pos.y = y;
|
|
||||||
vel.x = vx; vel.y = vy;
|
|
||||||
availableBullet.enabled = true;
|
|
||||||
|
|
||||||
return availableBullet;
|
|
||||||
}
|
}
|
||||||
|
return bullet;
|
||||||
return null; // 池已满
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process() {
|
destroyBullet(bullet: Entity): void {
|
||||||
// 批量处理所有激活的子弹
|
this.bulletManager.recycleEntity(bullet);
|
||||||
this.entityManager.forEachEntityWithComponent(
|
|
||||||
BulletComponent,
|
|
||||||
(entity, bullet) => {
|
|
||||||
if (!entity.enabled) return;
|
|
||||||
|
|
||||||
// 更新位置
|
|
||||||
const pos = entity.getComponent(PositionComponent);
|
|
||||||
const vel = entity.getComponent(VelocityComponent);
|
|
||||||
|
|
||||||
pos.x += vel.x * Time.deltaTime;
|
|
||||||
pos.y += vel.y * Time.deltaTime;
|
|
||||||
|
|
||||||
// 边界检查,回收到池中
|
|
||||||
if (pos.x < 0 || pos.x > 800 || pos.y < 0 || pos.y > 600) {
|
|
||||||
entity.enabled = false; // 回收而不是销毁
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### AI系统性能优化
|
## 性能监控
|
||||||
|
|
||||||
|
### 基础性能统计
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class AISystem extends EntitySystem {
|
class PerformanceMonitor {
|
||||||
private spatialGrid: SpatialGrid;
|
private scene: Scene;
|
||||||
private updateFrequency = 60; // 60Hz更新频率
|
private entityManager: EntityManager;
|
||||||
private lastUpdate = 0;
|
|
||||||
|
|
||||||
process() {
|
constructor(scene: Scene, entityManager: EntityManager) {
|
||||||
const currentTime = performance.now();
|
this.scene = scene;
|
||||||
|
this.entityManager = entityManager;
|
||||||
// 控制更新频率
|
|
||||||
if (currentTime - this.lastUpdate < 1000 / this.updateFrequency) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用空间分区优化邻居查询
|
|
||||||
const aiEntities = this.entityManager
|
|
||||||
.query()
|
|
||||||
.withAll([PositionComponent, AIComponent])
|
|
||||||
.active(true)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// 分批处理AI实体
|
|
||||||
const batchSize = 50;
|
|
||||||
for (let i = 0; i < aiEntities.length; i += batchSize) {
|
|
||||||
const batch = aiEntities.slice(i, i + batchSize);
|
|
||||||
this.processBatch(batch);
|
|
||||||
|
|
||||||
// 时间片控制,避免单帧卡顿
|
|
||||||
if (performance.now() - currentTime > 10) { // 10ms时间片
|
|
||||||
break; // 下一帧继续处理
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastUpdate = currentTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private processBatch(entities: Entity[]) {
|
public getPerformanceReport(): any {
|
||||||
entities.forEach(entity => {
|
return {
|
||||||
const pos = entity.getComponent(PositionComponent);
|
// 实体统计
|
||||||
const ai = entity.getComponent(AIComponent);
|
entities: {
|
||||||
|
total: this.entityManager.entityCount,
|
||||||
|
active: this.entityManager.activeEntityCount
|
||||||
|
},
|
||||||
|
|
||||||
// 空间查询优化邻居搜索
|
// 场景统计
|
||||||
const neighbors = this.spatialGrid.queryRadius(pos.x, pos.y, ai.sightRange);
|
scene: this.scene.getStats(),
|
||||||
|
|
||||||
// AI决策逻辑
|
// 查询系统统计
|
||||||
ai.update(neighbors);
|
querySystem: this.scene.querySystem.getStats(),
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控工具
|
|
||||||
|
|
||||||
### 实时性能仪表板
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class PerformanceDashboard {
|
|
||||||
private stats: any = {};
|
|
||||||
private updateInterval = 1000; // 1秒更新一次
|
|
||||||
|
|
||||||
constructor(private entityManager: EntityManager) {
|
|
||||||
setInterval(() => this.updateStats(), this.updateInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateStats() {
|
|
||||||
this.stats = {
|
|
||||||
// 基础统计
|
|
||||||
entities: this.entityManager.getStatistics(),
|
|
||||||
|
|
||||||
// 组件索引
|
|
||||||
componentIndex: this.entityManager.getComponentIndex().getPerformanceStats(),
|
|
||||||
|
|
||||||
// Archetype系统
|
|
||||||
archetype: this.entityManager.getArchetypeSystem().getStatistics(),
|
|
||||||
|
|
||||||
// 脏标记系统
|
|
||||||
dirtyTracking: this.entityManager.getDirtyTrackingSystem().getPerformanceStats(),
|
|
||||||
|
|
||||||
// 内存使用
|
// 内存使用
|
||||||
memory: this.entityManager.getMemoryUsage(),
|
memory: {
|
||||||
|
used: (performance as any).memory?.usedJSHeapSize || 0,
|
||||||
// 计算性能指标
|
total: (performance as any).memory?.totalJSHeapSize || 0
|
||||||
performance: this.calculatePerformanceMetrics()
|
}
|
||||||
};
|
|
||||||
|
|
||||||
this.displayStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculatePerformanceMetrics() {
|
|
||||||
const componentStats = this.stats.componentIndex;
|
|
||||||
const archetypeStats = this.stats.archetype;
|
|
||||||
|
|
||||||
return {
|
|
||||||
queryHitRate: componentStats.hitRate,
|
|
||||||
archetypeEfficiency: archetypeStats.averageEntitiesPerArchetype,
|
|
||||||
memoryEfficiency: this.stats.memory.compressionRatio,
|
|
||||||
overallPerformance: this.calculateOverallScore()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private displayStats() {
|
public logPerformance(): void {
|
||||||
console.log('=== ECS性能仪表板 ===');
|
const report = this.getPerformanceReport();
|
||||||
console.log('查询命中率:', this.stats.performance.queryHitRate.toFixed(2) + '%');
|
console.log('性能报告:', report);
|
||||||
console.log('内存使用:', (this.stats.memory.total / 1024 / 1024).toFixed(2) + 'MB');
|
|
||||||
console.log('整体性能评分:', this.stats.performance.overallPerformance.toFixed(1) + '/10');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 优化检查清单
|
### 帧率监控
|
||||||
|
|
||||||
### 开发阶段
|
```typescript
|
||||||
|
class FPSMonitor {
|
||||||
|
private frameCount = 0;
|
||||||
|
private lastTime = performance.now();
|
||||||
|
private fps = 0;
|
||||||
|
|
||||||
|
public update(): void {
|
||||||
|
this.frameCount++;
|
||||||
|
const currentTime = performance.now();
|
||||||
|
|
||||||
|
if (currentTime - this.lastTime >= 1000) {
|
||||||
|
this.fps = this.frameCount;
|
||||||
|
this.frameCount = 0;
|
||||||
|
this.lastTime = currentTime;
|
||||||
|
|
||||||
|
if (this.fps < 30) {
|
||||||
|
console.warn(`低帧率警告: ${this.fps} FPS`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFPS(): number {
|
||||||
|
return this.fps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- [ ] 使用EntityManager而不是直接操作Scene
|
## 最佳实践总结
|
||||||
- [ ] 优先使用组件查询和标签查询
|
|
||||||
- [ ] 设计合理的组件组合,避免过度碎片化
|
|
||||||
- [ ] 实现对象池机制减少频繁创建/销毁
|
|
||||||
|
|
||||||
### 运行时优化
|
### 查询优化
|
||||||
|
1. 优先使用标签查询和组件查询
|
||||||
|
2. 缓存频繁使用的查询结果
|
||||||
|
3. 避免过度使用自定义条件查询
|
||||||
|
4. 合理设置查询缓存更新频率
|
||||||
|
|
||||||
- [ ] 监控查询命中率,保持在80%以上
|
### 实体管理
|
||||||
- [ ] 控制Archetype数量,避免过度分散
|
1. 使用批量创建方法
|
||||||
- [ ] 配置适当的脏标记批量处理参数
|
2. 实现实体池化减少GC压力
|
||||||
- [ ] 定期进行内存清理和数据压缩
|
3. 及时清理无用实体
|
||||||
|
4. 合理设置实体标签
|
||||||
|
|
||||||
### 性能监控
|
### 组件设计
|
||||||
|
1. 保持组件数据紧凑
|
||||||
|
2. 避免在组件中分配大量对象
|
||||||
|
3. 使用组件池化
|
||||||
|
4. 分离数据和行为
|
||||||
|
|
||||||
- [ ] 定期检查性能统计数据
|
### 系统设计
|
||||||
- [ ] 监控内存使用趋势
|
1. 合理安排系统更新顺序
|
||||||
- [ ] 设置性能预警阈值
|
2. 对重计算任务使用时间分片
|
||||||
- [ ] 在不同设备上进行性能测试
|
3. 避免在系统中进行复杂查询
|
||||||
|
4. 缓存系统间的共享数据
|
||||||
|
|
||||||
通过系统性地应用这些优化策略,您可以构建出在各种规模下都能提供卓越性能的ECS游戏系统。
|
### 内存管理
|
||||||
|
1. 定期清理无用实体和组件
|
||||||
|
2. 使用对象池减少GC
|
||||||
|
3. 监控内存使用情况
|
||||||
|
4. 避免内存泄漏
|
||||||
|
|
||||||
|
通过遵循这些最佳实践,可以显著提升ECS框架的性能表现。
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
# ECS框架性能基准
|
|
||||||
|
|
||||||
本文档展示了ECS框架的真实性能数据和瓶颈分析。
|
|
||||||
|
|
||||||
## 🚀 快速测试
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 快速性能基准测试
|
|
||||||
npm run benchmark
|
|
||||||
|
|
||||||
# 完整性能测试
|
|
||||||
npm run test:performance
|
|
||||||
|
|
||||||
# 单元测试
|
|
||||||
npm run test:unit
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 性能基准数据
|
|
||||||
|
|
||||||
> 测试环境: Node.js, Windows 10, 现代桌面CPU
|
|
||||||
|
|
||||||
### 1. 实体创建性能
|
|
||||||
|
|
||||||
| 实体数量 | 创建时间 | 创建速度 | 每个实体耗时 | 性能等级 |
|
|
||||||
|---------|---------|---------|-------------|---------|
|
|
||||||
| 1,000 | 1.56ms | 640,697个/秒 | 0.0016ms | 🚀 极致 |
|
|
||||||
| 5,000 | 19.47ms | 256,805个/秒 | 0.0039ms | 🚀 极致 |
|
|
||||||
| 10,000 | 39.94ms | 250,345个/秒 | 0.0040ms | 🚀 极致 |
|
|
||||||
| 50,000 | 258.17ms | 193,673个/秒 | 0.0052ms | ✅ 优秀 |
|
|
||||||
| 100,000 | 463.04ms | 215,963个/秒 | 0.0046ms | ✅ 优秀 |
|
|
||||||
| 500,000 | 3,087ms | 161,990个/秒 | 0.0062ms | ✅ 优秀 |
|
|
||||||
|
|
||||||
**结论**: 🚀 实体创建性能达到极致水平,大规模创建50万实体仅需3秒
|
|
||||||
|
|
||||||
### 2. 性能瓶颈分析 (500,000个实体)
|
|
||||||
|
|
||||||
**当前瓶颈分布**:
|
|
||||||
```
|
|
||||||
实体创建: 46.3% (1,429ms)
|
|
||||||
组件添加: 53.5% (1,651ms) ← 主要瓶颈
|
|
||||||
标签分配: 0.2% (7ms)
|
|
||||||
```
|
|
||||||
|
|
||||||
**特征**: 框架实现了均衡的性能分布,各部分开销相对合理
|
|
||||||
|
|
||||||
### 3. 组件添加性能详细分析
|
|
||||||
|
|
||||||
| 组件类型 | 添加速度 | 平均耗时 | 性能等级 |
|
|
||||||
|---------|---------|---------|---------|
|
|
||||||
| PositionComponent | 596,929组件/秒 | 0.0017ms | 🚀 极致 |
|
|
||||||
| VelocityComponent | 1,186,770组件/秒 | 0.0008ms | 🚀 极致 |
|
|
||||||
| HealthComponent | 841,982组件/秒 | 0.0012ms | 🚀 极致 |
|
|
||||||
| RenderComponent | 763,351组件/秒 | 0.0013ms | 🚀 极致 |
|
|
||||||
| AIComponent | 185,964组件/秒 | 0.0054ms | ✅ 优秀 |
|
|
||||||
|
|
||||||
### 4. 优化技术性能影响
|
|
||||||
|
|
||||||
| 优化技术 | 性能提升 | 内存影响 | 适用场景 |
|
|
||||||
|---------|---------|---------|---------|
|
|
||||||
| 组件对象池 | 30-50% | 减少分配 | 频繁创建/销毁 |
|
|
||||||
| 位掩码优化器 | 20-40% | 缓存开销 | 大量查询操作 |
|
|
||||||
| 批量操作 | 显著提升 | 无明显影响 | 大规模实体创建 |
|
|
||||||
| 延迟索引更新 | 60-80% | 临时内存增加 | 批量实体操作 |
|
|
||||||
| 索引去重优化 | 避免O(n) | 轻微内存增加 | 防止重复实体 |
|
|
||||||
|
|
||||||
### 5. 查询系统性能
|
|
||||||
|
|
||||||
#### 5.1 基础查询性能
|
|
||||||
| 查询类型 | 查询速度 | 每次查询耗时 | 性能等级 |
|
|
||||||
|---------|---------|-------------|---------|
|
|
||||||
| 单组件查询 | 12,178次/秒 | 0.082ms | ✅ 优秀 |
|
|
||||||
| 多组件查询 | 9,439次/秒 | 0.106ms | ✅ 优秀 |
|
|
||||||
| 复合查询 | 7,407次/秒 | 0.135ms | ✅ 良好 |
|
|
||||||
|
|
||||||
#### 5.2 缓存查询性能
|
|
||||||
| 缓存状态 | 访问速度 | 性能特征 |
|
|
||||||
|---------|---------|---------|
|
|
||||||
| 缓存命中 | 零延迟 | 🚀 即时响应 |
|
|
||||||
| 缓存未命中 | 标准查询 | ✅ 自动构建 |
|
|
||||||
| 缓存清理 | 批量延迟 | 🔧 优化策略 |
|
|
||||||
|
|
||||||
### 6. 新功能性能基准
|
|
||||||
|
|
||||||
#### 6.1 组件对象池性能
|
|
||||||
```
|
|
||||||
📊 对象池 vs 直接创建 (10,000次操作)
|
|
||||||
对象池获取: 1.65ms (6,060,606次/秒)
|
|
||||||
直接创建: 1.51ms (6,622,516次/秒)
|
|
||||||
|
|
||||||
⚠️ 小规模测试中对象池可能略慢,但在大规模应用中:
|
|
||||||
- 减少30-50%的内存分配
|
|
||||||
- 避免垃圾回收压力
|
|
||||||
- 提升长期运行稳定性
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 6.2 位掩码优化器性能
|
|
||||||
```
|
|
||||||
🔥 位掩码操作性能 (100,000次操作)
|
|
||||||
单个掩码创建: 20.00ms (5,000,000次/秒)
|
|
||||||
组合掩码创建: 53.69ms (1,862,285次/秒)
|
|
||||||
缓存掩码访问: <1ms (近零延迟)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 性能扩展性分析
|
|
||||||
|
|
||||||
### 实体创建扩展性
|
|
||||||
```
|
|
||||||
📈 创建速度趋势分析
|
|
||||||
1K-10K实体: 250,000-640,000 实体/秒 (优秀)
|
|
||||||
10K-100K实体: 200,000-250,000 实体/秒 (良好)
|
|
||||||
100K-500K实体: 160,000-220,000 实体/秒 (稳定)
|
|
||||||
|
|
||||||
结论: 性能随规模稳定下降,无突然性能悬崖
|
|
||||||
```
|
|
||||||
|
|
||||||
### 内存使用效率
|
|
||||||
| 实体数量 | 内存使用 | 每实体内存 | 内存效率 |
|
|
||||||
|---------|---------|-----------|---------|
|
|
||||||
| 1,000 | 3.5MB | 3.5KB | 🚀 极致 |
|
|
||||||
| 5,000 | 7.1MB | 1.4KB | 🚀 极致 |
|
|
||||||
| 10,000 | 20.8MB | 2.1KB | ✅ 优秀 |
|
|
||||||
| 50,000 | ~100MB | ~2KB | ✅ 优秀 |
|
|
||||||
|
|
||||||
## 💡 性能优化建议
|
|
||||||
|
|
||||||
### 1. 实体创建最佳实践
|
|
||||||
|
|
||||||
**✅ 推荐做法**:
|
|
||||||
```typescript
|
|
||||||
// 使用批量创建API
|
|
||||||
const entities = scene.createEntities(10000, "Enemies");
|
|
||||||
|
|
||||||
// 延迟缓存清理
|
|
||||||
entities.forEach(entity => {
|
|
||||||
scene.addEntity(entity, false); // 延迟清理
|
|
||||||
});
|
|
||||||
scene.querySystem.clearCache(); // 手动清理
|
|
||||||
```
|
|
||||||
|
|
||||||
**❌ 避免做法**:
|
|
||||||
```typescript
|
|
||||||
// 避免循环单个创建
|
|
||||||
for (let i = 0; i < 10000; i++) {
|
|
||||||
scene.createEntity("Enemy" + i); // 每次触发缓存清理
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 组件池优化策略
|
|
||||||
|
|
||||||
**预热策略**:
|
|
||||||
```typescript
|
|
||||||
// 预热常用组件池
|
|
||||||
ComponentPoolManager.getInstance().preWarmPools({
|
|
||||||
BulletComponent: 2000, // 子弹大量创建
|
|
||||||
EffectComponent: 1000, // 特效频繁使用
|
|
||||||
PickupComponent: 500 // 道具适量缓存
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**使用模式**:
|
|
||||||
```typescript
|
|
||||||
// 高效的组件复用
|
|
||||||
const bullet = ComponentPoolManager.getInstance().getComponent(BulletComponent);
|
|
||||||
bullet.reset(); // 重置状态
|
|
||||||
entity.addComponent(bullet);
|
|
||||||
|
|
||||||
// 销毁时释放到池
|
|
||||||
ComponentPoolManager.getInstance().releaseComponent(bullet);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 查询优化策略
|
|
||||||
|
|
||||||
**缓存策略**:
|
|
||||||
```typescript
|
|
||||||
// 缓存频繁查询结果
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
private cachedMovingEntities: Entity[];
|
|
||||||
private lastCacheFrame: number = 0;
|
|
||||||
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
// 每5帧更新一次缓存
|
|
||||||
if (Time.frameCount - this.lastCacheFrame > 5) {
|
|
||||||
this.cachedMovingEntities = scene.getEntitiesWithComponents([Position, Velocity]);
|
|
||||||
this.lastCacheFrame = Time.frameCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用缓存结果
|
|
||||||
this.processMovement(this.cachedMovingEntities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 不同规模应用建议
|
|
||||||
|
|
||||||
#### 小型游戏 (< 5,000实体)
|
|
||||||
- ✅ 可以随意使用所有功能
|
|
||||||
- ✅ 不需要特殊优化
|
|
||||||
- ✅ 专注于游戏逻辑开发
|
|
||||||
|
|
||||||
#### 中型游戏 (5,000-50,000实体)
|
|
||||||
- ✅ 使用批量操作API
|
|
||||||
- ✅ 启用组件对象池
|
|
||||||
- ⚠️ 注意查询频率
|
|
||||||
|
|
||||||
#### 大型游戏 (50,000+实体)
|
|
||||||
- 🚀 必须使用批量操作
|
|
||||||
- 🚀 必须启用对象池
|
|
||||||
- 🚀 必须缓存查询结果
|
|
||||||
- 🚀 考虑分区处理
|
|
||||||
|
|
||||||
## 🌍 平台性能对比
|
|
||||||
|
|
||||||
### Windows 桌面端 (测试平台)
|
|
||||||
- **实体创建**: 640,697实体/秒
|
|
||||||
- **组件操作**: 596,929组件/秒
|
|
||||||
- **推荐实体数**: ≤ 200,000
|
|
||||||
|
|
||||||
### 预估其他平台性能
|
|
||||||
|
|
||||||
| 平台类型 | 预估性能比例 | 推荐实体数 | 特殊注意 |
|
|
||||||
|---------|-------------|-----------|---------|
|
|
||||||
| macOS桌面 | 90-100% | ≤ 180,000 | 内存管理优秀 |
|
|
||||||
| Linux桌面 | 95-105% | ≤ 200,000 | 性能最优 |
|
|
||||||
| Chrome浏览器 | 60-80% | ≤ 100,000 | V8引擎优化 |
|
|
||||||
| Firefox浏览器 | 50-70% | ≤ 80,000 | SpiderMonkey限制 |
|
|
||||||
| Safari浏览器 | 55-75% | ≤ 90,000 | JavaScriptCore |
|
|
||||||
| Node.js服务器 | 100-110% | ≤ 500,000 | 服务器级性能 |
|
|
||||||
| Android Chrome | 30-50% | ≤ 30,000 | 移动端限制 |
|
|
||||||
| iOS Safari | 40-60% | ≤ 40,000 | iOS优化较好 |
|
|
||||||
|
|
||||||
## 🔬 测试环境详情
|
|
||||||
|
|
||||||
### 硬件环境
|
|
||||||
- **操作系统**: Windows 10 (Build 26100)
|
|
||||||
- **处理器**: 现代桌面CPU
|
|
||||||
- **内存**: 充足RAM
|
|
||||||
- **存储**: SSD高速存储
|
|
||||||
|
|
||||||
### 软件环境
|
|
||||||
- **Node.js**: v16+
|
|
||||||
- **TypeScript**: v5.8.3
|
|
||||||
- **ECS框架版本**: v2.0.6
|
|
||||||
- **测试工具**: 内置基准测试套件
|
|
||||||
|
|
||||||
### 测试方法
|
|
||||||
- **实体配置**: 位置、速度、生命值、渲染、AI组件随机分配
|
|
||||||
- **测试迭代**: 多次测试取平均值
|
|
||||||
- **内存监控**: 实时内存使用情况
|
|
||||||
- **性能指标**: performance.now()高精度计时
|
|
||||||
|
|
||||||
## 📋 性能测试清单
|
|
||||||
|
|
||||||
### 运行完整性能测试
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 快速基准测试 (2-3分钟)
|
|
||||||
npm run benchmark
|
|
||||||
|
|
||||||
# 2. 完整性能测试 (10-15分钟)
|
|
||||||
npm run test:performance
|
|
||||||
|
|
||||||
# 3. 单元测试验证 (30秒)
|
|
||||||
npm run test:unit
|
|
||||||
|
|
||||||
# 4. 所有测试 (15-20分钟)
|
|
||||||
npm run test
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义性能测试
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { runEntityCreationBenchmark } from '@esengine/ecs-framework/Testing/Performance/benchmark';
|
|
||||||
|
|
||||||
// 自定义规模测试
|
|
||||||
await runEntityCreationBenchmark([1000, 5000, 10000]);
|
|
||||||
|
|
||||||
// 组件性能测试
|
|
||||||
await runComponentPerformanceTest();
|
|
||||||
|
|
||||||
// 查询性能测试
|
|
||||||
await runQueryPerformanceTest();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🏆 性能总结
|
|
||||||
|
|
||||||
### 🎯 核心能力
|
|
||||||
1. **实体创建速度**: 最高64万实体/秒
|
|
||||||
2. **大规模处理**: 50万实体仅需3秒创建
|
|
||||||
3. **均衡性能**: 各组件开销分布合理
|
|
||||||
4. **扩展性**: 性能随规模线性下降,无突然悬崖
|
|
||||||
|
|
||||||
### 🔧 技术特点
|
|
||||||
1. **批量操作架构** - 大幅减少单次操作开销
|
|
||||||
2. **智能缓存策略** - 延迟清理机制
|
|
||||||
3. **索引系统优化** - 避免O(n)操作
|
|
||||||
4. **内存管理优化** - 对象池和位掩码缓存
|
|
||||||
|
|
||||||
### 🌟 实际应用价值
|
|
||||||
- **小型游戏**: 性能过剩,专注玩法
|
|
||||||
- **中型游戏**: 性能充足,适度优化
|
|
||||||
- **大型游戏**: 需要优化策略,但完全可行
|
|
||||||
- **服务器端**: 可处理大规模实体管理
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**结论**: ECS框架达到了产品级性能标准,能够满足从休闲小游戏到复杂RTS游戏的各种需求。框架层面的性能已经充分优化,为开发者提供了坚实的性能基础。
|
|
||||||
@@ -36,7 +36,7 @@ const noneResult = querySystem.queryNone(DeadComponent);
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 类型安全的查询,返回实体和对应的组件
|
// 类型安全的查询,返回实体和对应的组件
|
||||||
const typedResult = querySystem.queryAllTyped(PositionComponent, VelocityComponent);
|
const typedResult = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||||
for (let i = 0; i < typedResult.entities.length; i++) {
|
for (let i = 0; i < typedResult.entities.length; i++) {
|
||||||
const entity = typedResult.entities[i];
|
const entity = typedResult.entities[i];
|
||||||
const [position, velocity] = typedResult.components[i];
|
const [position, velocity] = typedResult.components[i];
|
||||||
@@ -158,9 +158,6 @@ querySystem.warmUpCache(commonQueries);
|
|||||||
### 2. 索引优化
|
### 2. 索引优化
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 自动优化索引配置
|
|
||||||
querySystem.optimizeIndexes();
|
|
||||||
|
|
||||||
// 获取性能统计
|
// 获取性能统计
|
||||||
const stats = querySystem.getStats();
|
const stats = querySystem.getStats();
|
||||||
console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`);
|
console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`);
|
||||||
@@ -205,10 +202,14 @@ console.log(`新增: ${diff.added.length}, 移除: ${diff.removed.length}`);
|
|||||||
### 移动系统示例
|
### 移动系统示例
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EntitySystem } from '@esengine/ecs-framework';
|
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
class MovementSystem extends EntitySystem {
|
class MovementSystem extends EntitySystem {
|
||||||
public update(): void {
|
constructor() {
|
||||||
|
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
// 查询所有可移动的实体
|
// 查询所有可移动的实体
|
||||||
const movableEntities = this.scene.querySystem.queryTwoComponents(
|
const movableEntities = this.scene.querySystem.queryTwoComponents(
|
||||||
PositionComponent,
|
PositionComponent,
|
||||||
@@ -236,7 +237,11 @@ class MovementSystem extends EntitySystem {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class CollisionSystem extends EntitySystem {
|
class CollisionSystem extends EntitySystem {
|
||||||
public update(): void {
|
constructor() {
|
||||||
|
super(Matcher.empty().all(PositionComponent, ColliderComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
// 获取所有具有碰撞器的实体
|
// 获取所有具有碰撞器的实体
|
||||||
const collidableEntities = this.scene.querySystem.queryTwoComponents(
|
const collidableEntities = this.scene.querySystem.queryTwoComponents(
|
||||||
PositionComponent,
|
PositionComponent,
|
||||||
@@ -276,7 +281,11 @@ class CollisionSystem extends EntitySystem {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class HealthSystem extends EntitySystem {
|
class HealthSystem extends EntitySystem {
|
||||||
public update(): void {
|
constructor() {
|
||||||
|
super(Matcher.empty().all(HealthComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
// 查询所有具有生命值的实体
|
// 查询所有具有生命值的实体
|
||||||
const healthEntities = this.scene.querySystem.queryComponentTyped(HealthComponent);
|
const healthEntities = this.scene.querySystem.queryComponentTyped(HealthComponent);
|
||||||
const deadEntities: Entity[] = [];
|
const deadEntities: Entity[] = [];
|
||||||
|
|||||||
734
docs/scene-management-guide.md
Normal file
734
docs/scene-management-guide.md
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
# 场景管理完整指南
|
||||||
|
|
||||||
|
场景(Scene)是ECS框架中管理游戏对象和系统的核心容器。本指南将详细介绍如何有效地使用场景来构建和管理你的游戏。
|
||||||
|
|
||||||
|
## 场景基础概念
|
||||||
|
|
||||||
|
### 什么是场景?
|
||||||
|
|
||||||
|
场景是一个完整的游戏世界容器,它包含:
|
||||||
|
- 🎮 **实体集合** - 所有游戏对象
|
||||||
|
- ⚙️ **系统集合** - 处理游戏逻辑的系统
|
||||||
|
- 📊 **事件系统** - 场景内的事件通信
|
||||||
|
- 🔍 **查询系统** - 高效的实体查询
|
||||||
|
- 📈 **性能监控** - 场景级别的性能统计
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Scene, Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 创建场景
|
||||||
|
const gameScene = new Scene();
|
||||||
|
|
||||||
|
// 设置为当前活动场景
|
||||||
|
Core.scene = gameScene;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景的生命周期
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
// 场景开始时调用
|
||||||
|
onStart() {
|
||||||
|
console.log("场景开始");
|
||||||
|
this.initializeScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景更新时调用(每帧)
|
||||||
|
update() {
|
||||||
|
super.update(); // 调用父类更新
|
||||||
|
|
||||||
|
// 自定义更新逻辑
|
||||||
|
this.updateGameLogic();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景结束时调用
|
||||||
|
onDestroy() {
|
||||||
|
console.log("场景结束");
|
||||||
|
this.cleanup();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基础场景操作
|
||||||
|
|
||||||
|
### 1. 创建和配置场景
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MenuScene extends Scene {
|
||||||
|
private backgroundMusic: AudioClip;
|
||||||
|
|
||||||
|
onStart() {
|
||||||
|
this.setupUI();
|
||||||
|
this.setupSystems();
|
||||||
|
this.setupInput();
|
||||||
|
this.playBackgroundMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupUI() {
|
||||||
|
// 创建菜单UI实体
|
||||||
|
const titleEntity = this.createEntity("Title");
|
||||||
|
titleEntity.addComponent(new TextComponent("我的游戏", 48));
|
||||||
|
titleEntity.addComponent(new PositionComponent(400, 100));
|
||||||
|
|
||||||
|
const startButton = this.createEntity("StartButton");
|
||||||
|
startButton.addComponent(new ButtonComponent("开始游戏"));
|
||||||
|
startButton.addComponent(new PositionComponent(400, 300));
|
||||||
|
|
||||||
|
const settingsButton = this.createEntity("SettingsButton");
|
||||||
|
settingsButton.addComponent(new ButtonComponent("设置"));
|
||||||
|
settingsButton.addComponent(new PositionComponent(400, 400));
|
||||||
|
|
||||||
|
const exitButton = this.createEntity("ExitButton");
|
||||||
|
exitButton.addComponent(new ButtonComponent("退出"));
|
||||||
|
exitButton.addComponent(new PositionComponent(400, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSystems() {
|
||||||
|
// 添加UI相关系统
|
||||||
|
this.addEntityProcessor(new UIRenderSystem());
|
||||||
|
this.addEntityProcessor(new ButtonClickSystem());
|
||||||
|
this.addEntityProcessor(new MenuTransitionSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupInput() {
|
||||||
|
// 监听按钮点击事件
|
||||||
|
this.eventBus.on('button:clicked', this.onButtonClicked, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onButtonClicked(data: { buttonName: string }) {
|
||||||
|
switch (data.buttonName) {
|
||||||
|
case "开始游戏":
|
||||||
|
this.transitionToGame();
|
||||||
|
break;
|
||||||
|
case "设置":
|
||||||
|
this.showSettings();
|
||||||
|
break;
|
||||||
|
case "退出":
|
||||||
|
this.exitGame();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private transitionToGame() {
|
||||||
|
// 切换到游戏场景
|
||||||
|
const gameScene = new GameScene();
|
||||||
|
Core.scene = gameScene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 游戏主场景
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
private player: Entity;
|
||||||
|
private enemySpawner: Entity;
|
||||||
|
private ui: Entity;
|
||||||
|
|
||||||
|
onStart() {
|
||||||
|
this.setupWorld();
|
||||||
|
this.setupPlayer();
|
||||||
|
this.setupEnemies();
|
||||||
|
this.setupSystems();
|
||||||
|
this.setupUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupWorld() {
|
||||||
|
// 创建背景
|
||||||
|
const background = this.createEntity("Background");
|
||||||
|
background.addComponent(new SpriteComponent("background.png"));
|
||||||
|
background.addComponent(new PositionComponent(0, 0));
|
||||||
|
|
||||||
|
// 创建边界
|
||||||
|
this.createWorldBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupPlayer() {
|
||||||
|
this.player = this.createEntity("Player");
|
||||||
|
this.player.addComponent(new PositionComponent(400, 300));
|
||||||
|
this.player.addComponent(new VelocityComponent());
|
||||||
|
this.player.addComponent(new HealthComponent(100));
|
||||||
|
this.player.addComponent(new SpriteComponent("player.png"));
|
||||||
|
this.player.addComponent(new PlayerInputComponent());
|
||||||
|
this.player.addComponent(new WeaponComponent());
|
||||||
|
this.player.tag = EntityTags.PLAYER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupEnemies() {
|
||||||
|
this.enemySpawner = this.createEntity("EnemySpawner");
|
||||||
|
this.enemySpawner.addComponent(new SpawnerComponent());
|
||||||
|
this.enemySpawner.addComponent(new PositionComponent(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSystems() {
|
||||||
|
// 输入系统
|
||||||
|
this.addEntityProcessor(new PlayerInputSystem()).updateOrder = 0;
|
||||||
|
|
||||||
|
// 游戏逻辑系统
|
||||||
|
this.addEntityProcessor(new MovementSystem()).updateOrder = 10;
|
||||||
|
this.addEntityProcessor(new AISystem()).updateOrder = 15;
|
||||||
|
this.addEntityProcessor(new WeaponSystem()).updateOrder = 20;
|
||||||
|
this.addEntityProcessor(new CollisionSystem()).updateOrder = 30;
|
||||||
|
this.addEntityProcessor(new HealthSystem()).updateOrder = 40;
|
||||||
|
|
||||||
|
// 生成和清理系统
|
||||||
|
this.addEntityProcessor(new EnemySpawnSystem()).updateOrder = 50;
|
||||||
|
this.addEntityProcessor(new EntityCleanupSystem()).updateOrder = 60;
|
||||||
|
|
||||||
|
// 渲染系统
|
||||||
|
this.addEntityProcessor(new RenderSystem()).updateOrder = 100;
|
||||||
|
this.addEntityProcessor(new UIRenderSystem()).updateOrder = 110;
|
||||||
|
|
||||||
|
// 特效和音频系统
|
||||||
|
this.addEntityProcessor(new ParticleSystem()).updateOrder = 120;
|
||||||
|
this.addEntityProcessor(new AudioSystem()).updateOrder = 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupUI() {
|
||||||
|
this.ui = this.createEntity("GameUI");
|
||||||
|
this.ui.addComponent(new HealthBarComponent());
|
||||||
|
this.ui.addComponent(new ScoreDisplayComponent());
|
||||||
|
this.ui.addComponent(new AmmoDisplayComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private createWorldBounds() {
|
||||||
|
// 创建世界边界,防止实体跑出屏幕
|
||||||
|
const bounds = [
|
||||||
|
{ x: 0, y: 0, width: 10, height: 600 }, // 左边界
|
||||||
|
{ x: 790, y: 0, width: 10, height: 600 }, // 右边界
|
||||||
|
{ x: 0, y: 0, width: 800, height: 10 }, // 上边界
|
||||||
|
{ x: 0, y: 590, width: 800, height: 10 } // 下边界
|
||||||
|
];
|
||||||
|
|
||||||
|
bounds.forEach((bound, index) => {
|
||||||
|
const wall = this.createEntity(`Wall_${index}`);
|
||||||
|
wall.addComponent(new PositionComponent(bound.x, bound.y));
|
||||||
|
wall.addComponent(new ColliderComponent(bound.width, bound.height));
|
||||||
|
wall.addComponent(new WallComponent());
|
||||||
|
wall.tag = EntityTags.WALL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景切换和管理
|
||||||
|
|
||||||
|
### 1. 场景管理器
|
||||||
|
|
||||||
|
> **注意:** 以下的 SceneManager、TransitionManager 等是自定义的场景管理类示例,不是ECS框架提供的内置API。你可以基于这些示例实现自己的场景管理系统。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enum SceneType {
|
||||||
|
MENU = "menu",
|
||||||
|
GAME = "game",
|
||||||
|
PAUSE = "pause",
|
||||||
|
GAME_OVER = "game_over",
|
||||||
|
SETTINGS = "settings"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义场景管理器(示例实现)
|
||||||
|
class SceneManager {
|
||||||
|
private static instance: SceneManager;
|
||||||
|
private currentScene: Scene | null = null;
|
||||||
|
private previousScene: Scene | null = null;
|
||||||
|
private sceneHistory: Scene[] = [];
|
||||||
|
|
||||||
|
static getInstance(): SceneManager {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new SceneManager();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToScene(sceneType: SceneType, data?: any) {
|
||||||
|
// 保存当前场景到历史
|
||||||
|
if (this.currentScene) {
|
||||||
|
this.previousScene = this.currentScene;
|
||||||
|
this.sceneHistory.push(this.currentScene);
|
||||||
|
this.currentScene.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新场景
|
||||||
|
this.currentScene = this.createScene(sceneType, data);
|
||||||
|
Core.scene = this.currentScene;
|
||||||
|
|
||||||
|
console.log(`切换到场景: ${sceneType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): boolean {
|
||||||
|
if (this.sceneHistory.length > 0) {
|
||||||
|
const previousScene = this.sceneHistory.pop()!;
|
||||||
|
|
||||||
|
if (this.currentScene) {
|
||||||
|
this.currentScene.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentScene = previousScene;
|
||||||
|
Core.scene = this.currentScene;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushScene(sceneType: SceneType, data?: any) {
|
||||||
|
// 暂停当前场景,不销毁
|
||||||
|
if (this.currentScene) {
|
||||||
|
this.previousScene = this.currentScene;
|
||||||
|
this.sceneHistory.push(this.currentScene);
|
||||||
|
this.pauseScene(this.currentScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentScene = this.createScene(sceneType, data);
|
||||||
|
Core.scene = this.currentScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
popScene() {
|
||||||
|
if (this.sceneHistory.length > 0) {
|
||||||
|
if (this.currentScene) {
|
||||||
|
this.currentScene.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentScene = this.sceneHistory.pop()!;
|
||||||
|
this.resumeScene(this.currentScene);
|
||||||
|
Core.scene = this.currentScene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createScene(sceneType: SceneType, data?: any): Scene {
|
||||||
|
switch (sceneType) {
|
||||||
|
case SceneType.MENU:
|
||||||
|
return new MenuScene();
|
||||||
|
case SceneType.GAME:
|
||||||
|
return new GameScene(data);
|
||||||
|
case SceneType.PAUSE:
|
||||||
|
return new PauseScene();
|
||||||
|
case SceneType.GAME_OVER:
|
||||||
|
return new GameOverScene(data);
|
||||||
|
case SceneType.SETTINGS:
|
||||||
|
return new SettingsScene();
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown scene type: ${sceneType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private pauseScene(scene: Scene) {
|
||||||
|
// 暂停场景的所有系统
|
||||||
|
scene.systems.forEach(system => {
|
||||||
|
system.enabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private resumeScene(scene: Scene) {
|
||||||
|
// 恢复场景的所有系统
|
||||||
|
scene.systems.forEach(system => {
|
||||||
|
system.enabled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用场景管理器
|
||||||
|
const sceneManager = SceneManager.getInstance();
|
||||||
|
|
||||||
|
// 切换场景
|
||||||
|
sceneManager.switchToScene(SceneType.MENU);
|
||||||
|
|
||||||
|
// 推入场景(用于暂停菜单等)
|
||||||
|
sceneManager.pushScene(SceneType.PAUSE);
|
||||||
|
|
||||||
|
// 弹出场景(返回游戏)
|
||||||
|
sceneManager.popScene();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 场景转场效果
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TransitionManager {
|
||||||
|
private isTransitioning: boolean = false;
|
||||||
|
|
||||||
|
async fadeTransition(fromScene: Scene, toScene: Scene, duration: number = 1.0) {
|
||||||
|
if (this.isTransitioning) return;
|
||||||
|
|
||||||
|
this.isTransitioning = true;
|
||||||
|
|
||||||
|
// 创建转场覆盖层
|
||||||
|
const overlay = this.createFadeOverlay();
|
||||||
|
|
||||||
|
// 淡出当前场景
|
||||||
|
await this.fadeOut(overlay, duration / 2);
|
||||||
|
|
||||||
|
// 切换场景
|
||||||
|
fromScene.onDestroy();
|
||||||
|
Core.scene = toScene;
|
||||||
|
|
||||||
|
// 淡入新场景
|
||||||
|
await this.fadeIn(overlay, duration / 2);
|
||||||
|
|
||||||
|
// 清理覆盖层
|
||||||
|
overlay.destroy();
|
||||||
|
this.isTransitioning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async slideTransition(fromScene: Scene, toScene: Scene, direction: 'left' | 'right' | 'up' | 'down') {
|
||||||
|
if (this.isTransitioning) return;
|
||||||
|
|
||||||
|
this.isTransitioning = true;
|
||||||
|
|
||||||
|
// 实现滑动转场效果
|
||||||
|
const slideDistance = this.getSlideDistance(direction);
|
||||||
|
|
||||||
|
// 移动当前场景
|
||||||
|
await this.slideScene(fromScene, slideDistance);
|
||||||
|
|
||||||
|
// 切换场景
|
||||||
|
fromScene.onDestroy();
|
||||||
|
Core.scene = toScene;
|
||||||
|
|
||||||
|
// 从相反方向滑入新场景
|
||||||
|
await this.slideScene(toScene, -slideDistance);
|
||||||
|
|
||||||
|
this.isTransitioning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFadeOverlay(): Entity {
|
||||||
|
const overlay = Core.scene.createEntity("TransitionOverlay");
|
||||||
|
overlay.addComponent(new SpriteComponent("black_pixel.png"));
|
||||||
|
overlay.addComponent(new PositionComponent(0, 0));
|
||||||
|
|
||||||
|
const sprite = overlay.getComponent(SpriteComponent);
|
||||||
|
sprite.width = 800;
|
||||||
|
sprite.height = 600;
|
||||||
|
sprite.alpha = 0;
|
||||||
|
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景数据管理
|
||||||
|
|
||||||
|
### 1. 场景间数据传递
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GameData {
|
||||||
|
score: number;
|
||||||
|
level: number;
|
||||||
|
playerName: string;
|
||||||
|
difficulty: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
|
private gameData: GameData;
|
||||||
|
|
||||||
|
constructor(data?: GameData) {
|
||||||
|
super();
|
||||||
|
this.gameData = data || {
|
||||||
|
score: 0,
|
||||||
|
level: 1,
|
||||||
|
playerName: "Player",
|
||||||
|
difficulty: "normal"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
// 根据传入数据配置场景
|
||||||
|
this.setupPlayerWithData();
|
||||||
|
this.setupLevelWithDifficulty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupPlayerWithData() {
|
||||||
|
const player = this.createEntity("Player");
|
||||||
|
player.addComponent(new NameComponent(this.gameData.playerName));
|
||||||
|
player.addComponent(new ScoreComponent(this.gameData.score));
|
||||||
|
// ... 其他组件
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupLevelWithDifficulty() {
|
||||||
|
const difficultySettings = {
|
||||||
|
easy: { enemySpawnRate: 2.0, enemyHealth: 50 },
|
||||||
|
normal: { enemySpawnRate: 1.5, enemyHealth: 75 },
|
||||||
|
hard: { enemySpawnRate: 1.0, enemyHealth: 100 }
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = difficultySettings[this.gameData.difficulty];
|
||||||
|
|
||||||
|
const spawner = this.createEntity("EnemySpawner");
|
||||||
|
const spawnerComp = new SpawnerComponent();
|
||||||
|
spawnerComp.spawnInterval = settings.enemySpawnRate;
|
||||||
|
spawnerComp.enemyHealth = settings.enemyHealth;
|
||||||
|
spawner.addComponent(spawnerComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏结束时传递数据到下一个场景
|
||||||
|
gameOver() {
|
||||||
|
const finalScore = this.getPlayerScore();
|
||||||
|
const sceneManager = SceneManager.getInstance();
|
||||||
|
|
||||||
|
sceneManager.switchToScene(SceneType.GAME_OVER, {
|
||||||
|
score: finalScore,
|
||||||
|
level: this.gameData.level,
|
||||||
|
playerName: this.gameData.playerName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GameOverScene extends Scene {
|
||||||
|
constructor(private gameData: GameData) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart() {
|
||||||
|
this.displayResults();
|
||||||
|
this.setupRestartButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
private displayResults() {
|
||||||
|
const scoreText = this.createEntity("ScoreText");
|
||||||
|
scoreText.addComponent(new TextComponent(`最终分数: ${this.gameData.score}`));
|
||||||
|
scoreText.addComponent(new PositionComponent(400, 200));
|
||||||
|
|
||||||
|
const levelText = this.createEntity("LevelText");
|
||||||
|
levelText.addComponent(new TextComponent(`到达关卡: ${this.gameData.level}`));
|
||||||
|
levelText.addComponent(new PositionComponent(400, 250));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 持久化数据管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SaveManager {
|
||||||
|
private static SAVE_KEY = "game_save_data";
|
||||||
|
|
||||||
|
static saveScene(scene: Scene): void {
|
||||||
|
const saveData = {
|
||||||
|
playerData: this.extractPlayerData(scene),
|
||||||
|
sceneState: this.extractSceneState(scene),
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem(this.SAVE_KEY, JSON.stringify(saveData));
|
||||||
|
console.log("游戏已保存");
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadScene(): Scene | null {
|
||||||
|
const saveDataStr = localStorage.getItem(this.SAVE_KEY);
|
||||||
|
if (!saveDataStr) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const saveData = JSON.parse(saveDataStr);
|
||||||
|
return this.recreateScene(saveData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("读取存档失败:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static extractPlayerData(scene: Scene): any {
|
||||||
|
const player = scene.findEntitiesWithTag(EntityTags.PLAYER)[0];
|
||||||
|
if (!player) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: player.getComponent(PositionComponent),
|
||||||
|
health: player.getComponent(HealthComponent),
|
||||||
|
inventory: player.getComponent(InventoryComponent)?.getItems(),
|
||||||
|
score: player.getComponent(ScoreComponent)?.score
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static extractSceneState(scene: Scene): any {
|
||||||
|
return {
|
||||||
|
enemies: this.extractEnemiesData(scene),
|
||||||
|
items: this.extractItemsData(scene),
|
||||||
|
level: this.getCurrentLevel(scene)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static recreateScene(saveData: any): Scene {
|
||||||
|
const scene = new GameScene();
|
||||||
|
|
||||||
|
// 重建玩家
|
||||||
|
this.recreatePlayer(scene, saveData.playerData);
|
||||||
|
|
||||||
|
// 重建场景状态
|
||||||
|
this.recreateSceneState(scene, saveData.sceneState);
|
||||||
|
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动保存系统
|
||||||
|
class AutoSaveSystem extends IntervalSystem {
|
||||||
|
constructor() {
|
||||||
|
super(30.0); // 每30秒自动保存
|
||||||
|
}
|
||||||
|
|
||||||
|
processSystem() {
|
||||||
|
SaveManager.saveScene(this.scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景性能优化
|
||||||
|
|
||||||
|
### 1. 实体管理优化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class OptimizedScene extends Scene {
|
||||||
|
private activeEntities: Set<Entity> = new Set();
|
||||||
|
private inactiveEntities: Set<Entity> = new Set();
|
||||||
|
|
||||||
|
createEntity(name?: string): Entity {
|
||||||
|
const entity = super.createEntity(name);
|
||||||
|
this.activeEntities.add(entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyEntity(entity: Entity) {
|
||||||
|
this.activeEntities.delete(entity);
|
||||||
|
super.destroyEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂时禁用实体而不销毁
|
||||||
|
deactivateEntity(entity: Entity) {
|
||||||
|
if (this.activeEntities.has(entity)) {
|
||||||
|
this.activeEntities.delete(entity);
|
||||||
|
this.inactiveEntities.add(entity);
|
||||||
|
entity.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新激活实体
|
||||||
|
activateEntity(entity: Entity) {
|
||||||
|
if (this.inactiveEntities.has(entity)) {
|
||||||
|
this.inactiveEntities.delete(entity);
|
||||||
|
this.activeEntities.add(entity);
|
||||||
|
entity.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只更新活跃实体
|
||||||
|
update() {
|
||||||
|
for (const entity of this.activeEntities) {
|
||||||
|
if (entity.enabled) {
|
||||||
|
entity.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateEntitySystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量操作
|
||||||
|
deactivateAllEnemies() {
|
||||||
|
const enemies = this.findEntitiesWithTag(EntityTags.ENEMY);
|
||||||
|
enemies.forEach(enemy => this.deactivateEntity(enemy));
|
||||||
|
}
|
||||||
|
|
||||||
|
activateAllEnemies() {
|
||||||
|
const enemies = Array.from(this.inactiveEntities)
|
||||||
|
.filter(entity => entity.hasTag(EntityTags.ENEMY));
|
||||||
|
enemies.forEach(enemy => this.activateEntity(enemy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 系统性能监控
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class PerformanceMonitoredScene extends Scene {
|
||||||
|
private systemPerformance: Map<string, number[]> = new Map();
|
||||||
|
|
||||||
|
addEntityProcessor<T extends EntitySystem>(system: T): T {
|
||||||
|
const wrappedSystem = this.wrapSystemWithMonitoring(system);
|
||||||
|
return super.addEntityProcessor(wrappedSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrapSystemWithMonitoring<T extends EntitySystem>(system: T): T {
|
||||||
|
const originalUpdate = system.update.bind(system);
|
||||||
|
const systemName = system.constructor.name;
|
||||||
|
|
||||||
|
system.update = () => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
originalUpdate();
|
||||||
|
const endTime = performance.now();
|
||||||
|
|
||||||
|
this.recordSystemPerformance(systemName, endTime - startTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
private recordSystemPerformance(systemName: string, duration: number) {
|
||||||
|
if (!this.systemPerformance.has(systemName)) {
|
||||||
|
this.systemPerformance.set(systemName, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = this.systemPerformance.get(systemName)!;
|
||||||
|
records.push(duration);
|
||||||
|
|
||||||
|
// 只保留最近100次记录
|
||||||
|
if (records.length > 100) {
|
||||||
|
records.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPerformanceReport(): any {
|
||||||
|
const report = {};
|
||||||
|
|
||||||
|
this.systemPerformance.forEach((durations, systemName) => {
|
||||||
|
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
|
||||||
|
const maxDuration = Math.max(...durations);
|
||||||
|
const minDuration = Math.min(...durations);
|
||||||
|
|
||||||
|
report[systemName] = {
|
||||||
|
average: avgDuration.toFixed(2) + 'ms',
|
||||||
|
max: maxDuration.toFixed(2) + 'ms',
|
||||||
|
min: minDuration.toFixed(2) + 'ms',
|
||||||
|
samples: durations.length
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定期输出性能报告
|
||||||
|
private performanceReportTimer() {
|
||||||
|
Core.schedule(5.0, true, this, () => {
|
||||||
|
console.table(this.getPerformanceReport());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题和最佳实践
|
||||||
|
|
||||||
|
### Q: 何时创建新场景?
|
||||||
|
|
||||||
|
A:
|
||||||
|
- 游戏的不同阶段(菜单、游戏、设置)
|
||||||
|
- 不同的关卡
|
||||||
|
- 需要完全不同系统配置的情况
|
||||||
|
- 需要清理大量实体时
|
||||||
|
|
||||||
|
### Q: 场景切换时如何保持数据?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 使用场景构造函数传递数据
|
||||||
|
2. 使用全局数据管理器
|
||||||
|
3. 使用本地存储进行持久化
|
||||||
|
|
||||||
|
### Q: 如何优化场景性能?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 合理使用实体的启用/禁用
|
||||||
|
2. 监控系统性能
|
||||||
|
3. 批量操作实体
|
||||||
|
4. 使用对象池减少垃圾回收
|
||||||
|
|
||||||
|
### Q: 多个场景可以同时存在吗?
|
||||||
|
|
||||||
|
A: 框架同时只支持一个活跃场景,但可以通过场景栈实现多场景管理(如暂停菜单)。
|
||||||
|
|
||||||
|
通过合理使用场景系统,你可以构建出结构清晰、性能优良的游戏架构!
|
||||||
515
docs/system-guide.md
Normal file
515
docs/system-guide.md
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
# 系统(System)详解指南
|
||||||
|
|
||||||
|
系统是ECS架构中的"S",负责处理拥有特定组件的实体。本指南详细介绍框架中的各种系统类型及其使用方法。
|
||||||
|
|
||||||
|
## 系统基础概念
|
||||||
|
|
||||||
|
### 什么是系统?
|
||||||
|
|
||||||
|
系统是处理游戏逻辑的地方,它们:
|
||||||
|
- 🎯 **专注单一职责** - 每个系统只处理一种类型的逻辑
|
||||||
|
- 🔄 **自动执行** - 系统会在每帧自动被调用
|
||||||
|
- 📊 **基于组件过滤** - 只处理包含特定组件的实体
|
||||||
|
- ⚡ **高性能** - 利用ECS的数据局部性优势
|
||||||
|
|
||||||
|
### 系统的工作原理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 系统的基本工作流程:
|
||||||
|
// 1. 查询符合条件的实体
|
||||||
|
// 2. 遍历这些实体
|
||||||
|
// 3. 读取/修改实体的组件数据
|
||||||
|
// 4. 执行游戏逻辑
|
||||||
|
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
process(entities: Entity[]) {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(PositionComponent);
|
||||||
|
const velocity = entity.getComponent(VelocityComponent);
|
||||||
|
|
||||||
|
// 更新位置 = 当前位置 + 速度 * 时间
|
||||||
|
position.x += velocity.x * Time.deltaTime;
|
||||||
|
position.y += velocity.y * Time.deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统类型详解
|
||||||
|
|
||||||
|
### 1. EntitySystem - 基础系统
|
||||||
|
|
||||||
|
最常用的系统类型,每帧处理所有符合条件的实体。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class HealthSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
// 使用Matcher指定需要的组件
|
||||||
|
super(Matcher.empty().all(HealthComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主要处理逻辑
|
||||||
|
protected process(entities: Entity[]) {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const health = entity.getComponent(HealthComponent);
|
||||||
|
|
||||||
|
// 处理生命值逻辑
|
||||||
|
if (health.currentHealth <= 0) {
|
||||||
|
this.handleDeath(entity);
|
||||||
|
} else if (health.currentHealth < health.maxHealth) {
|
||||||
|
this.handleRegeneration(health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDeath(entity: Entity) {
|
||||||
|
// 添加死亡标记
|
||||||
|
entity.addComponent(new DeadComponent());
|
||||||
|
|
||||||
|
// 触发死亡事件
|
||||||
|
Core.emitter.emit('entity:died', {
|
||||||
|
entityId: entity.id,
|
||||||
|
entityName: entity.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRegeneration(health: HealthComponent) {
|
||||||
|
// 缓慢恢复生命值
|
||||||
|
health.currentHealth += health.regenRate * Time.deltaTime;
|
||||||
|
health.currentHealth = Math.min(health.currentHealth, health.maxHealth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 移动系统
|
||||||
|
- 渲染系统
|
||||||
|
- 碰撞检测系统
|
||||||
|
- AI系统
|
||||||
|
|
||||||
|
### 2. ProcessingSystem - 简化处理系统
|
||||||
|
|
||||||
|
不需要处理具体实体,主要用于执行全局逻辑或不依赖特定实体的系统处理。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ProcessingSystem, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameLogicSystem extends ProcessingSystem {
|
||||||
|
constructor() {
|
||||||
|
// ProcessingSystem可以不指定Matcher,或使用空Matcher
|
||||||
|
super(Matcher.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理系统逻辑(每帧执行)
|
||||||
|
public processSystem() {
|
||||||
|
// 执行全局游戏逻辑
|
||||||
|
this.updateGameState();
|
||||||
|
this.checkWinConditions();
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateGameState() {
|
||||||
|
// 更新游戏状态逻辑
|
||||||
|
console.log("更新游戏状态");
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkWinConditions() {
|
||||||
|
// 检查胜利条件
|
||||||
|
const players = this.scene.findEntitiesByTag(EntityTags.PLAYER);
|
||||||
|
const enemies = this.scene.findEntitiesByTag(EntityTags.ENEMY);
|
||||||
|
|
||||||
|
if (enemies.length === 0) {
|
||||||
|
this.triggerVictory();
|
||||||
|
} else if (players.length === 0) {
|
||||||
|
this.triggerGameOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUI() {
|
||||||
|
// 更新UI显示
|
||||||
|
const gameTime = Time.totalTime;
|
||||||
|
console.log(`游戏时间: ${gameTime.toFixed(1)}秒`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processIdle(entity: Entity, ai: AIComponent) {
|
||||||
|
ai.idleTimer += Time.deltaTime;
|
||||||
|
|
||||||
|
if (ai.idleTimer >= ai.idleTime) {
|
||||||
|
ai.state = AIState.PATROL;
|
||||||
|
ai.idleTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查附近是否有玩家
|
||||||
|
const nearbyPlayer = this.findNearbyPlayer(entity, ai.detectionRange);
|
||||||
|
if (nearbyPlayer) {
|
||||||
|
ai.state = AIState.CHASE;
|
||||||
|
ai.target = nearbyPlayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processPatrol(entity: Entity, ai: AIComponent, position: PositionComponent) {
|
||||||
|
// 简单的来回巡逻
|
||||||
|
if (!ai.patrolTarget) {
|
||||||
|
ai.patrolTarget = this.getNextPatrolPoint(ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
const direction = ai.patrolTarget.subtract(position);
|
||||||
|
const distance = direction.length();
|
||||||
|
|
||||||
|
if (distance < 10) {
|
||||||
|
ai.patrolTarget = this.getNextPatrolPoint(ai);
|
||||||
|
} else {
|
||||||
|
const normalized = direction.normalize();
|
||||||
|
position.x += normalized.x * ai.moveSpeed * Time.deltaTime;
|
||||||
|
position.y += normalized.y * ai.moveSpeed * Time.deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 全局游戏逻辑系统
|
||||||
|
- 胜负判断系统
|
||||||
|
- UI更新系统
|
||||||
|
- 不依赖特定实体的处理
|
||||||
|
|
||||||
|
### 3. IntervalSystem - 间隔执行系统
|
||||||
|
|
||||||
|
不是每帧都执行,而是按指定间隔执行的系统,适合不需要高频更新的逻辑。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IntervalSystem, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class SpawnSystem extends IntervalSystem {
|
||||||
|
private spawnPoints: { x: number; y: number }[] = [
|
||||||
|
{ x: 100, y: 100 },
|
||||||
|
{ x: 700, y: 100 },
|
||||||
|
{ x: 400, y: 500 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 每2秒执行一次
|
||||||
|
constructor() {
|
||||||
|
// IntervalSystem需要指定Matcher和间隔时间
|
||||||
|
super(Matcher.empty().all(SpawnerComponent), 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 间隔执行的逻辑(重写process方法)
|
||||||
|
protected process(entities: Entity[]) {
|
||||||
|
// entities就是匹配的生成器实体
|
||||||
|
|
||||||
|
for (const spawner of entities) {
|
||||||
|
const spawnerComp = spawner.getComponent(SpawnerComponent);
|
||||||
|
|
||||||
|
if (this.shouldSpawn(spawnerComp)) {
|
||||||
|
this.spawnEnemy(spawner, spawnerComp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldSpawn(spawner: SpawnerComponent): boolean {
|
||||||
|
// 检查是否应该生成
|
||||||
|
const currentEnemyCount = this.getCurrentEnemyCount();
|
||||||
|
return currentEnemyCount < spawner.maxEnemies &&
|
||||||
|
Math.random() < spawner.spawnChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnEnemy(spawnerEntity: Entity, spawner: SpawnerComponent) {
|
||||||
|
// 随机选择生成点
|
||||||
|
const spawnPoint = this.spawnPoints[
|
||||||
|
Math.floor(Math.random() * this.spawnPoints.length)
|
||||||
|
];
|
||||||
|
|
||||||
|
// 创建敌人实体
|
||||||
|
const enemy = this.scene.createEntity("Enemy");
|
||||||
|
enemy.addComponent(new PositionComponent(spawnPoint.x, spawnPoint.y));
|
||||||
|
enemy.addComponent(new HealthComponent(50));
|
||||||
|
enemy.addComponent(new AIComponent());
|
||||||
|
enemy.addComponent(new VelocityComponent(0, 0));
|
||||||
|
enemy.tag = EntityTags.ENEMY;
|
||||||
|
|
||||||
|
// 更新生成器统计
|
||||||
|
spawner.spawnedCount++;
|
||||||
|
spawner.lastSpawnTime = Time.totalTime;
|
||||||
|
|
||||||
|
// 发送生成事件
|
||||||
|
Core.emitter.emit('enemy:spawned', {
|
||||||
|
enemyId: enemy.id,
|
||||||
|
spawnPoint: spawnPoint,
|
||||||
|
spawnerEntity: spawnerEntity.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 敌人生成系统
|
||||||
|
- 自动保存系统
|
||||||
|
- 资源回收系统
|
||||||
|
- 定期数据同步
|
||||||
|
|
||||||
|
### 4. PassiveSystem - 被动系统
|
||||||
|
|
||||||
|
不主动遍历实体,而是响应事件的系统。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PassiveSystem, Matcher, Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class ScoreSystem extends PassiveSystem {
|
||||||
|
private score: number = 0;
|
||||||
|
private multiplier: number = 1;
|
||||||
|
private combo: number = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// PassiveSystem也需要Matcher,即使不使用
|
||||||
|
super(Matcher.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
super.initialize();
|
||||||
|
|
||||||
|
// 监听游戏事件(使用Core.emitter)
|
||||||
|
Core.emitter.addObserver('enemy:killed', this.onEnemyKilled, this);
|
||||||
|
Core.emitter.addObserver('item:collected', this.onItemCollected, this);
|
||||||
|
Core.emitter.addObserver('combo:broken', this.onComboBroken, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassiveSystem被移除时清理
|
||||||
|
destroy() {
|
||||||
|
// 清理事件监听
|
||||||
|
Core.emitter.removeObserver('enemy:killed', this.onEnemyKilled, this);
|
||||||
|
Core.emitter.removeObserver('item:collected', this.onItemCollected, this);
|
||||||
|
Core.emitter.removeObserver('combo:broken', this.onComboBroken, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEnemyKilled(data: { enemyType: string; position: { x: number; y: number } }) {
|
||||||
|
// 根据敌人类型给分
|
||||||
|
let baseScore = this.getScoreForEnemyType(data.enemyType);
|
||||||
|
|
||||||
|
// 连击奖励
|
||||||
|
this.combo++;
|
||||||
|
if (this.combo > 3) {
|
||||||
|
this.multiplier = Math.min(this.combo * 0.1, 3.0); // 最多3倍
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalScore = Math.floor(baseScore * this.multiplier);
|
||||||
|
this.addScore(finalScore);
|
||||||
|
|
||||||
|
// 显示分数奖励
|
||||||
|
this.showScorePopup(data.position, finalScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addScore(points: number) {
|
||||||
|
this.score += points;
|
||||||
|
|
||||||
|
// 发送分数更新事件
|
||||||
|
Core.emitter.emit('score:updated', {
|
||||||
|
score: this.score,
|
||||||
|
points: points,
|
||||||
|
multiplier: this.multiplier,
|
||||||
|
combo: this.combo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 分数统计系统
|
||||||
|
- 音效播放系统
|
||||||
|
- UI更新系统
|
||||||
|
- 成就系统
|
||||||
|
|
||||||
|
## 系统管理和注册
|
||||||
|
|
||||||
|
### 在场景中添加系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Scene, Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
const scene = new Scene();
|
||||||
|
|
||||||
|
// 添加各种系统(使用addEntityProcessor方法)
|
||||||
|
scene.addEntityProcessor(new MovementSystem());
|
||||||
|
scene.addEntityProcessor(new GameLogicSystem());
|
||||||
|
scene.addEntityProcessor(new SpawnSystem());
|
||||||
|
scene.addEntityProcessor(new ScoreSystem());
|
||||||
|
|
||||||
|
// 设置系统的执行优先级
|
||||||
|
const movementSystem = scene.getEntityProcessor(MovementSystem);
|
||||||
|
if (movementSystem) {
|
||||||
|
movementSystem.updateOrder = 10; // 数值越小越先执行
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSystem = scene.getEntityProcessor(RenderSystem);
|
||||||
|
if (renderSystem) {
|
||||||
|
renderSystem.updateOrder = 100; // 渲染系统最后执行
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置为当前场景
|
||||||
|
Core.scene = scene;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 系统的启用和禁用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 暂时禁用某个系统
|
||||||
|
const gameLogicSystem = scene.getEntityProcessor(GameLogicSystem);
|
||||||
|
if (gameLogicSystem) {
|
||||||
|
gameLogicSystem.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新启用
|
||||||
|
if (gameLogicSystem) {
|
||||||
|
gameLogicSystem.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除系统
|
||||||
|
scene.removeEntityProcessor(gameLogicSystem);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统设计最佳实践
|
||||||
|
|
||||||
|
### 1. 单一职责原则
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的设计:每个系统只负责一件事
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
// 只负责移动
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollisionSystem extends EntitySystem {
|
||||||
|
// 只负责碰撞检测
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderSystem extends EntitySystem {
|
||||||
|
// 只负责渲染
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 不好的设计:一个系统做太多事情
|
||||||
|
class GameplaySystem extends EntitySystem {
|
||||||
|
// 既处理移动,又处理碰撞,还处理渲染...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 合理的系统执行顺序
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 设置合理的执行顺序
|
||||||
|
scene.addEntityProcessor(new InputSystem()).updateOrder = 0; // 输入最先
|
||||||
|
scene.addEntityProcessor(new GameLogicSystem()).updateOrder = 10; // 游戏逻辑
|
||||||
|
scene.addEntityProcessor(new MovementSystem()).updateOrder = 20; // 移动计算
|
||||||
|
scene.addEntityProcessor(new CollisionSystem()).updateOrder = 30; // 碰撞检测
|
||||||
|
scene.addEntityProcessor(new HealthSystem()).updateOrder = 40; // 生命值处理
|
||||||
|
scene.addEntityProcessor(new RenderSystem()).updateOrder = 100; // 渲染最后
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 系统间通信
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 使用事件进行系统间通信
|
||||||
|
class CollisionSystem extends EntitySystem {
|
||||||
|
process(entities: Entity[]) {
|
||||||
|
// ... 碰撞检测逻辑
|
||||||
|
|
||||||
|
if (collision) {
|
||||||
|
// 发送碰撞事件,让其他系统响应
|
||||||
|
Core.emitter.emit('collision:detected', {
|
||||||
|
entity1: collider1,
|
||||||
|
entity2: collider2,
|
||||||
|
collisionPoint: point
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HealthSystem extends PassiveSystem {
|
||||||
|
onAddedToScene() {
|
||||||
|
// 监听碰撞事件
|
||||||
|
Core.emitter.addObserver('collision:detected', this.onCollision, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onCollision(data: CollisionEventData) {
|
||||||
|
// 处理碰撞伤害
|
||||||
|
if (data.entity1.hasComponent(HealthComponent)) {
|
||||||
|
const health = data.entity1.getComponent(HealthComponent);
|
||||||
|
health.takeDamage(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 性能优化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class OptimizedMovementSystem extends EntitySystem {
|
||||||
|
private lastUpdateTime: number = 0;
|
||||||
|
private readonly UPDATE_INTERVAL = 16; // 60FPS
|
||||||
|
|
||||||
|
process(entities: Entity[]) {
|
||||||
|
const currentTime = Time.totalTime;
|
||||||
|
|
||||||
|
// 限制更新频率
|
||||||
|
if (currentTime - this.lastUpdateTime < this.UPDATE_INTERVAL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量处理
|
||||||
|
this.processBatch(entities);
|
||||||
|
|
||||||
|
this.lastUpdateTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private processBatch(entities: Entity[]) {
|
||||||
|
// 使用for循环而不是forEach,性能更好
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
const entity = entities[i];
|
||||||
|
// 处理逻辑...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 系统的执行顺序重要吗?
|
||||||
|
|
||||||
|
A: 非常重要!合理的执行顺序可以避免逻辑错误:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 正确顺序:
|
||||||
|
// 1. 输入系统(收集玩家输入)
|
||||||
|
// 2. AI系统(敌人决策)
|
||||||
|
// 3. 移动系统(更新位置)
|
||||||
|
// 4. 碰撞系统(检测碰撞)
|
||||||
|
// 5. 渲染系统(显示画面)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 什么时候使用哪种系统类型?
|
||||||
|
|
||||||
|
A:
|
||||||
|
- **EntitySystem** - 大部分游戏逻辑(移动、AI、碰撞等)
|
||||||
|
- **ProcessingSystem** - 复杂的单实体处理(复杂AI、粒子系统)
|
||||||
|
- **IntervalSystem** - 不需要每帧执行的逻辑(生成器、自动保存)
|
||||||
|
- **PassiveSystem** - 事件响应系统(分数、音效、UI更新)
|
||||||
|
|
||||||
|
### Q: 系统可以访问其他系统吗?
|
||||||
|
|
||||||
|
A: 不建议直接访问。推荐使用事件系统进行系统间通信,保持松耦合。
|
||||||
|
|
||||||
|
### Q: 如何调试系统性能?
|
||||||
|
|
||||||
|
A: 使用框架内置的性能监控:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const monitor = PerformanceMonitor.instance;
|
||||||
|
monitor.startFrame('MovementSystem');
|
||||||
|
// 系统逻辑...
|
||||||
|
monitor.endFrame('MovementSystem');
|
||||||
|
|
||||||
|
// 查看性能报告
|
||||||
|
console.log(monitor.getReport());
|
||||||
|
```
|
||||||
|
|
||||||
|
通过合理使用这些系统类型,你可以构建出高性能、易维护的游戏逻辑!
|
||||||
651
docs/timer-guide.md
Normal file
651
docs/timer-guide.md
Normal file
@@ -0,0 +1,651 @@
|
|||||||
|
# 定时器系统使用指南
|
||||||
|
|
||||||
|
定时器系统是游戏开发中的重要工具,用于处理延迟执行、重复任务、倒计时等功能。本指南详细介绍如何使用ECS框架的定时器系统。
|
||||||
|
|
||||||
|
## 定时器基础概念
|
||||||
|
|
||||||
|
### 什么是定时器?
|
||||||
|
|
||||||
|
定时器允许你:
|
||||||
|
- ⏰ **延迟执行** - 在指定时间后执行某个操作
|
||||||
|
- 🔄 **重复执行** - 定期重复执行某个操作
|
||||||
|
- 🛑 **取消执行** - 在执行前取消定时器
|
||||||
|
- 🎯 **精确控制** - 精确控制执行时机
|
||||||
|
|
||||||
|
### 定时器的优势
|
||||||
|
|
||||||
|
相比直接在游戏循环中计时,定时器系统提供:
|
||||||
|
- 🧹 **自动管理** - 自动处理定时器的生命周期
|
||||||
|
- 🎮 **游戏时间控制** - 支持游戏暂停、时间缩放
|
||||||
|
- 💾 **内存优化** - 自动回收完成的定时器
|
||||||
|
- 🔧 **易于使用** - 简单的API调用
|
||||||
|
|
||||||
|
## 基础定时器使用
|
||||||
|
|
||||||
|
### 1. 简单延迟执行
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Core, Timer } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 3秒后执行一次
|
||||||
|
Core.schedule(3.0, false, this, (timer) => {
|
||||||
|
console.log("3秒钟到了!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 实际游戏例子:延迟显示提示
|
||||||
|
class GameTutorial {
|
||||||
|
startTutorial() {
|
||||||
|
// 2秒后显示第一个提示
|
||||||
|
Core.schedule(2.0, false, this, () => {
|
||||||
|
this.showTip("欢迎来到游戏世界!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5秒后显示移动提示
|
||||||
|
Core.schedule(5.0, false, this, () => {
|
||||||
|
this.showTip("使用WASD键移动角色");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 8秒后显示攻击提示
|
||||||
|
Core.schedule(8.0, false, this, () => {
|
||||||
|
this.showTip("按空格键攻击敌人");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private showTip(message: string) {
|
||||||
|
// 显示提示的逻辑
|
||||||
|
console.log(`提示: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 重复执行
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 每1秒执行一次,持续执行
|
||||||
|
const repeatTimer = Core.schedule(1.0, true, this, (timer) => {
|
||||||
|
console.log("每秒执行一次");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 实际游戏例子:生命值恢复
|
||||||
|
class HealthRegeneration {
|
||||||
|
private regenTimer: ITimer;
|
||||||
|
|
||||||
|
startRegeneration(entity: Entity) {
|
||||||
|
const health = entity.getComponent(HealthComponent);
|
||||||
|
|
||||||
|
// 每2秒恢复5点生命值
|
||||||
|
this.regenTimer = Core.schedule(2.0, true, this, () => {
|
||||||
|
if (health.currentHealth < health.maxHealth) {
|
||||||
|
health.currentHealth += 5;
|
||||||
|
health.currentHealth = Math.min(health.currentHealth, health.maxHealth);
|
||||||
|
|
||||||
|
console.log(`生命值恢复:${health.currentHealth}/${health.maxHealth}`);
|
||||||
|
|
||||||
|
// 满血时停止恢复
|
||||||
|
if (health.currentHealth >= health.maxHealth) {
|
||||||
|
this.stopRegeneration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRegeneration() {
|
||||||
|
if (this.regenTimer) {
|
||||||
|
this.regenTimer.stop();
|
||||||
|
this.regenTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 获取定时器引用进行控制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ITimer } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class BombTimer {
|
||||||
|
private bombTimer: ITimer;
|
||||||
|
private explosionTime: number = 5.0;
|
||||||
|
|
||||||
|
startBomb(position: { x: number; y: number }) {
|
||||||
|
console.log("炸弹已放置!5秒后爆炸...");
|
||||||
|
|
||||||
|
// 创建定时器并保存引用
|
||||||
|
this.bombTimer = Core.schedule(this.explosionTime, false, this, () => {
|
||||||
|
this.explode(position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
defuseBomb() {
|
||||||
|
if (this.bombTimer && !this.bombTimer.isDone) {
|
||||||
|
// 拆除炸弹
|
||||||
|
this.bombTimer.stop();
|
||||||
|
console.log("炸弹已被拆除!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemainingTime(): number {
|
||||||
|
if (this.bombTimer && !this.bombTimer.isDone) {
|
||||||
|
return this.explosionTime - this.bombTimer.elapsedTime;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private explode(position: { x: number; y: number }) {
|
||||||
|
console.log("💥 炸弹爆炸了!");
|
||||||
|
// 爆炸效果逻辑...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级定时器功能
|
||||||
|
|
||||||
|
### 1. 定时器链 - 顺序执行多个任务
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class CutsceneManager {
|
||||||
|
playCutscene() {
|
||||||
|
// 第一个镜头:2秒
|
||||||
|
Core.schedule(2.0, false, this, () => {
|
||||||
|
this.showScene("开场镜头");
|
||||||
|
|
||||||
|
// 第二个镜头:3秒后
|
||||||
|
Core.schedule(3.0, false, this, () => {
|
||||||
|
this.showScene("角色登场");
|
||||||
|
|
||||||
|
// 第三个镜头:2秒后
|
||||||
|
Core.schedule(2.0, false, this, () => {
|
||||||
|
this.showScene("背景介绍");
|
||||||
|
|
||||||
|
// 结束:1秒后
|
||||||
|
Core.schedule(1.0, false, this, () => {
|
||||||
|
this.endCutscene();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private showScene(sceneName: string) {
|
||||||
|
console.log(`播放场景: ${sceneName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private endCutscene() {
|
||||||
|
console.log("过场动画结束,开始游戏!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更优雅的链式写法
|
||||||
|
class ImprovedCutsceneManager {
|
||||||
|
playCutscene() {
|
||||||
|
this.scheduleSequence([
|
||||||
|
{ delay: 2.0, action: () => this.showScene("开场镜头") },
|
||||||
|
{ delay: 3.0, action: () => this.showScene("角色登场") },
|
||||||
|
{ delay: 2.0, action: () => this.showScene("背景介绍") },
|
||||||
|
{ delay: 1.0, action: () => this.endCutscene() }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleSequence(sequence: Array<{delay: number, action: () => void}>) {
|
||||||
|
let currentDelay = 0;
|
||||||
|
|
||||||
|
sequence.forEach(step => {
|
||||||
|
currentDelay += step.delay;
|
||||||
|
Core.schedule(currentDelay, false, this, step.action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 条件定时器 - 满足条件时执行
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ConditionalTimer {
|
||||||
|
waitForCondition(
|
||||||
|
condition: () => boolean,
|
||||||
|
action: () => void,
|
||||||
|
checkInterval: number = 0.1,
|
||||||
|
timeout: number = 10.0
|
||||||
|
) {
|
||||||
|
let timeElapsed = 0;
|
||||||
|
|
||||||
|
const checkTimer = Core.schedule(checkInterval, true, this, () => {
|
||||||
|
timeElapsed += checkInterval;
|
||||||
|
|
||||||
|
if (condition()) {
|
||||||
|
// 条件满足,执行动作并停止检查
|
||||||
|
checkTimer.stop();
|
||||||
|
action();
|
||||||
|
} else if (timeElapsed >= timeout) {
|
||||||
|
// 超时,停止检查
|
||||||
|
checkTimer.stop();
|
||||||
|
console.log("等待条件超时");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用例子
|
||||||
|
class WaitForPlayerExample {
|
||||||
|
waitForPlayerToReachGoal() {
|
||||||
|
const player = this.getPlayer();
|
||||||
|
const goalPosition = { x: 500, y: 300 };
|
||||||
|
|
||||||
|
this.waitForCondition(
|
||||||
|
// 条件:玩家到达目标位置
|
||||||
|
() => {
|
||||||
|
const playerPos = player.getComponent(PositionComponent);
|
||||||
|
return playerPos.distanceTo(goalPosition) < 50;
|
||||||
|
},
|
||||||
|
// 动作:触发下一关
|
||||||
|
() => {
|
||||||
|
console.log("玩家到达目标!开始下一关");
|
||||||
|
this.loadNextLevel();
|
||||||
|
},
|
||||||
|
0.1, // 每0.1秒检查一次
|
||||||
|
30.0 // 30秒后超时
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 可暂停的定时器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class PausableTimer {
|
||||||
|
private timers: ITimer[] = [];
|
||||||
|
private isPaused: boolean = false;
|
||||||
|
|
||||||
|
schedule(delay: number, repeat: boolean, callback: () => void): ITimer {
|
||||||
|
const timer = Core.schedule(delay, repeat, this, callback);
|
||||||
|
this.timers.push(timer);
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseAll() {
|
||||||
|
this.isPaused = true;
|
||||||
|
this.timers.forEach(timer => {
|
||||||
|
if (!timer.isDone) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeAll() {
|
||||||
|
if (!this.isPaused) return;
|
||||||
|
|
||||||
|
this.isPaused = false;
|
||||||
|
// 重新启动所有未完成的定时器
|
||||||
|
// 注意:这是简化实现,实际需要保存剩余时间
|
||||||
|
this.timers = this.timers.filter(timer => !timer.isDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
this.timers.forEach(timer => timer.stop());
|
||||||
|
this.timers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏暂停系统
|
||||||
|
class GamePauseSystem {
|
||||||
|
private gameTimers: PausableTimer = new PausableTimer();
|
||||||
|
private isGamePaused: boolean = false;
|
||||||
|
|
||||||
|
pauseGame() {
|
||||||
|
if (this.isGamePaused) return;
|
||||||
|
|
||||||
|
this.isGamePaused = true;
|
||||||
|
this.gameTimers.pauseAll();
|
||||||
|
|
||||||
|
// 显示暂停菜单
|
||||||
|
this.showPauseMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeGame() {
|
||||||
|
if (!this.isGamePaused) return;
|
||||||
|
|
||||||
|
this.isGamePaused = false;
|
||||||
|
this.gameTimers.resumeAll();
|
||||||
|
|
||||||
|
// 隐藏暂停菜单
|
||||||
|
this.hidePauseMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleGameTimer(delay: number, repeat: boolean, callback: () => void) {
|
||||||
|
return this.gameTimers.schedule(delay, repeat, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实际游戏应用示例
|
||||||
|
|
||||||
|
### 1. Buff/Debuff 系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class BuffSystem {
|
||||||
|
applyBuff(entity: Entity, buffType: string, duration: number) {
|
||||||
|
const buff = new BuffComponent(buffType, duration);
|
||||||
|
entity.addComponent(buff);
|
||||||
|
|
||||||
|
// 应用Buff效果
|
||||||
|
this.applyBuffEffect(entity, buffType);
|
||||||
|
|
||||||
|
// 设置定时器移除Buff
|
||||||
|
Core.schedule(duration, false, this, () => {
|
||||||
|
if (!entity.isDestroyed && entity.hasComponent(BuffComponent)) {
|
||||||
|
this.removeBuff(entity, buffType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`应用了 ${buffType} Buff,持续时间 ${duration} 秒`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyBuffEffect(entity: Entity, buffType: string) {
|
||||||
|
const stats = entity.getComponent(StatsComponent);
|
||||||
|
|
||||||
|
switch (buffType) {
|
||||||
|
case 'speed_boost':
|
||||||
|
stats.moveSpeed *= 1.5;
|
||||||
|
break;
|
||||||
|
case 'damage_boost':
|
||||||
|
stats.damage *= 2.0;
|
||||||
|
break;
|
||||||
|
case 'invincible':
|
||||||
|
entity.addComponent(new InvincibleComponent());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeBuff(entity: Entity, buffType: string) {
|
||||||
|
const buff = entity.getComponent(BuffComponent);
|
||||||
|
if (buff && buff.buffType === buffType) {
|
||||||
|
entity.removeComponent(buff);
|
||||||
|
this.removeBuffEffect(entity, buffType);
|
||||||
|
console.log(`${buffType} Buff 已过期`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 技能冷却系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SkillSystem {
|
||||||
|
private cooldowns: Map<string, number> = new Map();
|
||||||
|
|
||||||
|
useSkill(player: Entity, skillName: string): boolean {
|
||||||
|
// 检查冷却
|
||||||
|
if (this.isOnCooldown(skillName)) {
|
||||||
|
const remainingTime = this.getCooldownRemaining(skillName);
|
||||||
|
console.log(`技能冷却中,还需 ${remainingTime.toFixed(1)} 秒`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行技能
|
||||||
|
this.executeSkill(player, skillName);
|
||||||
|
|
||||||
|
// 启动冷却
|
||||||
|
const cooldownTime = this.getSkillCooldown(skillName);
|
||||||
|
this.startCooldown(skillName, cooldownTime);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startCooldown(skillName: string, duration: number) {
|
||||||
|
const endTime = Time.totalTime + duration;
|
||||||
|
this.cooldowns.set(skillName, endTime);
|
||||||
|
|
||||||
|
// 设置定时器清理冷却
|
||||||
|
Core.schedule(duration, false, this, () => {
|
||||||
|
this.cooldowns.delete(skillName);
|
||||||
|
console.log(`技能 ${skillName} 冷却完成!`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private isOnCooldown(skillName: string): boolean {
|
||||||
|
const endTime = this.cooldowns.get(skillName);
|
||||||
|
return endTime !== undefined && Time.totalTime < endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCooldownRemaining(skillName: string): number {
|
||||||
|
const endTime = this.cooldowns.get(skillName);
|
||||||
|
return endTime ? Math.max(0, endTime - Time.totalTime) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private executeSkill(player: Entity, skillName: string) {
|
||||||
|
switch (skillName) {
|
||||||
|
case 'fireball':
|
||||||
|
this.castFireball(player);
|
||||||
|
break;
|
||||||
|
case 'heal':
|
||||||
|
this.castHeal(player);
|
||||||
|
break;
|
||||||
|
case 'dash':
|
||||||
|
this.performDash(player);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSkillCooldown(skillName: string): number {
|
||||||
|
const cooldowns = {
|
||||||
|
'fireball': 3.0,
|
||||||
|
'heal': 10.0,
|
||||||
|
'dash': 5.0
|
||||||
|
};
|
||||||
|
return cooldowns[skillName] || 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 关卡时间限制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class LevelTimer {
|
||||||
|
private timeLimit: number;
|
||||||
|
private timeRemaining: number;
|
||||||
|
private timerActive: boolean = false;
|
||||||
|
private updateTimer: ITimer;
|
||||||
|
|
||||||
|
startLevel(timeLimitSeconds: number) {
|
||||||
|
this.timeLimit = timeLimitSeconds;
|
||||||
|
this.timeRemaining = timeLimitSeconds;
|
||||||
|
this.timerActive = true;
|
||||||
|
|
||||||
|
// 每秒更新倒计时
|
||||||
|
this.updateTimer = Core.schedule(1.0, true, this, () => {
|
||||||
|
this.updateCountdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`关卡开始!时间限制:${timeLimitSeconds} 秒`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCountdown() {
|
||||||
|
if (!this.timerActive) return;
|
||||||
|
|
||||||
|
this.timeRemaining--;
|
||||||
|
|
||||||
|
// 更新UI显示
|
||||||
|
this.updateTimerUI(this.timeRemaining);
|
||||||
|
|
||||||
|
// 时间警告
|
||||||
|
if (this.timeRemaining === 30) {
|
||||||
|
console.log("⚠️ 警告:还剩30秒!");
|
||||||
|
this.playWarningSound();
|
||||||
|
} else if (this.timeRemaining === 10) {
|
||||||
|
console.log("🚨 紧急:还剩10秒!");
|
||||||
|
this.playUrgentSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间到
|
||||||
|
if (this.timeRemaining <= 0) {
|
||||||
|
this.timeUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private timeUp() {
|
||||||
|
this.timerActive = false;
|
||||||
|
this.updateTimer.stop();
|
||||||
|
|
||||||
|
console.log("⏰ 时间到!游戏结束");
|
||||||
|
|
||||||
|
// 触发游戏结束
|
||||||
|
Core.emitter.emit('level:timeout');
|
||||||
|
}
|
||||||
|
|
||||||
|
completeLevel() {
|
||||||
|
if (this.timerActive) {
|
||||||
|
this.timerActive = false;
|
||||||
|
this.updateTimer.stop();
|
||||||
|
|
||||||
|
const completionTime = this.timeLimit - this.timeRemaining;
|
||||||
|
console.log(`🎉 关卡完成!用时:${completionTime} 秒`);
|
||||||
|
|
||||||
|
// 根据剩余时间给予奖励
|
||||||
|
this.calculateTimeBonus(this.timeRemaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateTimeBonus(timeLeft: number) {
|
||||||
|
const bonus = Math.floor(timeLeft * 10); // 每秒剩余10分
|
||||||
|
if (bonus > 0) {
|
||||||
|
console.log(`时间奖励:${bonus} 分`);
|
||||||
|
Core.emitter.emit('score:time_bonus', { bonus });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimeRemaining(): number {
|
||||||
|
return this.timeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimeRemainingFormatted(): string {
|
||||||
|
const minutes = Math.floor(this.timeRemaining / 60);
|
||||||
|
const seconds = this.timeRemaining % 60;
|
||||||
|
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 定时器性能优化
|
||||||
|
|
||||||
|
### 1. 定时器池化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TimerPool {
|
||||||
|
private static instance: TimerPool;
|
||||||
|
private timerPool: ITimer[] = [];
|
||||||
|
|
||||||
|
static getInstance(): TimerPool {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new TimerPool();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimer(): ITimer {
|
||||||
|
return this.timerPool.pop() || this.createTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseTimer(timer: ITimer) {
|
||||||
|
timer.stop();
|
||||||
|
this.timerPool.push(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTimer(): ITimer {
|
||||||
|
// 创建新定时器的逻辑
|
||||||
|
return new Timer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 批量定时器管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class BatchTimerManager {
|
||||||
|
private timers: Set<ITimer> = new Set();
|
||||||
|
|
||||||
|
scheduleMany(configs: Array<{delay: number, repeat: boolean, callback: () => void}>) {
|
||||||
|
return configs.map(config => {
|
||||||
|
const timer = Core.schedule(config.delay, config.repeat, this, config.callback);
|
||||||
|
this.timers.add(timer);
|
||||||
|
return timer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAll() {
|
||||||
|
this.timers.forEach(timer => timer.stop());
|
||||||
|
this.timers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
// 清理已完成的定时器
|
||||||
|
this.timers.forEach(timer => {
|
||||||
|
if (timer.isDone) {
|
||||||
|
this.timers.delete(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题和最佳实践
|
||||||
|
|
||||||
|
### Q: 定时器会自动清理吗?
|
||||||
|
|
||||||
|
A: 是的,完成的定时器会自动清理。但如果需要提前停止,记得调用 `timer.stop()`。
|
||||||
|
|
||||||
|
### Q: 定时器会受到游戏暂停影响吗?
|
||||||
|
|
||||||
|
A: 定时器使用游戏时间,如果实现了时间缩放功能,定时器会相应调整。
|
||||||
|
|
||||||
|
### Q: 如何实现精确的帧同步定时器?
|
||||||
|
|
||||||
|
A: 使用帧计数而不是时间:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class FrameTimer {
|
||||||
|
private frameCount: number = 0;
|
||||||
|
private targetFrame: number;
|
||||||
|
|
||||||
|
scheduleFrames(frames: number, callback: () => void) {
|
||||||
|
this.targetFrame = this.frameCount + frames;
|
||||||
|
|
||||||
|
const checkFrame = () => {
|
||||||
|
this.frameCount++;
|
||||||
|
if (this.frameCount >= this.targetFrame) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(checkFrame);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(checkFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 如何避免定时器内存泄漏?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 及时停止不需要的定时器
|
||||||
|
2. 在对象销毁时清理所有定时器
|
||||||
|
3. 使用弱引用避免循环引用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SafeTimerUser {
|
||||||
|
private timers: ITimer[] = [];
|
||||||
|
|
||||||
|
scheduleTimer(delay: number, callback: () => void) {
|
||||||
|
const timer = Core.schedule(delay, false, this, callback);
|
||||||
|
this.timers.push(timer);
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// 清理所有定时器
|
||||||
|
this.timers.forEach(timer => timer.stop());
|
||||||
|
this.timers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
定时器是游戏开发中非常有用的工具,合理使用可以让你的游戏逻辑更加优雅和高效!
|
||||||
600
docs/use-cases.md
Normal file
600
docs/use-cases.md
Normal file
@@ -0,0 +1,600 @@
|
|||||||
|
# ECS框架使用场景示例
|
||||||
|
|
||||||
|
本文档展示ECS框架在不同类型游戏中的具体应用案例。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [小型休闲游戏](#小型休闲游戏)
|
||||||
|
2. [中型动作游戏](#中型动作游戏)
|
||||||
|
3. [大型策略游戏](#大型策略游戏)
|
||||||
|
4. [MMO游戏](#mmo游戏)
|
||||||
|
|
||||||
|
## 小型休闲游戏
|
||||||
|
|
||||||
|
### 场景:简单的飞机大战游戏
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
Scene,
|
||||||
|
EntityManager,
|
||||||
|
Entity,
|
||||||
|
Component,
|
||||||
|
EntitySystem,
|
||||||
|
Matcher
|
||||||
|
} from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 组件定义
|
||||||
|
class PositionComponent extends Component {
|
||||||
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VelocityComponent extends Component {
|
||||||
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerComponent extends Component {}
|
||||||
|
class EnemyComponent extends Component {}
|
||||||
|
class BulletComponent extends Component {}
|
||||||
|
|
||||||
|
// 游戏管理器
|
||||||
|
class PlaneWarGame {
|
||||||
|
private scene: Scene;
|
||||||
|
private entityManager: EntityManager;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene = new Scene();
|
||||||
|
this.entityManager = new EntityManager();
|
||||||
|
this.setupGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupGame(): void {
|
||||||
|
// 创建玩家
|
||||||
|
const player = this.entityManager.createEntity("Player");
|
||||||
|
player.addComponent(new PositionComponent(400, 500));
|
||||||
|
player.addComponent(new VelocityComponent(0, 0));
|
||||||
|
player.addComponent(new PlayerComponent());
|
||||||
|
player.tag = 1; // 玩家标签
|
||||||
|
|
||||||
|
// 创建敌人
|
||||||
|
this.spawnEnemies(5);
|
||||||
|
|
||||||
|
// 添加系统
|
||||||
|
this.scene.addEntityProcessor(new MovementSystem());
|
||||||
|
this.scene.addEntityProcessor(new CollisionSystem());
|
||||||
|
this.scene.addEntityProcessor(new CleanupSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnEnemies(count: number): void {
|
||||||
|
const enemies = this.scene.createEntities(count, "Enemy");
|
||||||
|
enemies.forEach((enemy, index) => {
|
||||||
|
enemy.addComponent(new PositionComponent(
|
||||||
|
Math.random() * 800,
|
||||||
|
-50
|
||||||
|
));
|
||||||
|
enemy.addComponent(new VelocityComponent(0, 100));
|
||||||
|
enemy.addComponent(new EnemyComponent());
|
||||||
|
enemy.tag = 2; // 敌人标签
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(): void {
|
||||||
|
this.scene.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动系统
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
const movingEntities = this.scene.querySystem.queryAll(
|
||||||
|
PositionComponent,
|
||||||
|
VelocityComponent
|
||||||
|
);
|
||||||
|
|
||||||
|
movingEntities.entities.forEach(entity => {
|
||||||
|
const pos = entity.getComponent(PositionComponent);
|
||||||
|
const vel = entity.getComponent(VelocityComponent);
|
||||||
|
|
||||||
|
pos.x += vel.x * Time.deltaTime;
|
||||||
|
pos.y += vel.y * Time.deltaTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## 中型动作游戏
|
||||||
|
|
||||||
|
### 场景:2D平台跳跃游戏
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 更复杂的组件
|
||||||
|
class HealthComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public maxHealth: number = 100,
|
||||||
|
public currentHealth: number = 100
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimationComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public currentAnimation: string = "idle",
|
||||||
|
public frameIndex: number = 0,
|
||||||
|
public frameTime: number = 0
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhysicsComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public mass: number = 1,
|
||||||
|
public friction: number = 0.8,
|
||||||
|
public isGrounded: boolean = false
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 平台游戏管理器
|
||||||
|
class PlatformGame {
|
||||||
|
private scene: Scene;
|
||||||
|
private entityManager: EntityManager;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene = new Scene();
|
||||||
|
this.entityManager = new EntityManager();
|
||||||
|
this.setupGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupGame(): void {
|
||||||
|
// 创建玩家
|
||||||
|
this.createPlayer();
|
||||||
|
|
||||||
|
// 创建敌人
|
||||||
|
this.createEnemies(10);
|
||||||
|
|
||||||
|
// 创建平台
|
||||||
|
this.createPlatforms();
|
||||||
|
|
||||||
|
// 添加系统(按更新顺序)
|
||||||
|
this.scene.addEntityProcessor(new InputSystem()).updateOrder = 10;
|
||||||
|
this.scene.addEntityProcessor(new PhysicsSystem()).updateOrder = 20;
|
||||||
|
this.scene.addEntityProcessor(new AnimationSystem()).updateOrder = 30;
|
||||||
|
this.scene.addEntityProcessor(new CombatSystem()).updateOrder = 40;
|
||||||
|
this.scene.addEntityProcessor(new RenderSystem()).updateOrder = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPlayer(): void {
|
||||||
|
const player = this.entityManager.createEntity("Player");
|
||||||
|
player.addComponent(new PositionComponent(100, 300));
|
||||||
|
player.addComponent(new VelocityComponent(0, 0));
|
||||||
|
player.addComponent(new HealthComponent(100));
|
||||||
|
player.addComponent(new AnimationComponent("idle"));
|
||||||
|
player.addComponent(new PhysicsComponent(1, 0.8));
|
||||||
|
player.tag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createEnemies(count: number): void {
|
||||||
|
const enemies = this.scene.createEntities(count, "Enemy");
|
||||||
|
enemies.forEach((enemy, index) => {
|
||||||
|
enemy.addComponent(new PositionComponent(
|
||||||
|
200 + index * 100,
|
||||||
|
300
|
||||||
|
));
|
||||||
|
enemy.addComponent(new VelocityComponent(0, 0));
|
||||||
|
enemy.addComponent(new HealthComponent(50));
|
||||||
|
enemy.addComponent(new AnimationComponent("patrol"));
|
||||||
|
enemy.addComponent(new PhysicsComponent(0.8, 0.9));
|
||||||
|
enemy.tag = 2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPlatforms(): void {
|
||||||
|
const platforms = this.scene.createEntities(5, "Platform");
|
||||||
|
platforms.forEach((platform, index) => {
|
||||||
|
platform.addComponent(new PositionComponent(
|
||||||
|
index * 200,
|
||||||
|
400 + Math.random() * 100
|
||||||
|
));
|
||||||
|
platform.tag = 3; // 平台标签
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## 大型策略游戏
|
||||||
|
|
||||||
|
### 场景:即时战略游戏
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 策略游戏组件
|
||||||
|
class UnitComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public unitType: string,
|
||||||
|
public playerId: number,
|
||||||
|
public level: number = 1
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AIComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public state: string = "idle",
|
||||||
|
public target: Entity | null = null,
|
||||||
|
public lastDecisionTime: number = 0
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public gold: number = 0,
|
||||||
|
public wood: number = 0,
|
||||||
|
public food: number = 0
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 策略游戏管理器
|
||||||
|
class StrategyGame {
|
||||||
|
private scene: Scene;
|
||||||
|
private entityManager: EntityManager;
|
||||||
|
private players: Map<number, Entity> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene = new Scene();
|
||||||
|
this.entityManager = new EntityManager();
|
||||||
|
this.setupGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupGame(): void {
|
||||||
|
// 创建玩家
|
||||||
|
this.createPlayers(4);
|
||||||
|
|
||||||
|
// 为每个玩家创建初始单位
|
||||||
|
this.players.forEach((player, playerId) => {
|
||||||
|
this.createInitialUnits(playerId, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加系统
|
||||||
|
this.scene.addEntityProcessor(new AISystem()).updateOrder = 10;
|
||||||
|
this.scene.addEntityProcessor(new CombatSystem()).updateOrder = 20;
|
||||||
|
this.scene.addEntityProcessor(new ResourceSystem()).updateOrder = 30;
|
||||||
|
this.scene.addEntityProcessor(new UnitManagementSystem()).updateOrder = 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPlayers(count: number): void {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const player = this.entityManager.createEntity(`Player_${i}`);
|
||||||
|
player.addComponent(new ResourceComponent(1000, 500, 100));
|
||||||
|
player.tag = 10 + i; // 玩家标签从10开始
|
||||||
|
this.players.set(i, player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInitialUnits(playerId: number, count: number): void {
|
||||||
|
const units = this.scene.createEntities(count, `Unit_${playerId}`);
|
||||||
|
|
||||||
|
units.forEach((unit, index) => {
|
||||||
|
unit.addComponent(new PositionComponent(
|
||||||
|
playerId * 200 + Math.random() * 100,
|
||||||
|
playerId * 200 + Math.random() * 100
|
||||||
|
));
|
||||||
|
unit.addComponent(new UnitComponent("warrior", playerId));
|
||||||
|
unit.addComponent(new HealthComponent(100));
|
||||||
|
unit.addComponent(new AIComponent());
|
||||||
|
unit.tag = 20 + playerId; // 单位标签
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量单位操作
|
||||||
|
public createArmy(playerId: number, unitType: string, count: number): Entity[] {
|
||||||
|
const units = this.scene.createEntities(count, `${unitType}_${playerId}`);
|
||||||
|
|
||||||
|
// 批量配置组件
|
||||||
|
units.forEach(unit => {
|
||||||
|
unit.addComponent(new UnitComponent(unitType, playerId));
|
||||||
|
unit.addComponent(new HealthComponent(100));
|
||||||
|
unit.addComponent(new PositionComponent(
|
||||||
|
Math.random() * 1000,
|
||||||
|
Math.random() * 1000
|
||||||
|
));
|
||||||
|
unit.tag = 20 + playerId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return units;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询玩家的所有单位
|
||||||
|
public getPlayerUnits(playerId: number): Entity[] {
|
||||||
|
return this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(UnitComponent)
|
||||||
|
.withTag(20 + playerId)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询特定类型的单位
|
||||||
|
public getUnitsByType(unitType: string): Entity[] {
|
||||||
|
return this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(UnitComponent)
|
||||||
|
.where(entity => {
|
||||||
|
const unit = entity.getComponent(UnitComponent);
|
||||||
|
return unit && unit.unitType === unitType;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI系统
|
||||||
|
class AISystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(AIComponent, UnitComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
const aiUnits = this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(AIComponent, UnitComponent)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
aiUnits.forEach(unit => {
|
||||||
|
this.processAI(unit);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private processAI(unit: Entity): void {
|
||||||
|
const ai = unit.getComponent(AIComponent);
|
||||||
|
const unitComp = unit.getComponent(UnitComponent);
|
||||||
|
|
||||||
|
if (!ai || !unitComp) return;
|
||||||
|
|
||||||
|
// 简单AI逻辑
|
||||||
|
switch (ai.state) {
|
||||||
|
case "idle":
|
||||||
|
this.findTarget(unit);
|
||||||
|
break;
|
||||||
|
case "attack":
|
||||||
|
this.attackTarget(unit);
|
||||||
|
break;
|
||||||
|
case "move":
|
||||||
|
this.moveToTarget(unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findTarget(unit: Entity): void {
|
||||||
|
const unitComp = unit.getComponent(UnitComponent);
|
||||||
|
if (!unitComp) return;
|
||||||
|
|
||||||
|
// 查找敌方单位
|
||||||
|
const enemies = this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(UnitComponent)
|
||||||
|
.where(entity => {
|
||||||
|
const enemyUnit = entity.getComponent(UnitComponent);
|
||||||
|
return enemyUnit && enemyUnit.playerId !== unitComp.playerId;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
if (enemies.length > 0) {
|
||||||
|
const ai = unit.getComponent(AIComponent);
|
||||||
|
if (ai) {
|
||||||
|
ai.target = enemies[0];
|
||||||
|
ai.state = "attack";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private attackTarget(unit: Entity): void {
|
||||||
|
// 攻击逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private moveToTarget(unit: Entity): void {
|
||||||
|
// 移动逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## MMO游戏
|
||||||
|
|
||||||
|
### 场景:大型多人在线游戏
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// MMO特有组件
|
||||||
|
class NetworkComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public playerId: string,
|
||||||
|
public isLocal: boolean = false,
|
||||||
|
public lastSyncTime: number = 0
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InventoryComponent extends Component {
|
||||||
|
public items: Map<string, number> = new Map();
|
||||||
|
|
||||||
|
addItem(itemId: string, count: number): void {
|
||||||
|
const current = this.items.get(itemId) || 0;
|
||||||
|
this.items.set(itemId, current + count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GuildComponent extends Component {
|
||||||
|
constructor(
|
||||||
|
public guildId: string,
|
||||||
|
public rank: string = "member"
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MMO游戏管理器
|
||||||
|
class MMOGame {
|
||||||
|
private scene: Scene;
|
||||||
|
private entityManager: EntityManager;
|
||||||
|
private localPlayerId: string;
|
||||||
|
|
||||||
|
constructor(localPlayerId: string) {
|
||||||
|
this.scene = new Scene();
|
||||||
|
this.entityManager = new EntityManager();
|
||||||
|
this.localPlayerId = localPlayerId;
|
||||||
|
this.setupGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupGame(): void {
|
||||||
|
// 添加MMO特有系统
|
||||||
|
this.scene.addEntityProcessor(new NetworkSyncSystem()).updateOrder = 5;
|
||||||
|
this.scene.addEntityProcessor(new PlayerSystem()).updateOrder = 10;
|
||||||
|
this.scene.addEntityProcessor(new NPCSystem()).updateOrder = 15;
|
||||||
|
this.scene.addEntityProcessor(new GuildSystem()).updateOrder = 20;
|
||||||
|
this.scene.addEntityProcessor(new InventorySystem()).updateOrder = 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建玩家角色
|
||||||
|
public createPlayer(playerId: string, isLocal: boolean = false): Entity {
|
||||||
|
const player = this.entityManager.createEntity(`Player_${playerId}`);
|
||||||
|
player.addComponent(new PositionComponent(0, 0));
|
||||||
|
player.addComponent(new HealthComponent(1000));
|
||||||
|
player.addComponent(new NetworkComponent(playerId, isLocal));
|
||||||
|
player.addComponent(new InventoryComponent());
|
||||||
|
player.tag = isLocal ? 1 : 2; // 本地玩家标签1,远程玩家标签2
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量创建NPC
|
||||||
|
public createNPCs(count: number): Entity[] {
|
||||||
|
const npcs = this.scene.createEntities(count, "NPC");
|
||||||
|
|
||||||
|
npcs.forEach((npc, index) => {
|
||||||
|
npc.addComponent(new PositionComponent(
|
||||||
|
Math.random() * 2000,
|
||||||
|
Math.random() * 2000
|
||||||
|
));
|
||||||
|
npc.addComponent(new HealthComponent(500));
|
||||||
|
npc.addComponent(new AIComponent("patrol"));
|
||||||
|
npc.tag = 3; // NPC标签
|
||||||
|
});
|
||||||
|
|
||||||
|
return npcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询附近的玩家
|
||||||
|
public getNearbyPlayers(centerX: number, centerY: number, radius: number): Entity[] {
|
||||||
|
return this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(PositionComponent, NetworkComponent)
|
||||||
|
.where(entity => {
|
||||||
|
const pos = entity.getComponent(PositionComponent);
|
||||||
|
if (!pos) return false;
|
||||||
|
|
||||||
|
const distance = Math.sqrt(
|
||||||
|
Math.pow(pos.x - centerX, 2) +
|
||||||
|
Math.pow(pos.y - centerY, 2)
|
||||||
|
);
|
||||||
|
return distance <= radius;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询公会成员
|
||||||
|
public getGuildMembers(guildId: string): Entity[] {
|
||||||
|
return this.entityManager
|
||||||
|
.query()
|
||||||
|
.withAll(GuildComponent, NetworkComponent)
|
||||||
|
.where(entity => {
|
||||||
|
const guild = entity.getComponent(GuildComponent);
|
||||||
|
return guild && guild.guildId === guildId;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取在线玩家统计
|
||||||
|
public getOnlinePlayerStats(): any {
|
||||||
|
const allPlayers = this.entityManager.getEntitiesWithComponent(NetworkComponent);
|
||||||
|
const localPlayers = this.entityManager.getEntitiesByTag(1);
|
||||||
|
const remotePlayers = this.entityManager.getEntitiesByTag(2);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: allPlayers.length,
|
||||||
|
local: localPlayers.length,
|
||||||
|
remote: remotePlayers.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络同步系统
|
||||||
|
class NetworkSyncSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(NetworkComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
const networkEntities = this.entityManager.getEntitiesWithComponent(NetworkComponent);
|
||||||
|
|
||||||
|
networkEntities.forEach(entity => {
|
||||||
|
const network = entity.getComponent(NetworkComponent);
|
||||||
|
if (!network || network.isLocal) return;
|
||||||
|
|
||||||
|
// 同步远程实体数据
|
||||||
|
this.syncRemoteEntity(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncRemoteEntity(entity: Entity): void {
|
||||||
|
// 网络同步逻辑
|
||||||
|
const network = entity.getComponent(NetworkComponent);
|
||||||
|
if (!network) return;
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
if (currentTime - network.lastSyncTime > 100) { // 100ms同步一次
|
||||||
|
// 发送同步数据
|
||||||
|
network.lastSyncTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## 性能优化建议
|
||||||
|
|
||||||
|
### 小型游戏(< 1000实体)
|
||||||
|
- 使用简单的查询方法
|
||||||
|
- 不需要复杂的优化
|
||||||
|
- 重点关注代码可读性
|
||||||
|
|
||||||
|
### 中型游戏(1000-10000实体)
|
||||||
|
- 使用标签查询优化性能
|
||||||
|
- 实现基础的对象池
|
||||||
|
- 缓存频繁查询的结果
|
||||||
|
|
||||||
|
### 大型游戏(10000-100000实体)
|
||||||
|
- 使用时间分片处理大量实体
|
||||||
|
- 实现空间分区优化邻近查询
|
||||||
|
- 使用批量操作减少单次调用开销
|
||||||
|
|
||||||
|
### MMO游戏(100000+实体)
|
||||||
|
- 实现分区管理,只处理相关区域的实体
|
||||||
|
- 使用异步处理避免阻塞主线程
|
||||||
|
- 实现智能缓存和预加载机制
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
ECS框架的灵活性使其能够适应各种规模的游戏开发需求:
|
||||||
|
|
||||||
|
1. **小型游戏**:简单直接,快速开发
|
||||||
|
2. **中型游戏**:平衡性能和复杂度
|
||||||
|
3. **大型游戏**:充分利用优化特性
|
||||||
|
4. **MMO游戏**:处理海量实体和复杂交互
|
||||||
|
|
||||||
|
选择合适的架构模式和优化策略,可以让ECS框架在不同场景下都发挥最佳性能。
|
||||||
@@ -181,8 +181,8 @@ class ComponentCache {
|
|||||||
* // 获取组件
|
* // 获取组件
|
||||||
* const health = entity.getComponent(HealthComponent);
|
* const health = entity.getComponent(HealthComponent);
|
||||||
*
|
*
|
||||||
* // 设置位置
|
* // 添加位置组件
|
||||||
* entity.position = new Vector2(100, 200);
|
* entity.addComponent(new PositionComponent(100, 200));
|
||||||
*
|
*
|
||||||
* // 添加子实体
|
* // 添加子实体
|
||||||
* const weapon = new Entity("Weapon", 2);
|
* const weapon = new Entity("Weapon", 2);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { EntitySystem } from './Systems/EntitySystem';
|
|||||||
import { ComponentStorageManager } from './Core/ComponentStorage';
|
import { ComponentStorageManager } from './Core/ComponentStorage';
|
||||||
import { QuerySystem } from './Core/QuerySystem';
|
import { QuerySystem } from './Core/QuerySystem';
|
||||||
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
|
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
|
||||||
import type { IScene } from '../Types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 游戏场景类
|
* 游戏场景类
|
||||||
|
|||||||
@@ -2,27 +2,6 @@
|
|||||||
* 框架核心类型定义
|
* 框架核心类型定义
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** 更新顺序比较器接口 */
|
|
||||||
export interface IUpdateOrderComparable {
|
|
||||||
updateOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 日志类型枚举 */
|
|
||||||
export enum LogType {
|
|
||||||
Error = 0,
|
|
||||||
Assert = 1,
|
|
||||||
Warning = 2,
|
|
||||||
Log = 3,
|
|
||||||
Exception = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 组件变换类型枚举 */
|
|
||||||
export enum ComponentTransform {
|
|
||||||
Position = 1,
|
|
||||||
Scale = 2,
|
|
||||||
Rotation = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件接口
|
* 组件接口
|
||||||
*
|
*
|
||||||
@@ -51,136 +30,14 @@ export interface IComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体接口
|
* 系统基础接口
|
||||||
*
|
*
|
||||||
* 定义实体的基本契约,所有实体都应该实现此接口
|
* 为现有的EntitySystem类提供类型定义
|
||||||
*/
|
|
||||||
export interface IEntity {
|
|
||||||
/** 实体唯一标识符 */
|
|
||||||
readonly id: string | number;
|
|
||||||
/** 实体名称 */
|
|
||||||
name: string;
|
|
||||||
/** 实体激活状态 */
|
|
||||||
active: boolean;
|
|
||||||
/** 实体启用状态 */
|
|
||||||
enabled: boolean;
|
|
||||||
/** 实体是否已销毁 */
|
|
||||||
readonly isDestroyed: boolean;
|
|
||||||
/** 更新顺序 */
|
|
||||||
updateOrder: number;
|
|
||||||
/** 实体标签 */
|
|
||||||
tag: number;
|
|
||||||
|
|
||||||
/** 添加组件 */
|
|
||||||
addComponent<T extends IComponent>(component: T): T;
|
|
||||||
/** 创建并添加组件 */
|
|
||||||
createComponent<T extends IComponent>(componentType: ComponentType<T>, ...args: any[]): T;
|
|
||||||
/** 获取组件 */
|
|
||||||
getComponent<T extends IComponent>(type: ComponentType<T>): T | null;
|
|
||||||
/** 获取或创建组件 */
|
|
||||||
getOrCreateComponent<T extends IComponent>(type: ComponentType<T>, ...args: any[]): T;
|
|
||||||
/** 移除组件 */
|
|
||||||
removeComponent(component: IComponent): void;
|
|
||||||
/** 通过类型移除组件 */
|
|
||||||
removeComponentByType<T extends IComponent>(type: ComponentType<T>): T | null;
|
|
||||||
/** 检查是否有组件 */
|
|
||||||
hasComponent<T extends IComponent>(type: ComponentType<T>): boolean;
|
|
||||||
/** 获取所有指定类型的组件 */
|
|
||||||
getComponents<T extends IComponent>(type: ComponentType<T>): T[];
|
|
||||||
|
|
||||||
/** 添加子实体 */
|
|
||||||
addChild(child: IEntity): IEntity;
|
|
||||||
/** 移除子实体 */
|
|
||||||
removeChild(child: IEntity): boolean;
|
|
||||||
/** 查找子实体 */
|
|
||||||
findChild(name: string, recursive?: boolean): IEntity | null;
|
|
||||||
|
|
||||||
/** 更新实体 */
|
|
||||||
update(): void;
|
|
||||||
/** 销毁实体 */
|
|
||||||
destroy(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实体基础接口(向后兼容)
|
|
||||||
*
|
|
||||||
* 为现有的Entity类提供更灵活的类型定义
|
|
||||||
*/
|
|
||||||
export interface IEntityBase {
|
|
||||||
/** 实体唯一标识符 */
|
|
||||||
readonly id: string | number;
|
|
||||||
/** 实体名称 */
|
|
||||||
name: string;
|
|
||||||
/** 实体激活状态 */
|
|
||||||
active: boolean;
|
|
||||||
/** 实体启用状态 */
|
|
||||||
enabled: boolean;
|
|
||||||
/** 实体是否已销毁 */
|
|
||||||
readonly isDestroyed: boolean;
|
|
||||||
/** 更新顺序 */
|
|
||||||
updateOrder: number;
|
|
||||||
/** 实体标签 */
|
|
||||||
tag: number;
|
|
||||||
|
|
||||||
/** 添加组件(泛型版本) */
|
|
||||||
addComponent<T>(component: T): T;
|
|
||||||
/** 获取组件(泛型版本) */
|
|
||||||
getComponent<T>(type: ComponentClass<T>): T | null;
|
|
||||||
/** 检查是否有组件(泛型版本) */
|
|
||||||
hasComponent<T>(type: ComponentClass<T>): boolean;
|
|
||||||
|
|
||||||
/** 添加子实体 */
|
|
||||||
addChild(child: any): any;
|
|
||||||
/** 移除子实体 */
|
|
||||||
removeChild(child: any): boolean;
|
|
||||||
/** 查找子实体 */
|
|
||||||
findChild(name: string, recursive?: boolean): any;
|
|
||||||
|
|
||||||
/** 更新实体 */
|
|
||||||
update(): void;
|
|
||||||
/** 销毁实体 */
|
|
||||||
destroy(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统接口
|
|
||||||
*
|
|
||||||
* 定义系统的基本契约,所有系统都应该实现此接口
|
|
||||||
*/
|
|
||||||
export interface ISystem {
|
|
||||||
/** 系统名称 */
|
|
||||||
readonly systemName: string;
|
|
||||||
/** 系统处理的实体列表 */
|
|
||||||
readonly entities: readonly IEntity[];
|
|
||||||
/** 更新顺序/优先级 */
|
|
||||||
updateOrder: number;
|
|
||||||
/** 系统启用状态 */
|
|
||||||
enabled: boolean;
|
|
||||||
|
|
||||||
/** 系统初始化 */
|
|
||||||
initialize(): void;
|
|
||||||
/** 更新系统(主要处理阶段) */
|
|
||||||
update(): void;
|
|
||||||
/** 延迟更新系统 */
|
|
||||||
lateUpdate?(): void;
|
|
||||||
|
|
||||||
/** 当实体添加到系统时的回调 */
|
|
||||||
onEntityAdded?(entity: IEntity): void;
|
|
||||||
/** 当实体从系统移除时的回调 */
|
|
||||||
onEntityRemoved?(entity: IEntity): void;
|
|
||||||
/** 当实体组件发生变化时的回调 */
|
|
||||||
onEntityChanged?(entity: IEntity): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统基础接口(向后兼容)
|
|
||||||
*
|
|
||||||
* 为现有的EntitySystem类提供更灵活的类型定义
|
|
||||||
*/
|
*/
|
||||||
export interface ISystemBase {
|
export interface ISystemBase {
|
||||||
/** 系统名称 */
|
/** 系统名称 */
|
||||||
readonly systemName: string;
|
readonly systemName: string;
|
||||||
/** 系统处理的实体列表(泛型版本) */
|
/** 系统处理的实体列表 */
|
||||||
readonly entities: readonly any[];
|
readonly entities: readonly any[];
|
||||||
/** 更新顺序/优先级 */
|
/** 更新顺序/优先级 */
|
||||||
updateOrder: number;
|
updateOrder: number;
|
||||||
@@ -195,39 +52,6 @@ export interface ISystemBase {
|
|||||||
lateUpdate?(): void;
|
lateUpdate?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景接口
|
|
||||||
*
|
|
||||||
* 定义场景的基本契约
|
|
||||||
*/
|
|
||||||
export interface IScene {
|
|
||||||
/** 场景名称 */
|
|
||||||
name: string;
|
|
||||||
/** 场景中的实体列表 */
|
|
||||||
readonly entities: readonly IEntity[];
|
|
||||||
/** 场景中的系统列表 */
|
|
||||||
readonly systems: readonly ISystem[];
|
|
||||||
|
|
||||||
/** 创建实体 */
|
|
||||||
createEntity(name: string): IEntity;
|
|
||||||
/** 添加实体到场景 */
|
|
||||||
addEntity(entity: IEntity): void;
|
|
||||||
/** 从场景移除实体 */
|
|
||||||
removeEntity(entity: IEntity): void;
|
|
||||||
/** 查找实体 */
|
|
||||||
findEntity(name: string): IEntity | null;
|
|
||||||
|
|
||||||
/** 添加系统到场景 */
|
|
||||||
addSystem<T extends ISystem>(system: T): T;
|
|
||||||
/** 从场景移除系统 */
|
|
||||||
removeSystem(system: ISystem): void;
|
|
||||||
/** 获取系统 */
|
|
||||||
getSystem<T extends ISystem>(systemType: new (...args: any[]) => T): T | null;
|
|
||||||
|
|
||||||
/** 更新场景 */
|
|
||||||
update(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件类型定义
|
* 组件类型定义
|
||||||
*
|
*
|
||||||
@@ -235,170 +59,6 @@ export interface IScene {
|
|||||||
*/
|
*/
|
||||||
export type ComponentType<T extends IComponent = IComponent> = new (...args: any[]) => T;
|
export type ComponentType<T extends IComponent = IComponent> = new (...args: any[]) => T;
|
||||||
|
|
||||||
/**
|
|
||||||
* 原始组件类型(向后兼容)
|
|
||||||
*
|
|
||||||
* 用于与现有Component类的兼容
|
|
||||||
*/
|
|
||||||
export type ComponentClass<T = any> = new (...args: any[]) => T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实体查询匹配器接口
|
|
||||||
*
|
|
||||||
* 用于查询符合特定条件的实体
|
|
||||||
*/
|
|
||||||
export interface IMatcher {
|
|
||||||
/** 必须包含的组件类型 */
|
|
||||||
all(...componentTypes: ComponentType[]): IMatcher;
|
|
||||||
/** 至少包含其中一个组件类型 */
|
|
||||||
any(...componentTypes: ComponentType[]): IMatcher;
|
|
||||||
/** 不能包含的组件类型 */
|
|
||||||
exclude(...componentTypes: ComponentType[]): IMatcher;
|
|
||||||
/** 检查实体是否匹配 */
|
|
||||||
isInterestedEntity(entity: IEntity): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 性能监控接口
|
|
||||||
*/
|
|
||||||
export interface IPerformanceData {
|
|
||||||
/** 执行时间(毫秒) */
|
|
||||||
executionTime: number;
|
|
||||||
/** 调用次数 */
|
|
||||||
callCount: number;
|
|
||||||
/** 平均执行时间 */
|
|
||||||
averageTime: number;
|
|
||||||
/** 最大执行时间 */
|
|
||||||
maxTime: number;
|
|
||||||
/** 最小执行时间 */
|
|
||||||
minTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期管理接口
|
|
||||||
*/
|
|
||||||
export interface ILifecycle {
|
|
||||||
/** 初始化 */
|
|
||||||
initialize?(): void;
|
|
||||||
/** 启动 */
|
|
||||||
start?(): void;
|
|
||||||
/** 更新 */
|
|
||||||
update?(): void;
|
|
||||||
/** 延迟更新 */
|
|
||||||
lateUpdate?(): void;
|
|
||||||
/** 停止 */
|
|
||||||
stop?(): void;
|
|
||||||
/** 销毁 */
|
|
||||||
destroy?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实体管理器接口
|
|
||||||
*
|
|
||||||
* 提供统一的实体管理和查询机制,支持高效的实体操作
|
|
||||||
*/
|
|
||||||
export interface IEntityManager {
|
|
||||||
/** 所有实体数量 */
|
|
||||||
readonly entityCount: number;
|
|
||||||
/** 激活的实体数量 */
|
|
||||||
readonly activeEntityCount: number;
|
|
||||||
|
|
||||||
/** 创建实体 */
|
|
||||||
createEntity(name?: string): IEntity;
|
|
||||||
/** 销毁实体 */
|
|
||||||
destroyEntity(entity: IEntity | string | number): boolean;
|
|
||||||
/** 批量销毁实体 */
|
|
||||||
destroyEntities(entities: (IEntity | string | number)[]): number;
|
|
||||||
/** 销毁所有实体 */
|
|
||||||
destroyAllEntities(): number;
|
|
||||||
|
|
||||||
/** 根据ID获取实体 */
|
|
||||||
getEntity(id: string | number): IEntity | null;
|
|
||||||
/** 根据名称获取实体 */
|
|
||||||
getEntityByName(name: string): IEntity | null;
|
|
||||||
/** 根据名称获取所有实体 */
|
|
||||||
getEntitiesByName(name: string): IEntity[];
|
|
||||||
/** 根据标签获取实体 */
|
|
||||||
getEntitiesByTag(tag: number): IEntity[];
|
|
||||||
/** 获取所有实体 */
|
|
||||||
getAllEntities(): IEntity[];
|
|
||||||
/** 获取所有激活的实体 */
|
|
||||||
getActiveEntities(): IEntity[];
|
|
||||||
|
|
||||||
/** 获取拥有指定组件的实体 */
|
|
||||||
getEntitiesWithComponent<T extends IComponent>(componentType: ComponentType<T>): IEntity[];
|
|
||||||
/** 获取拥有指定组件的实体及其组件 */
|
|
||||||
getEntitiesWithComponentData<T extends IComponent>(componentType: ComponentType<T>): Array<{entity: IEntity, component: T}>;
|
|
||||||
/** 获取拥有所有指定组件的实体 */
|
|
||||||
getEntitiesWithComponents(...componentTypes: ComponentType[]): IEntity[];
|
|
||||||
/** 获取拥有任一指定组件的实体 */
|
|
||||||
getEntitiesWithAnyComponent(...componentTypes: ComponentType[]): IEntity[];
|
|
||||||
/** 获取不包含指定组件的实体 */
|
|
||||||
getEntitiesWithoutComponent<T extends IComponent>(componentType: ComponentType<T>): IEntity[];
|
|
||||||
|
|
||||||
/** 对所有实体执行操作 */
|
|
||||||
forEachEntity(action: (entity: IEntity) => void): void;
|
|
||||||
/** 对符合条件的实体执行操作 */
|
|
||||||
forEachEntityWhere(predicate: (entity: IEntity) => boolean, action: (entity: IEntity) => void): void;
|
|
||||||
/** 对拥有指定组件的实体执行操作 */
|
|
||||||
forEachEntityWithComponent<T extends IComponent>(
|
|
||||||
componentType: ComponentType<T>,
|
|
||||||
action: (entity: IEntity, component: T) => void
|
|
||||||
): void;
|
|
||||||
|
|
||||||
/** 查找第一个符合条件的实体 */
|
|
||||||
findEntity(predicate: (entity: IEntity) => boolean): IEntity | null;
|
|
||||||
/** 查找所有符合条件的实体 */
|
|
||||||
findEntities(predicate: (entity: IEntity) => boolean): IEntity[];
|
|
||||||
|
|
||||||
/** 获取统计信息 */
|
|
||||||
getStatistics(): {
|
|
||||||
totalEntities: number;
|
|
||||||
activeEntities: number;
|
|
||||||
destroyedEntities: number;
|
|
||||||
entitiesByTag: Map<number, number>;
|
|
||||||
componentsCount: Map<string, number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 清理已销毁的实体 */
|
|
||||||
cleanup(): number;
|
|
||||||
/** 压缩存储空间 */
|
|
||||||
compact(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实体查询构建器接口
|
|
||||||
*
|
|
||||||
* 提供流式API构建复杂的实体查询条件
|
|
||||||
*/
|
|
||||||
export interface IEntityQueryBuilder {
|
|
||||||
/** 必须包含所有指定组件 */
|
|
||||||
withAll(...componentTypes: ComponentType[]): IEntityQueryBuilder;
|
|
||||||
/** 必须包含任一指定组件 */
|
|
||||||
withAny(...componentTypes: ComponentType[]): IEntityQueryBuilder;
|
|
||||||
/** 必须不包含指定组件 */
|
|
||||||
without(...componentTypes: ComponentType[]): IEntityQueryBuilder;
|
|
||||||
/** 必须包含指定标签 */
|
|
||||||
withTag(tag: number): IEntityQueryBuilder;
|
|
||||||
/** 必须不包含指定标签 */
|
|
||||||
withoutTag(tag: number): IEntityQueryBuilder;
|
|
||||||
/** 必须处于激活状态 */
|
|
||||||
active(): IEntityQueryBuilder;
|
|
||||||
/** 必须处于启用状态 */
|
|
||||||
enabled(): IEntityQueryBuilder;
|
|
||||||
/** 自定义过滤条件 */
|
|
||||||
where(predicate: (entity: IEntity) => boolean): IEntityQueryBuilder;
|
|
||||||
|
|
||||||
/** 执行查询,返回所有匹配的实体 */
|
|
||||||
execute(): IEntity[];
|
|
||||||
/** 执行查询,返回第一个匹配的实体 */
|
|
||||||
first(): IEntity | null;
|
|
||||||
/** 执行查询,返回匹配实体的数量 */
|
|
||||||
count(): number;
|
|
||||||
/** 对查询结果执行操作 */
|
|
||||||
forEach(action: (entity: IEntity) => void): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事件总线接口
|
* 事件总线接口
|
||||||
* 提供类型安全的事件发布订阅机制
|
* 提供类型安全的事件发布订阅机制
|
||||||
|
|||||||
@@ -16,6 +16,20 @@ export class Timer implements ITimer{
|
|||||||
return this.context as T;
|
return this.context as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时器是否已完成
|
||||||
|
*/
|
||||||
|
public get isDone(): boolean {
|
||||||
|
return this._isDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时器已运行的时间
|
||||||
|
*/
|
||||||
|
public get elapsedTime(): number {
|
||||||
|
return this._elapsedTime;
|
||||||
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this._elapsedTime = 0;
|
this._elapsedTime = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
Reference in New Issue
Block a user