更新文档
This commit is contained in:
61
.github/workflows/docs.yml
vendored
Normal file
61
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Deploy Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build core package
|
||||||
|
run: npm run build:core
|
||||||
|
|
||||||
|
- name: Generate API documentation
|
||||||
|
run: npm run docs:api
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: npm run docs:build
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: docs/.vitepress/dist
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -55,6 +55,8 @@ pnpm-lock.yaml
|
|||||||
# 文档生成
|
# 文档生成
|
||||||
docs/api/
|
docs/api/
|
||||||
docs/build/
|
docs/build/
|
||||||
|
docs/.vitepress/cache/
|
||||||
|
docs/.vitepress/dist/
|
||||||
|
|
||||||
# 备份文件
|
# 备份文件
|
||||||
*.bak
|
*.bak
|
||||||
|
|||||||
444
README.md
444
README.md
@@ -1,180 +1,20 @@
|
|||||||
# ECS Framework
|
# ECS Framework
|
||||||
|
|
||||||
[](https://git.io/typing-svg)
|
|
||||||
|
|
||||||
[](https://github.com/esengine/ecs-framework/actions)
|
[](https://github.com/esengine/ecs-framework/actions)
|
||||||
[](https://badge.fury.io/js/%40esengine%2Fecs-framework)
|
[](https://badge.fury.io/js/%40esengine%2Fecs-framework)
|
||||||
[](https://www.typescriptlang.org/)
|
[](https://www.typescriptlang.org/)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://github.com/esengine/ecs-framework/stargazers)
|
[](https://github.com/esengine/ecs-framework/stargazers)
|
||||||
|
|
||||||
TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。
|
一个高性能的 TypeScript ECS (Entity-Component-System) 框架,专为现代游戏开发而设计。
|
||||||
|
|
||||||
## 项目特色
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
[](https://store.cocos.com/app/detail/7823)
|
|
||||||
[](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 架构原理
|
|
||||||
|
|
||||||
ECS Framework 采用多World + 多Scene的现代化架构设计:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
subgraph Main["🎮 ECS Framework - 多World・多Scene架构"]
|
|
||||||
direction TB
|
|
||||||
|
|
||||||
subgraph CoreLayer["⚙️ 核心层 (Core Foundation)"]
|
|
||||||
direction LR
|
|
||||||
Core["🔧 <b>Core</b><br/>📋 生命周期管理<br/>⚙️ 配置系统<br/>🔗 平台兼容"]
|
|
||||||
Registry["📝 <b>ComponentRegistry</b><br/>🏷️ 类型注册<br/>✨ 装饰器支持<br/>🔒 类型安全"]
|
|
||||||
Pool["🔢 <b>IdentifierPool</b><br/>🆔 实体ID分配<br/>♻️ ID回收<br/>📊 BigInt兼容"]
|
|
||||||
PoolMgr["♻️ <b>PoolManager</b><br/>🎯 对象池<br/>⚡ 内存优化<br/>📈 性能提升"]
|
|
||||||
EventBus["📡 <b>EventBus</b><br/>🔄 事件系统<br/>⚡ 异步/同步<br/>🎭 类型安全"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph WorldLayer["🌍 世界管理层 (World Management)"]
|
|
||||||
direction TB
|
|
||||||
WorldMgr["🗺️ <b>WorldManager</b><br/>🚀 多World调度<br/>📊 资源管理<br/>🔍 统计监控<br/>🧹 自动清理"]
|
|
||||||
|
|
||||||
subgraph WorldsContainer["多World容器"]
|
|
||||||
direction LR
|
|
||||||
World1["🌐 <b>GameWorld</b><br/>🎮 游戏逻辑<br/>🌟 全局系统<br/>🔄 跨Scene业务"]
|
|
||||||
World2["🌐 <b>UIWorld</b><br/>🎨 界面管理<br/>⚡ 独立更新<br/>🔒 资源隔离"]
|
|
||||||
end
|
|
||||||
|
|
||||||
GlobalSys["🎭 <b>Global Systems</b><br/>🌐 NetworkSync<br/>👥 PlayerMgmt<br/>📡 跨Scene通信"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph SceneLayer["🎬 场景层 (Scene Management)"]
|
|
||||||
direction LR
|
|
||||||
Scene1["🎯 <b>BattleScene</b><br/>⚔️ 实体管理<br/>🎪 系统调度<br/>⚡ 高性能处理"]
|
|
||||||
Scene2["🎯 <b>MenuScene</b><br/>🎨 界面逻辑<br/>🔄 生命周期<br/>💾 状态管理"]
|
|
||||||
Scene3["🎯 <b>UIScene</b><br/>📦 组件存储<br/>🔍 查询引擎<br/>🎭 交互处理"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph ECLayer["🤖 实体组件层 (Entity-Component System)"]
|
|
||||||
direction TB
|
|
||||||
|
|
||||||
subgraph EntityMgmt["📦 实体管理 (Entity Management)"]
|
|
||||||
direction LR
|
|
||||||
EntityMgr["👥 <b>EntityManager</b><br/>📋 集合管理<br/>🌳 层次结构<br/>⚡ 高效操作"]
|
|
||||||
Entities["🎭 <b>Entities</b><br/>👤 Player<br/>👹 Enemy<br/>💥 Bullet<br/>🎯 轻量容器"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph ComponentStore["🧩 组件存储 (Component Storage)"]
|
|
||||||
direction LR
|
|
||||||
Storage["💾 <b>ComponentStorage</b><br/>📊 SoA模式<br/>📚 AoS模式<br/>⚡ 内存优化"]
|
|
||||||
StorageMgr["🗄️ <b>StorageManager</b><br/>🏷️ 类型管理<br/>🔄 脏标记<br/>📈 性能监控"]
|
|
||||||
Components["🎲 <b>Components</b><br/>📍 Position<br/>🏃 Velocity<br/>❤️ Health<br/>📊 纯数据"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph SystemLayer["⚡ 系统层 (System Processing)"]
|
|
||||||
direction TB
|
|
||||||
|
|
||||||
subgraph EntitySys["🔄 实体系统 (Entity Systems)"]
|
|
||||||
direction LR
|
|
||||||
EntitySystems["🎪 <b>EntitySystems</b><br/>🏃 MovementSystem<br/>🎨 RenderSystem<br/>🧠 AISystem<br/>⚡ 业务逻辑"]
|
|
||||||
Processors["📋 <b>EntityProcessors</b><br/>🎯 调度管理<br/>📊 优先级<br/>⚡ 批量处理"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph QueryLayer["🔍 查询优化层 (Query & Optimization)"]
|
|
||||||
direction LR
|
|
||||||
Matcher["🎯 <b>Matcher</b><br/>✅ withAll<br/>🔄 withAny<br/>❌ withNone<br/>🌊 流式API<br/>💾 智能缓存"]
|
|
||||||
QuerySys["🔎 <b>QuerySystem</b><br/>⚡ 实时查询<br/>📦 批量优化<br/>🔄 自动更新"]
|
|
||||||
Archetype["🏗️ <b>ArchetypeSystem</b><br/>📊 组件分组<br/>🎯 原型缓存<br/>💻 BitSet优化"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph DebugLayer["📊 监控调试层 (Debug & Monitoring)"]
|
|
||||||
direction LR
|
|
||||||
Debug["🐛 <b>DebugManager</b><br/>🌐 WebSocket调试<br/>🎮 Cocos Creator插件<br/>📸 内存快照"]
|
|
||||||
Perf["📈 <b>PerformanceMonitor</b><br/>📊 性能统计<br/>⚠️ 阈值告警<br/>📱 实时监控"]
|
|
||||||
Logger["📋 <b>Logger</b><br/>📊 分级日志<br/>🎨 彩色输出<br/>🔧 自定义处理器"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
%% 连接关系 - 使用更丰富的箭头样式
|
|
||||||
Core -.->|初始化| WorldMgr
|
|
||||||
Core -.->|注册| Registry
|
|
||||||
Core -.->|分配| Pool
|
|
||||||
Core -.->|管理| PoolMgr
|
|
||||||
Core -.->|事件| EventBus
|
|
||||||
|
|
||||||
WorldMgr ==>|调度| World1
|
|
||||||
WorldMgr ==>|调度| World2
|
|
||||||
World1 -.->|管理| GlobalSys
|
|
||||||
|
|
||||||
World1 ==>|包含| Scene1
|
|
||||||
World1 ==>|包含| Scene2
|
|
||||||
World2 ==>|包含| Scene3
|
|
||||||
|
|
||||||
Scene1 -->|使用| EntityMgr
|
|
||||||
Scene2 -->|使用| EntityMgr
|
|
||||||
Scene3 -->|使用| EntityMgr
|
|
||||||
|
|
||||||
EntityMgr -->|管理| Entities
|
|
||||||
Entities -->|附加| Components
|
|
||||||
|
|
||||||
Scene1 -->|存储| Storage
|
|
||||||
Scene2 -->|存储| Storage
|
|
||||||
Scene3 -->|存储| Storage
|
|
||||||
Storage -->|管理| StorageMgr
|
|
||||||
|
|
||||||
Scene1 -->|调度| EntitySystems
|
|
||||||
Scene2 -->|调度| EntitySystems
|
|
||||||
Scene3 -->|调度| EntitySystems
|
|
||||||
EntitySystems -->|处理| Processors
|
|
||||||
|
|
||||||
EntitySystems -->|查询| Matcher
|
|
||||||
Matcher -->|缓存| QuerySys
|
|
||||||
QuerySys -->|优化| Archetype
|
|
||||||
|
|
||||||
Core -.->|调试| Debug
|
|
||||||
Core -.->|监控| Perf
|
|
||||||
Core -.->|日志| Logger
|
|
||||||
|
|
||||||
%% 样式定义 - 使用Mermaid支持的语法
|
|
||||||
classDef coreStyle fill:#E3F2FD,stroke:#1976D2,stroke-width:3px,color:#0D47A1
|
|
||||||
classDef worldStyle fill:#F3E5F5,stroke:#7B1FA2,stroke-width:3px,color:#4A148C
|
|
||||||
classDef sceneStyle fill:#FFF3E0,stroke:#F57C00,stroke-width:3px,color:#E65100
|
|
||||||
classDef entityStyle fill:#E8F5E8,stroke:#388E3C,stroke-width:3px,color:#1B5E20
|
|
||||||
classDef systemStyle fill:#FCE4EC,stroke:#C2185B,stroke-width:3px,color:#880E4F
|
|
||||||
classDef queryStyle fill:#E0F2F1,stroke:#00695C,stroke-width:3px,color:#004D40
|
|
||||||
classDef debugStyle fill:#FFF8E1,stroke:#F9A825,stroke-width:3px,color:#FF8F00
|
|
||||||
|
|
||||||
class Core,Registry,Pool,PoolMgr,EventBus coreStyle
|
|
||||||
class WorldMgr,World1,World2,GlobalSys worldStyle
|
|
||||||
class Scene1,Scene2,Scene3 sceneStyle
|
|
||||||
class EntityMgr,Entities,Storage,StorageMgr,Components entityStyle
|
|
||||||
class EntitySystems,Processors systemStyle
|
|
||||||
class Matcher,QuerySys,Archetype queryStyle
|
|
||||||
class Debug,Perf,Logger debugStyle
|
|
||||||
```
|
|
||||||
|
|
||||||
### 核心概念
|
|
||||||
|
|
||||||
| 概念 | 职责 | 特点 |
|
|
||||||
|------|------|------|
|
|
||||||
| **Entity** | 游戏对象唯一标识 | 轻量级容器,无业务逻辑 |
|
|
||||||
| **Component** | 纯数据结构 | 描述实体属性,支持SoA优化 |
|
|
||||||
| **System** | 业务逻辑处理 | 操作组件数据,可热插拔 |
|
|
||||||
| **Scene** | 实体和系统容器 | 独立的游戏场景 |
|
|
||||||
| **World** | Scene和全局系统容器 | 支持跨Scene的全局逻辑 |
|
|
||||||
| **WorldManager** | 多World管理 | 统一调度和资源管理 |
|
|
||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
- **完整的 TypeScript 支持** - 强类型检查和代码提示
|
- **高性能** - 针对大规模实体优化,支持SoA存储和批量处理
|
||||||
- **高效查询系统** - 流式 API 和智能缓存
|
- **类型安全** - 完整的TypeScript支持,编译时类型检查
|
||||||
- **性能优化技术** - SparseSet索引、Archetype 系统、脏标记
|
- **现代架构** - 支持多World、多Scene的分层架构设计
|
||||||
- **事件系统** - 类型安全的事件处理
|
- **开发友好** - 内置调试工具和性能监控
|
||||||
- **调试工具** - 内置性能监控和 [Cocos Creator 可视化调试插件](https://store.cocos.com/app/detail/7823)
|
- **跨平台** - 支持Cocos Creator、Laya引擎和Web平台
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@@ -184,271 +24,101 @@ npm install @esengine/ecs-framework
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 1. 基础使用
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Core, Scene, Entity, Component, EntitySystem, ECSComponent, ECSSystem, Matcher, Time } from '@esengine/ecs-framework';
|
import { Core, Scene, Component, EntitySystem, ECSComponent, ECSSystem, Matcher, Time } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
// 创建核心实例
|
|
||||||
const core = Core.create({ debug: true });
|
|
||||||
const scene = new Scene();
|
|
||||||
Core.setScene(scene);
|
|
||||||
|
|
||||||
// 定义组件
|
// 定义组件
|
||||||
@ECSComponent('PositionComponent')
|
@ECSComponent('Position')
|
||||||
class PositionComponent extends Component {
|
class Position extends Component {
|
||||||
public x: number = 0;
|
constructor(public x = 0, public y = 0) {
|
||||||
public y: number = 0;
|
|
||||||
|
|
||||||
constructor(x: number = 0, y: number = 0) {
|
|
||||||
super();
|
super();
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ECSComponent('VelocityComponent')
|
@ECSComponent('Velocity')
|
||||||
class VelocityComponent extends Component {
|
class Velocity extends Component {
|
||||||
public x: number = 0;
|
constructor(public dx = 0, public dy = 0) {
|
||||||
public y: number = 0;
|
|
||||||
|
|
||||||
constructor(x: number = 0, y: number = 0) {
|
|
||||||
super();
|
super();
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建实体
|
|
||||||
const entity = scene.createEntity("Player");
|
|
||||||
entity.addComponent(new PositionComponent(100, 100));
|
|
||||||
entity.addComponent(new VelocityComponent(5, 0));
|
|
||||||
|
|
||||||
// 创建系统
|
// 创建系统
|
||||||
@ECSSystem('MovementSystem')
|
@ECSSystem('Movement')
|
||||||
class MovementSystem extends EntitySystem {
|
class MovementSystem extends EntitySystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(Matcher.all(PositionComponent, VelocityComponent));
|
super(Matcher.all(Position, Velocity));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override process(entities: Entity[]) {
|
protected process(entities: readonly Entity[]): void {
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
const position = entity.getComponent(PositionComponent)!;
|
const position = entity.getComponent(Position)!;
|
||||||
const velocity = entity.getComponent(VelocityComponent)!;
|
const velocity = entity.getComponent(Velocity)!;
|
||||||
|
|
||||||
position.x += velocity.x * Time.deltaTime;
|
position.x += velocity.dx * Time.deltaTime;
|
||||||
position.y += velocity.y * Time.deltaTime;
|
position.y += velocity.dy * Time.deltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.addEntityProcessor(new MovementSystem());
|
// 创建场景并启动
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
|
||||||
// 游戏循环
|
const player = this.createEntity("Player");
|
||||||
Core.update(deltaTime);
|
player.addComponent(new Position(100, 100));
|
||||||
```
|
player.addComponent(new Velocity(50, 0));
|
||||||
|
|
||||||
### 2. 类型装饰器
|
|
||||||
|
|
||||||
在代码压缩混淆后,类名会改变导致框架无法识别组件类型。使用装饰器确保稳定性:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ECSComponent, ECSSystem } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 组件装饰器
|
|
||||||
@ECSComponent('PositionComponent')
|
|
||||||
class PositionComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ECSComponent('VelocityComponent')
|
|
||||||
class VelocityComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统装饰器
|
|
||||||
@ECSSystem('MovementSystem')
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(PositionComponent, VelocityComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]) {
|
|
||||||
// 处理逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 高级特性
|
|
||||||
|
|
||||||
### 查询系统
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Matcher, ECSSystem } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 使用Matcher和EntitySystem进行高效查询
|
|
||||||
@ECSSystem('QuerySystem')
|
|
||||||
class QuerySystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(PositionComponent, VelocityComponent).none(HealthComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]) {
|
|
||||||
// 处理匹配的实体
|
|
||||||
console.log(`Found ${entities.length} entities`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更复杂的查询条件
|
// 启动游戏
|
||||||
@ECSSystem('CombatSystem')
|
Core.create();
|
||||||
class CombatSystem extends EntitySystem {
|
Core.setScene(new GameScene());
|
||||||
constructor() {
|
|
||||||
super(
|
|
||||||
Matcher
|
|
||||||
.all(PositionComponent, HealthComponent) // 必须有位置和血量
|
|
||||||
.any(WeaponComponent, MagicComponent) // 有武器或魔法
|
|
||||||
.none(DeadComponent) // 不能是死亡状态
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]) {
|
// 游戏循环中更新
|
||||||
// 处理战斗逻辑
|
function gameLoop(deltaTime: number) {
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 事件系统
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EventHandler, ECSEventType, IEntityEventData } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class GameSystem {
|
|
||||||
@EventHandler(ECSEventType.ENTITY_DESTROYED)
|
|
||||||
onEntityDestroyed(data: IEntityEventData) {
|
|
||||||
console.log('实体销毁:', data.entityName, '实体ID:', data.entityId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(ECSEventType.ENTITY_CREATED)
|
|
||||||
onEntityCreated(data: IEntityEventData) {
|
|
||||||
console.log('实体创建:', data.entityName, '标签:', data.entityTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### SoA 存储优化
|
|
||||||
|
|
||||||
针对大规模实体处理的内存布局优化:
|
|
||||||
|
|
||||||
| 存储方式 | 内存布局 | 适用场景 | 性能特点 |
|
|
||||||
|----------|----------|----------|----------|
|
|
||||||
| **AoS** (Array of Structures) | `[{x,y,z}, {x,y,z}, {x,y,z}]` | 通用场景 | 访问灵活,缓存效率一般 |
|
|
||||||
| **SoA** (Structure of Arrays) | `{x:[1,2,3], y:[4,5,6], z:[7,8,9]}` | 批量处理 | SIMD优化,缓存友好 |
|
|
||||||
|
|
||||||
**SoA 优势:**
|
|
||||||
- 🚀 提升 2-4x 批量处理性能
|
|
||||||
- 💾 更好的CPU缓存利用率
|
|
||||||
- 🔧 支持SIMD向量化操作
|
|
||||||
- ⚡ 减少内存访问跳跃
|
|
||||||
|
|
||||||
用法示例:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EnableSoA, Float32, Int32 } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
@EnableSoA
|
|
||||||
class OptimizedTransformComponent extends Component {
|
|
||||||
@Float32 public x: number = 0;
|
|
||||||
@Float32 public y: number = 0;
|
|
||||||
@Float32 public rotation: number = 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**性能优势**:
|
|
||||||
- **缓存友好** - 连续内存访问,缓存命中率提升85%
|
|
||||||
- **批量处理** - 同类型数据处理速度提升2-3倍
|
|
||||||
- **热切换** - 开发期AoS便于调试,生产期SoA提升性能
|
|
||||||
- **自动优化** - `@EnableSoA`装饰器自动转换存储结构
|
|
||||||
|
|
||||||
## 平台集成
|
|
||||||
|
|
||||||
### Cocos Creator
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
update(deltaTime: number) {
|
|
||||||
Core.update(deltaTime);
|
Core.update(deltaTime);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**专用调试插件**:
|
## 核心特性
|
||||||
- [ECS 可视化调试插件](https://store.cocos.com/app/detail/7823) - 提供完整的可视化调试界面
|
|
||||||
- 实体查看器、组件编辑器、系统监控
|
|
||||||
- 性能分析和实时数据监控
|
|
||||||
|
|
||||||
### Laya 引擎
|
- **实体查询** - 使用 Matcher API 进行高效的实体过滤
|
||||||
```typescript
|
- **事件系统** - 类型安全的事件发布/订阅机制
|
||||||
Laya.timer.frameLoop(1, this, () => {
|
- **性能优化** - SoA 存储优化,支持大规模实体处理
|
||||||
Core.update(Laya.timer.delta / 1000);
|
- **多场景** - 支持 World/Scene 分层架构
|
||||||
});
|
- **时间管理** - 内置定时器和时间控制系统
|
||||||
```
|
|
||||||
|
|
||||||
### 原生浏览器
|
## 平台支持
|
||||||
```typescript
|
|
||||||
function gameLoop(currentTime: number) {
|
支持主流游戏引擎和 Web 平台:
|
||||||
const deltaTime = (currentTime - lastTime) / 1000;
|
|
||||||
Core.update(deltaTime);
|
- **Cocos Creator** - 内置引擎集成支持,提供[专用调试插件](https://store.cocos.com/app/detail/7823)
|
||||||
requestAnimationFrame(gameLoop);
|
- **Laya 引擎** - 完整的生命周期管理
|
||||||
}
|
- **原生 Web** - 浏览器环境直接运行
|
||||||
```
|
- **小游戏平台** - 微信、支付宝等小游戏
|
||||||
|
|
||||||
|
|
||||||
## API 参考
|
## 示例项目
|
||||||
|
|
||||||
### 核心类
|
- [割草机演示](https://github.com/esengine/lawn-mower-demo) - 完整的游戏示例
|
||||||
|
|
||||||
| 类 | 描述 |
|
|
||||||
|---|---|
|
|
||||||
| `Core` | 框架核心管理 |
|
|
||||||
| `Scene` | 场景容器 |
|
|
||||||
| `Entity` | 实体对象 |
|
|
||||||
| `Component` | 组件基类 |
|
|
||||||
| `EntitySystem` | 系统基类 |
|
|
||||||
| `EntityManager` | 实体管理器 |
|
|
||||||
|
|
||||||
### 查询 API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Matcher API - 推荐方式,高效且类型安全
|
|
||||||
Matcher.all(...components) // 包含所有组件
|
|
||||||
Matcher.any(...components) // 包含任意组件
|
|
||||||
Matcher.none(...components) // 不包含组件
|
|
||||||
|
|
||||||
// 组合查询示例
|
|
||||||
Matcher
|
|
||||||
.all(PositionComponent, VelocityComponent) // 必须有这些组件
|
|
||||||
.any(PlayerComponent, AIComponent) // 其中之一
|
|
||||||
.none(DeadComponent, DisabledComponent); // 排除这些
|
|
||||||
```
|
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
- [快速入门](docs/getting-started.md) - 详细教程和平台集成
|
- [快速入门](https://esengine.github.io/ecs-framework/guide/getting-started.html) - 详细教程和平台集成
|
||||||
- [技术概念](docs/concepts-explained.md) - ECS 架构和框架特性
|
- [完整指南](https://esengine.github.io/ecs-framework/guide/) - ECS 概念和使用指南
|
||||||
- [组件设计](docs/component-design-guide.md) - 组件设计最佳实践
|
- [API 参考](https://esengine.github.io/ecs-framework/api/) - 完整 API 文档
|
||||||
- [性能优化](docs/performance-optimization.md) - 性能优化技术
|
|
||||||
- [API 参考](docs/core-concepts.md) - 完整 API 文档
|
|
||||||
|
|
||||||
## 扩展库
|
## 生态系统
|
||||||
|
|
||||||
- [路径寻找](https://github.com/esengine/ecs-astar) - A*、BFS、Dijkstra 算法
|
- [路径寻找](https://github.com/esengine/ecs-astar) - A*、BFS、Dijkstra 算法
|
||||||
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI
|
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI
|
||||||
|
|
||||||
## 社区
|
## 社区与支持
|
||||||
|
|
||||||
- QQ 群:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
|
- [问题反馈](https://github.com/esengine/ecs-framework/issues) - Bug 报告和功能建议
|
||||||
- GitHub:[提交 Issue](https://github.com/esengine/ecs-framework/issues)
|
- [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - ecs游戏框架交流
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE) © 2025 ECS Framework
|
||||||
167
docs/.vitepress/config.mjs
Normal file
167
docs/.vitepress/config.mjs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
import Icons from 'unplugin-icons/vite'
|
||||||
|
import { readFileSync } from 'fs'
|
||||||
|
import { join, dirname } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const corePackageJson = JSON.parse(
|
||||||
|
readFileSync(join(__dirname, '../../packages/core/package.json'), 'utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
Icons({
|
||||||
|
compiler: 'vue3',
|
||||||
|
autoInstall: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
title: 'ECS Framework',
|
||||||
|
description: '高性能TypeScript ECS框架 - 为游戏开发而生',
|
||||||
|
lang: 'zh-CN',
|
||||||
|
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: '首页', link: '/' },
|
||||||
|
{ text: '快速开始', link: '/guide/getting-started' },
|
||||||
|
{ text: '指南', link: '/guide/' },
|
||||||
|
{ text: 'API', link: '/api/README' },
|
||||||
|
{ text: '示例', link: 'https://github.com/esengine/lawn-mower-demo' },
|
||||||
|
{
|
||||||
|
text: `v${corePackageJson.version}`,
|
||||||
|
link: 'https://github.com/esengine/ecs-framework/releases'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
sidebar: {
|
||||||
|
'/guide/': [
|
||||||
|
{
|
||||||
|
text: '开始使用',
|
||||||
|
items: [
|
||||||
|
{ text: '快速开始', link: '/guide/getting-started' },
|
||||||
|
{ text: '指南概览', link: '/guide/' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '核心概念',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '实体类 (Entity)', link: '/guide/entity' },
|
||||||
|
{ text: '组件系统 (Component)', link: '/guide/component' },
|
||||||
|
{ text: '系统架构 (System)', link: '/guide/system' },
|
||||||
|
{ text: '场景管理 (Scene)', link: '/guide/scene' },
|
||||||
|
{ text: '事件系统 (Event)', link: '/guide/event-system' },
|
||||||
|
{ text: '时间和定时器 (Time)', link: '/guide/time-and-timers' },
|
||||||
|
{ text: '日志系统 (Logger)', link: '/guide/logging' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'/api/': [
|
||||||
|
{
|
||||||
|
text: 'API 参考',
|
||||||
|
items: [
|
||||||
|
{ text: '概述', link: '/api/README' },
|
||||||
|
{
|
||||||
|
text: '核心类',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Core', link: '/api/classes/Core' },
|
||||||
|
{ text: 'Scene', link: '/api/classes/Scene' },
|
||||||
|
{ text: 'World', link: '/api/classes/World' },
|
||||||
|
{ text: 'Entity', link: '/api/classes/Entity' },
|
||||||
|
{ text: 'Component', link: '/api/classes/Component' },
|
||||||
|
{ text: 'EntitySystem', link: '/api/classes/EntitySystem' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '系统类',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'PassiveSystem', link: '/api/classes/PassiveSystem' },
|
||||||
|
{ text: 'ProcessingSystem', link: '/api/classes/ProcessingSystem' },
|
||||||
|
{ text: 'IntervalSystem', link: '/api/classes/IntervalSystem' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '工具类',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Matcher', link: '/api/classes/Matcher' },
|
||||||
|
{ text: 'Time', link: '/api/classes/Time' },
|
||||||
|
{ text: 'PerformanceMonitor', link: '/api/classes/PerformanceMonitor' },
|
||||||
|
{ text: 'DebugManager', link: '/api/classes/DebugManager' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '接口',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'IScene', link: '/api/interfaces/IScene' },
|
||||||
|
{ text: 'IComponent', link: '/api/interfaces/IComponent' },
|
||||||
|
{ text: 'ISystemBase', link: '/api/interfaces/ISystemBase' },
|
||||||
|
{ text: 'ICoreConfig', link: '/api/interfaces/ICoreConfig' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '装饰器',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: '@ECSComponent', link: '/api/functions/ECSComponent' },
|
||||||
|
{ text: '@ECSSystem', link: '/api/functions/ECSSystem' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '枚举',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'ECSEventType', link: '/api/enumerations/ECSEventType' },
|
||||||
|
{ text: 'LogLevel', link: '/api/enumerations/LogLevel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/esengine/ecs-framework' }
|
||||||
|
],
|
||||||
|
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2025 ECS Framework'
|
||||||
|
},
|
||||||
|
|
||||||
|
editLink: {
|
||||||
|
pattern: 'https://github.com/esengine/ecs-framework/edit/master/docs/:path',
|
||||||
|
text: '在 GitHub 上编辑此页'
|
||||||
|
},
|
||||||
|
|
||||||
|
search: {
|
||||||
|
provider: 'local'
|
||||||
|
},
|
||||||
|
|
||||||
|
outline: {
|
||||||
|
level: [2, 3],
|
||||||
|
label: '目录'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
head: [
|
||||||
|
['meta', { name: 'theme-color', content: '#646cff' }],
|
||||||
|
['link', { rel: 'icon', href: '/favicon.ico' }]
|
||||||
|
],
|
||||||
|
|
||||||
|
base: '/',
|
||||||
|
cleanUrls: true,
|
||||||
|
|
||||||
|
markdown: {
|
||||||
|
lineNumbers: true,
|
||||||
|
theme: {
|
||||||
|
light: 'github-light',
|
||||||
|
dark: 'github-dark'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# 新手教程完整指南
|
|
||||||
|
|
||||||
欢迎使用ECS框架!本指南为新手提供了完整的学习路径,从基础概念到高级应用,帮你快速掌握ECS框架开发游戏。
|
|
||||||
|
|
||||||
## 学习路径
|
|
||||||
|
|
||||||
### 第一阶段:基础入门(必读)
|
|
||||||
|
|
||||||
#### 1. [快速开始](getting-started.md)
|
|
||||||
- **5分钟入门** - 创建你的第一个ECS游戏
|
|
||||||
- **环境搭建** - 安装和配置框架
|
|
||||||
- **第一个游戏** - 完整的示例游戏
|
|
||||||
- **基础API** - 核心功能介绍
|
|
||||||
|
|
||||||
#### 2. [核心概念](core-concepts.md)
|
|
||||||
- **ECS架构** - 实体、组件、系统的关系
|
|
||||||
- **API参考** - 核心类和方法
|
|
||||||
- **最佳实践** - 代码规范和设计模式
|
|
||||||
- **查询系统** - 如何高效查找实体
|
|
||||||
|
|
||||||
#### 3. [概念详解](concepts-explained.md) **新手必读**
|
|
||||||
- **通俗解释** - 用简单语言解释复杂概念
|
|
||||||
- **性能优化技术** - 组件索引、Archetype、脏标记
|
|
||||||
- **索引选择指南** - 何时使用哈希索引vs位图索引
|
|
||||||
- **应用场景** - 不同游戏类型的选择建议
|
|
||||||
|
|
||||||
### 第二阶段:核心功能掌握
|
|
||||||
|
|
||||||
#### 4. [实体管理指南](entity-guide.md)
|
|
||||||
- **实体基础** - 什么是实体,如何创建和使用
|
|
||||||
- **标签系统** - 实体分类和查找
|
|
||||||
- **生命周期** - 实体的创建、更新、销毁
|
|
||||||
- **简单示例** - 玩家、敌人、道具实体
|
|
||||||
|
|
||||||
#### 5. [组件设计最佳实践](component-design-guide.md) **设计必读**
|
|
||||||
- **组件设计原则** - 单一职责、数据为主
|
|
||||||
- **组件类型** - 数据组件、标记组件、行为组件
|
|
||||||
- **组件通信** - 如何让组件协同工作
|
|
||||||
- **性能优化** - 对象池和数据紧凑性
|
|
||||||
- **测试和调试** - 如何测试你的组件
|
|
||||||
|
|
||||||
#### 6. [系统详解指南](system-guide.md) **逻辑必读**
|
|
||||||
- **四种系统类型** - EntitySystem、ProcessingSystem、IntervalSystem、PassiveSystem
|
|
||||||
- **使用场景** - 什么时候用哪种系统
|
|
||||||
- **执行顺序** - 系统间的依赖关系
|
|
||||||
- **系统通信** - 事件驱动的松耦合设计
|
|
||||||
- **性能优化** - 批量处理和频率控制
|
|
||||||
|
|
||||||
### 第三阶段:高级功能应用
|
|
||||||
|
|
||||||
#### 7. [场景管理指南](scene-management-guide.md)
|
|
||||||
- **场景概念** - 什么是场景,如何组织游戏世界
|
|
||||||
- **场景切换** - 菜单、游戏、暂停场景的切换
|
|
||||||
- **数据传递** - 场景间如何传递数据
|
|
||||||
- **实际应用** - 完整的游戏场景设计
|
|
||||||
- **性能优化** - 场景级别的性能监控
|
|
||||||
|
|
||||||
#### 8. [定时器系统指南](timer-guide.md)
|
|
||||||
- **定时器基础** - 延迟执行、重复执行
|
|
||||||
- **定时器链** - 顺序执行多个任务
|
|
||||||
- **条件定时器** - 等待特定条件满足
|
|
||||||
- **可暂停定时器** - 游戏暂停功能
|
|
||||||
- **游戏应用** - Buff系统、技能冷却、关卡限时
|
|
||||||
|
|
||||||
#### 9. [查询系统使用](query-system-usage.md)
|
|
||||||
- **基础查询** - 按组件查找实体
|
|
||||||
- **复杂查询** - 组合条件和排除条件
|
|
||||||
- **性能监控** - 查询性能统计
|
|
||||||
- **优化技巧** - 提高查询效率
|
|
||||||
|
|
||||||
#### 10. [事件系统示例](event-system-example.md)
|
|
||||||
- **事件基础** - 发送和监听事件
|
|
||||||
- **游戏事件** - 玩家输入、碰撞、分数等
|
|
||||||
- **系统解耦** - 用事件实现系统间通信
|
|
||||||
- **事件统计** - 监控事件系统性能
|
|
||||||
|
|
||||||
### 第四阶段:实战应用
|
|
||||||
|
|
||||||
#### 11. [实体管理器高级功能](entity-manager-example.md)
|
|
||||||
- 🏭 **批量操作** - 高效创建和管理大量实体
|
|
||||||
- **高级查询** - EntityQueryBuilder的使用
|
|
||||||
- **性能监控** - 实体管理性能统计
|
|
||||||
- **实际案例** - 弹幕游戏、RTS游戏的实体管理
|
|
||||||
|
|
||||||
#### 12. [应用案例集合](use-cases.md)
|
|
||||||
- **不同游戏类型** - 休闲游戏、动作游戏、策略游戏
|
|
||||||
- **具体实现** - 完整的代码示例
|
|
||||||
- **性能分析** - 各种应用的性能特点
|
|
||||||
- **设计思路** - 如何选择合适的架构
|
|
||||||
|
|
||||||
### 第五阶段:性能优化
|
|
||||||
|
|
||||||
#### 13. [性能基准测试](performance.md)
|
|
||||||
- **基准数据** - 框架性能表现
|
|
||||||
- **对比分析** - 与其他框架的比较
|
|
||||||
- **优化建议** - 针对不同规模的优化策略
|
|
||||||
- **性能检查清单** - 确保最佳性能的要点
|
|
||||||
|
|
||||||
#### 14. [性能优化技术](performance-optimization.md)
|
|
||||||
- **核心优化** - 组件索引、Archetype、脏标记
|
|
||||||
- **内存优化** - 对象池、数据紧凑性
|
|
||||||
- **批量处理** - 减少单次操作开销
|
|
||||||
- **监控工具** - 性能分析和调试
|
|
||||||
|
|
||||||
## 推荐学习顺序
|
|
||||||
|
|
||||||
### 适合完全新手(第一次接触ECS)
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 快速开始 → 2. 概念详解 → 3. 核心概念 → 4. 实体管理指南
|
|
||||||
→ 5. 组件设计指南 → 6. 系统详解指南 → 7. 应用案例
|
|
||||||
```
|
|
||||||
|
|
||||||
### 适合有游戏开发经验的开发者
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 快速开始 → 2. 核心概念 → 3. 组件设计指南 → 4. 系统详解指南
|
|
||||||
→ 5. 场景管理指南 → 6. 性能优化技术
|
|
||||||
```
|
|
||||||
|
|
||||||
### 适合追求高性能的开发者
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 快速开始 → 2. 概念详解(重点看性能优化) → 3. 性能基准测试
|
|
||||||
→ 4. 性能优化技术 → 5. 实体管理器高级功能
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见学习问题
|
|
||||||
|
|
||||||
### Q: 我应该从哪里开始?
|
|
||||||
|
|
||||||
A: 建议先阅读[快速开始](getting-started.md),然后根据你的背景选择学习路径:
|
|
||||||
- **新手**:重点看概念详解
|
|
||||||
- **有经验**:直接看核心概念和设计指南
|
|
||||||
- **追求性能**:重点看性能相关文档
|
|
||||||
|
|
||||||
### Q: ECS和传统OOP有什么区别?
|
|
||||||
|
|
||||||
A: 详见[概念详解](concepts-explained.md)的"ECS vs 传统架构"部分,用简单例子解释两者差异。
|
|
||||||
|
|
||||||
### Q: 如何选择组件索引类型?
|
|
||||||
|
|
||||||
A: [概念详解](concepts-explained.md)有详细的索引选择指南,包括决策流程图和具体示例。
|
|
||||||
|
|
||||||
### Q: 系统的执行顺序重要吗?
|
|
||||||
|
|
||||||
A: 非常重要详细解释了系统顺序的重要性和设置方法。
|
|
||||||
|
|
||||||
### Q: 如何调试性能问题?
|
|
||||||
|
|
||||||
A:
|
|
||||||
1. 使用[性能基准测试](performance.md)中的工具
|
|
||||||
2. 参考[性能优化技术](performance-optimization.md)的监控方法
|
|
||||||
3. 查看[实体管理器示例](entity-manager-example.md)的统计功能
|
|
||||||
|
|
||||||
## 📖 扩展阅读
|
|
||||||
|
|
||||||
### 设计模式和架构
|
|
||||||
- [组件设计最佳实践](component-design-guide.md) - 如何设计可维护的组件
|
|
||||||
- [系统详解指南](system-guide.md) - 系统间的协作模式
|
|
||||||
|
|
||||||
### 性能和优化
|
|
||||||
- [概念详解](concepts-explained.md) - 性能优化技术原理
|
|
||||||
- [性能优化技术](performance-optimization.md) - 具体优化实现
|
|
||||||
|
|
||||||
### 实际应用
|
|
||||||
- [应用案例集合](use-cases.md) - 不同类型游戏的实现
|
|
||||||
- [场景管理指南](scene-management-guide.md) - 复杂游戏的场景组织
|
|
||||||
|
|
||||||
## 学习建议
|
|
||||||
|
|
||||||
### 实践为主
|
|
||||||
- **边学边做** - 每学一个概念都尝试写代码实现
|
|
||||||
- **从小做起** - 先做简单的游戏,再逐步增加复杂度
|
|
||||||
- **多做实验** - 尝试不同的设计方案,体会优劣
|
|
||||||
|
|
||||||
### 理解原理
|
|
||||||
- **思考为什么** - 不只学怎么做,更要理解为什么这样做
|
|
||||||
- **关注性能** - 了解各种操作的性能影响
|
|
||||||
- **深入源码** - 有疑问时查看框架源码
|
|
||||||
|
|
||||||
### 循序渐进
|
|
||||||
- **按顺序学习** - 先掌握基础,再学高级功能
|
|
||||||
- **专注重点** - 每次只专注一个主题,不要贪多
|
|
||||||
- **反复练习** - 重要概念要多练习才能熟练
|
|
||||||
|
|
||||||
开始你的ECS学习之旅吧!
|
|
||||||
@@ -1,704 +0,0 @@
|
|||||||
# 组件设计最佳实践指南
|
|
||||||
|
|
||||||
组件是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 PlayerSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(PlayerComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是玩家实体
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理玩家逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EnemySystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(EnemyComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是敌人实体
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理敌人逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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;
|
|
||||||
|
|
||||||
// 发送事件,让其他系统响应
|
|
||||||
// 注意:需要在实际使用中获取EntityManager实例
|
|
||||||
// 示例:entityManager.eventBus.emit('health:damaged', {...});
|
|
||||||
|
|
||||||
if (this.currentHealth <= 0) {
|
|
||||||
// 示例:entityManager.eventBus.emit('health:died', {...});
|
|
||||||
console.log('实体死亡');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他组件响应事件
|
|
||||||
class AnimationComponent extends Component {
|
|
||||||
onAddedToEntity() {
|
|
||||||
super.onAddedToEntity();
|
|
||||||
|
|
||||||
// 监听受伤事件(需要在实际使用中获取EntityManager实例)
|
|
||||||
// 示例:entityManager.eventBus.on('health:damaged', this.onDamaged, { context: this });
|
|
||||||
}
|
|
||||||
|
|
||||||
onRemovedFromEntity() {
|
|
||||||
// 事件监听会在组件移除时自动清理
|
|
||||||
// 如需手动清理,保存listenerId并调用eventBus.off()
|
|
||||||
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 {
|
|
||||||
// 方形碰撞检测
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
遵循这些原则,你就能设计出高质量、易维护的组件系统!
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
# 技术概念详解
|
|
||||||
|
|
||||||
本文档用通俗易懂的语言解释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 = scene.createEntity("Player")
|
|
||||||
.addComponent(new PositionComponent()) // 位置数据
|
|
||||||
.addComponent(new HealthComponent()) // 生命值数据
|
|
||||||
.addComponent(new PlayerInputComponent()) // 玩家输入标记
|
|
||||||
|
|
||||||
const enemy = scene.createEntity("Enemy")
|
|
||||||
.addComponent(new PositionComponent()) // 复用位置数据
|
|
||||||
.addComponent(new HealthComponent()) // 复用生命值数据
|
|
||||||
.addComponent(new AIComponent()) // AI标记
|
|
||||||
|
|
||||||
// 系统自动处理具有特定组件的实体
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
onUpdate() {
|
|
||||||
// 处理具有Position和Velocity组件的实体
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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 entitiesWithHealth = entityManager.query()
|
|
||||||
.withAll(HealthComponent)
|
|
||||||
.execute(); // 直接获取,SparseSet自动优化
|
|
||||||
```
|
|
||||||
|
|
||||||
**应用场景:**
|
|
||||||
- 频繁查询特定组件的实体
|
|
||||||
- 大规模实体场景(数千到数万个实体)
|
|
||||||
- 实时游戏中的系统更新
|
|
||||||
|
|
||||||
### SparseSet 组件索引
|
|
||||||
|
|
||||||
**什么是 SparseSet?**
|
|
||||||
SparseSet是一种高效的数据结构,结合了哈希表的快速访问和数组的缓存友好特性。
|
|
||||||
|
|
||||||
**SparseSet 的优势:**
|
|
||||||
- **O(1) 添加/删除/查找** - 所有基本操作都是常数时间
|
|
||||||
- **缓存友好遍历** - 密集数组存储,提高遍历性能
|
|
||||||
- **内存高效** - 自动管理稀疏和密集数据
|
|
||||||
- **无需配置** - 框架自动选择最优策略
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 统一的查询API,无需手动配置
|
|
||||||
const entitiesWithHealth = entityManager.query()
|
|
||||||
.withAll(HealthComponent)
|
|
||||||
.execute(); // O(1) 访问,SparseSet自动优化
|
|
||||||
```
|
|
||||||
|
|
||||||
**应用场景:**
|
|
||||||
- 任意规模的实体场景(从几十到数万)
|
|
||||||
- 频繁的组件添加/删除操作
|
|
||||||
- 高性能的批量查询需求
|
|
||||||
|
|
||||||
### 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");
|
|
||||||
player.addComponent(new PositionComponent());
|
|
||||||
player.addComponent(new HealthComponent());
|
|
||||||
|
|
||||||
// 批量创建 - 需要循环处理
|
|
||||||
const bullets: Entity[] = [];
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
const bullet = scene.createEntity(`Bullet_${i}`);
|
|
||||||
bullet.addComponent(new PositionComponent());
|
|
||||||
bullet.addComponent(new VelocityComponent());
|
|
||||||
bullets.push(bullet);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查询系统
|
|
||||||
|
|
||||||
**流式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
|
|
||||||
// 优化的批量创建方式
|
|
||||||
const bullets: Entity[] = [];
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
const bullet = scene.createEntity(`Bullet_${i}`);
|
|
||||||
bullet.addComponent(new PositionComponent());
|
|
||||||
bullet.addComponent(new VelocityComponent());
|
|
||||||
bullets.push(bullet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量查询操作
|
|
||||||
const allMovableEntities = entityManager.query()
|
|
||||||
.withAll(PositionComponent, VelocityComponent)
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
**应用场景:**
|
|
||||||
- 生成大量子弹/粒子
|
|
||||||
- 加载关卡时创建大量实体
|
|
||||||
- 清理场景时删除大量实体
|
|
||||||
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
ECS框架包含以下核心技术概念:
|
|
||||||
|
|
||||||
1. **ECS架构** - 组件化设计模式
|
|
||||||
2. **SparseSet索引** - 高效的组件查询
|
|
||||||
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,926 +0,0 @@
|
|||||||
# 核心 API 参考
|
|
||||||
|
|
||||||
本文档详细介绍 ECS Framework 的核心 API 和使用方法。
|
|
||||||
|
|
||||||
> **不熟悉ECS概念?** 建议先阅读 [技术概念详解](concepts-explained.md) 了解ECS架构基础和性能优化原理
|
|
||||||
|
|
||||||
## ECS 架构概述
|
|
||||||
|
|
||||||
ECS 架构将传统的面向对象设计分解为三个核心部分:
|
|
||||||
|
|
||||||
- **Entity(实体)** - 游戏世界中的对象,包含基本属性如位置、旋转、缩放
|
|
||||||
- **Component(组件)** - 包含数据和行为的功能模块
|
|
||||||
- **System(系统)** - 处理实体集合的逻辑处理单元
|
|
||||||
|
|
||||||
## Core(核心)
|
|
||||||
|
|
||||||
Core 是框架的核心管理类,负责游戏的生命周期管理。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构。
|
|
||||||
|
|
||||||
### 创建和配置
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Core, ICoreConfig } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建核心实例(使用配置对象 - 推荐)
|
|
||||||
const config: ICoreConfig = {
|
|
||||||
debug: true, // 启用调试模式
|
|
||||||
enableEntitySystems: true, // 启用实体系统
|
|
||||||
debugConfig: { // 可选:远程调试配置
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080',
|
|
||||||
autoReconnect: true,
|
|
||||||
updateInterval: 1000,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: true,
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const core = Core.create(config);
|
|
||||||
|
|
||||||
// 简化创建(向后兼容)
|
|
||||||
const core1 = Core.create(true); // 调试模式
|
|
||||||
const core2 = Core.create(false); // 发布模式
|
|
||||||
const core3 = Core.create(); // 默认调试模式
|
|
||||||
```
|
|
||||||
|
|
||||||
### 场景管理API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 单Scene模式(默认,向后兼容)
|
|
||||||
const scene = new Scene();
|
|
||||||
Core.setScene(scene); // 设置场景
|
|
||||||
const currentScene = Core.getScene(); // 获取当前场景
|
|
||||||
|
|
||||||
// 多World模式(高级功能)
|
|
||||||
Core.enableWorldManager(); // 启用World管理器
|
|
||||||
const worldManager = Core.getWorldManager();
|
|
||||||
|
|
||||||
// 创建World
|
|
||||||
const gameWorld = worldManager.createWorld('GameWorld', {
|
|
||||||
name: 'GameWorld',
|
|
||||||
maxScenes: 10,
|
|
||||||
autoCleanup: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 在World中管理Scene
|
|
||||||
const battleScene = gameWorld.createScene('battle', new Scene());
|
|
||||||
const uiScene = gameWorld.createScene('ui', new Scene());
|
|
||||||
|
|
||||||
gameWorld.setSceneActive('battle', true);
|
|
||||||
gameWorld.setSceneActive('ui', true);
|
|
||||||
|
|
||||||
// 启动World
|
|
||||||
gameWorld.start();
|
|
||||||
|
|
||||||
// 获取World统计
|
|
||||||
const worldStats = gameWorld.getStats();
|
|
||||||
console.log('World状态:', worldStats);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 事件系统
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntityManager, ECSEventType } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 获取EntityManager的事件系统
|
|
||||||
const entityManager = new EntityManager();
|
|
||||||
const eventBus = entityManager.eventBus;
|
|
||||||
|
|
||||||
// 监听实体事件
|
|
||||||
eventBus.onEntityCreated((data) => {
|
|
||||||
console.log(`实体创建: ${data.entityName}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
eventBus.onComponentAdded((data) => {
|
|
||||||
console.log(`组件添加: ${data.componentType}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 发送自定义事件
|
|
||||||
eventBus.emit("customEvent", { data: "value" });
|
|
||||||
|
|
||||||
// 使用事件装饰器(推荐)
|
|
||||||
import { EventHandler } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class GameSystem {
|
|
||||||
@EventHandler('entity:died')
|
|
||||||
onEntityDied(data: any) {
|
|
||||||
console.log('实体死亡:', data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 定时器系统
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 延迟执行
|
|
||||||
Core.schedule(2.0, false, this, (timer) => {
|
|
||||||
console.log("2秒后执行");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重复执行
|
|
||||||
Core.schedule(1.0, true, this, (timer) => {
|
|
||||||
console.log("每秒执行一次");
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Scene(场景)
|
|
||||||
|
|
||||||
场景是游戏世界的容器,管理实体和系统的生命周期。
|
|
||||||
|
|
||||||
### 创建和使用场景
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建场景
|
|
||||||
const scene = new Scene();
|
|
||||||
scene.name = "GameScene";
|
|
||||||
|
|
||||||
// 设置为当前场景
|
|
||||||
Core.setScene(scene;
|
|
||||||
|
|
||||||
// 场景生命周期
|
|
||||||
scene.begin(); // 开始场景
|
|
||||||
scene.update(); // 更新场景
|
|
||||||
scene.end(); // 结束场景
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量实体管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 批量创建实体 - 高性能
|
|
||||||
const entities = scene.createEntities(1000, "Enemy");
|
|
||||||
|
|
||||||
// 批量添加实体(延迟缓存清理)
|
|
||||||
entities.forEach(entity => {
|
|
||||||
scene.addEntity(entity, false); // 延迟清理
|
|
||||||
});
|
|
||||||
scene.querySystem.clearCache(); // 手动清理缓存
|
|
||||||
|
|
||||||
// 获取性能统计
|
|
||||||
const stats = scene.getStats();
|
|
||||||
console.log(`实体数量: ${stats.entityCount}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Entity(实体)
|
|
||||||
|
|
||||||
实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性。
|
|
||||||
|
|
||||||
### 实体的基本属性
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const entity = scene.createEntity("MyEntity");
|
|
||||||
|
|
||||||
// 标签(用于分类)
|
|
||||||
entity.tag = 1;
|
|
||||||
|
|
||||||
// 启用状态
|
|
||||||
entity.enabled = true;
|
|
||||||
|
|
||||||
// 活跃状态
|
|
||||||
entity.active = true;
|
|
||||||
|
|
||||||
// 更新顺序
|
|
||||||
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;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实体层级关系
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 添加子实体
|
|
||||||
const parent = scene.createEntity("Parent");
|
|
||||||
const child = scene.createEntity("Child");
|
|
||||||
parent.addChild(child);
|
|
||||||
|
|
||||||
// 获取父实体
|
|
||||||
const parentEntity = child.parent;
|
|
||||||
|
|
||||||
// 获取所有子实体
|
|
||||||
const children = parent.children;
|
|
||||||
|
|
||||||
// 查找子实体
|
|
||||||
const foundChild = parent.findChild("Child");
|
|
||||||
|
|
||||||
// 按标签查找子实体
|
|
||||||
const taggedChildren = parent.findChildrenByTag(1);
|
|
||||||
|
|
||||||
// 移除子实体
|
|
||||||
parent.removeChild(child);
|
|
||||||
|
|
||||||
// 移除所有子实体
|
|
||||||
parent.removeAllChildren();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实体生命周期
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 检查实体是否被销毁
|
|
||||||
if (!entity.isDestroyed) {
|
|
||||||
// 实体仍然有效
|
|
||||||
}
|
|
||||||
|
|
||||||
// 销毁实体
|
|
||||||
entity.destroy();
|
|
||||||
|
|
||||||
// 获取实体调试信息
|
|
||||||
const debugInfo = entity.getDebugInfo();
|
|
||||||
console.log(debugInfo);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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 -= damage;
|
|
||||||
if (this.currentHealth <= 0) {
|
|
||||||
this.entity.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public heal(amount: number) {
|
|
||||||
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 组件生命周期
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class MyComponent extends Component {
|
|
||||||
public onAddedToEntity() {
|
|
||||||
// 组件被添加到实体时调用
|
|
||||||
console.log("组件已添加到实体:", this.entity.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onRemovedFromEntity() {
|
|
||||||
// 组件从实体移除时调用
|
|
||||||
console.log("组件已从实体移除");
|
|
||||||
}
|
|
||||||
|
|
||||||
public onEnabled() {
|
|
||||||
// 组件启用时调用
|
|
||||||
console.log("组件已启用");
|
|
||||||
}
|
|
||||||
|
|
||||||
public onDisabled() {
|
|
||||||
// 组件禁用时调用
|
|
||||||
console.log("组件已禁用");
|
|
||||||
}
|
|
||||||
|
|
||||||
public update() {
|
|
||||||
// 每帧更新(如果组件启用)
|
|
||||||
console.log("组件更新");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 组件管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 添加组件
|
|
||||||
const health = entity.addComponent(new HealthComponent());
|
|
||||||
|
|
||||||
// 创建并添加组件
|
|
||||||
const movement = entity.createComponent(MovementComponent, 200); // 传递构造参数
|
|
||||||
|
|
||||||
// 获取组件
|
|
||||||
const healthComp = entity.getComponent(HealthComponent);
|
|
||||||
|
|
||||||
// 检查组件是否存在
|
|
||||||
if (entity.hasComponent(HealthComponent)) {
|
|
||||||
// 处理逻辑
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取或创建组件
|
|
||||||
const weapon = entity.getOrCreateComponent(WeaponComponent);
|
|
||||||
|
|
||||||
// 获取多个同类型组件
|
|
||||||
const allHealthComps = entity.getComponents(HealthComponent);
|
|
||||||
|
|
||||||
// 移除组件
|
|
||||||
entity.removeComponent(healthComp);
|
|
||||||
|
|
||||||
// 按类型移除组件
|
|
||||||
entity.removeComponentByType(HealthComponent);
|
|
||||||
|
|
||||||
// 移除所有组件
|
|
||||||
entity.removeAllComponents();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 组件对象池优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Component, ComponentPoolManager } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class BulletComponent extends Component {
|
|
||||||
public damage: number = 10;
|
|
||||||
public speed: number = 300;
|
|
||||||
|
|
||||||
// 对象池重置方法
|
|
||||||
public reset() {
|
|
||||||
this.damage = 10;
|
|
||||||
this.speed = 300;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册组件池
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 预热所有组件池
|
|
||||||
ComponentPoolManager.getInstance().prewarmAll(100);
|
|
||||||
|
|
||||||
// 获取池统计
|
|
||||||
const stats = ComponentPoolManager.getInstance().getPoolStats();
|
|
||||||
console.log('组件池统计:', stats);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Scene(场景)
|
|
||||||
|
|
||||||
场景是实体和系统的容器,管理游戏世界的状态。
|
|
||||||
|
|
||||||
### 场景生命周期
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class GameScene extends Scene {
|
|
||||||
public initialize() {
|
|
||||||
// 场景初始化,创建实体和系统
|
|
||||||
this.setupEntities();
|
|
||||||
this.setupSystems();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onStart() {
|
|
||||||
// 场景开始运行时调用
|
|
||||||
console.log("场景开始");
|
|
||||||
}
|
|
||||||
|
|
||||||
public unload() {
|
|
||||||
// 场景卸载时调用
|
|
||||||
console.log("场景卸载");
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupEntities() {
|
|
||||||
const player = this.createEntity("Player");
|
|
||||||
player.addComponent(new PlayerComponent());
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupSystems() {
|
|
||||||
this.addEntityProcessor(new MovementSystem());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实体管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建实体
|
|
||||||
const entity = scene.createEntity("MyEntity");
|
|
||||||
|
|
||||||
// 添加现有实体
|
|
||||||
scene.addEntity(entity);
|
|
||||||
|
|
||||||
// 查找实体
|
|
||||||
const player = scene.findEntity("Player");
|
|
||||||
const entityById = scene.findEntityById(123);
|
|
||||||
const entitiesByTag = scene.findEntitiesByTag(1);
|
|
||||||
|
|
||||||
// 销毁所有实体
|
|
||||||
scene.destroyAllEntities();
|
|
||||||
|
|
||||||
// 获取场景统计信息
|
|
||||||
const stats = scene.getStats();
|
|
||||||
console.log("实体数量:", stats.entityCount);
|
|
||||||
console.log("系统数量:", stats.processorCount);
|
|
||||||
```
|
|
||||||
|
|
||||||
## World(世界)
|
|
||||||
|
|
||||||
World是Scene的容器,提供了更高级的场景管理功能。每个World可以包含多个Scene,适用于复杂的游戏架构。
|
|
||||||
|
|
||||||
### World基本使用
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { World, Scene, IWorldConfig } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建World配置
|
|
||||||
const worldConfig: IWorldConfig = {
|
|
||||||
name: 'GameWorld',
|
|
||||||
debug: true,
|
|
||||||
maxScenes: 10,
|
|
||||||
autoCleanup: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建World
|
|
||||||
const gameWorld = new World(worldConfig);
|
|
||||||
|
|
||||||
// 在World中创建Scene
|
|
||||||
const battleScene = gameWorld.createScene('battle', new Scene());
|
|
||||||
const uiScene = gameWorld.createScene('ui', new Scene());
|
|
||||||
const menuScene = gameWorld.createScene('menu');
|
|
||||||
|
|
||||||
// 激活Scene
|
|
||||||
gameWorld.setSceneActive('battle', true);
|
|
||||||
gameWorld.setSceneActive('ui', true);
|
|
||||||
|
|
||||||
// 启动World
|
|
||||||
gameWorld.start();
|
|
||||||
```
|
|
||||||
|
|
||||||
### World生命周期管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 启动World(启动所有全局System)
|
|
||||||
gameWorld.start();
|
|
||||||
|
|
||||||
// 检查World状态
|
|
||||||
if (gameWorld.isActive) {
|
|
||||||
console.log('World正在运行');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止World(停止所有Scene和全局System)
|
|
||||||
gameWorld.stop();
|
|
||||||
|
|
||||||
// 销毁World(清理所有资源)
|
|
||||||
gameWorld.destroy();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scene管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取Scene
|
|
||||||
const battleScene = gameWorld.getScene<Scene>('battle');
|
|
||||||
|
|
||||||
// 检查Scene是否激活
|
|
||||||
if (gameWorld.isSceneActive('battle')) {
|
|
||||||
console.log('战斗场景正在运行');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除Scene
|
|
||||||
gameWorld.removeScene('menu');
|
|
||||||
|
|
||||||
// 获取所有Scene ID
|
|
||||||
const sceneIds = gameWorld.getSceneIds();
|
|
||||||
console.log('所有Scene:', sceneIds);
|
|
||||||
|
|
||||||
// 获取活跃Scene数量
|
|
||||||
const activeCount = gameWorld.getActiveSceneCount();
|
|
||||||
console.log('活跃Scene数量:', activeCount);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 全局System管理
|
|
||||||
|
|
||||||
World支持全局System,这些System会在所有Scene之前执行,适用于跨Scene的业务逻辑:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { IGlobalSystem } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 全局网络同步系统
|
|
||||||
class GlobalNetworkSystem implements IGlobalSystem {
|
|
||||||
public readonly name = 'GlobalNetworkSystem';
|
|
||||||
|
|
||||||
public initialize(): void {
|
|
||||||
// 初始化网络连接
|
|
||||||
console.log('网络系统初始化');
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(): void {
|
|
||||||
// 处理全局网络同步逻辑
|
|
||||||
// 注意:全局系统处理的是World级别的逻辑,不直接处理实体
|
|
||||||
// 如需处理特定实体,请在Scene中使用EntitySystem
|
|
||||||
this.syncGlobalNetworkState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
// 重置系统状态
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy(): void {
|
|
||||||
// 清理网络连接
|
|
||||||
console.log('网络系统销毁');
|
|
||||||
}
|
|
||||||
|
|
||||||
private syncGlobalNetworkState(): void {
|
|
||||||
// 全局网络状态同步
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加全局System
|
|
||||||
const networkSystem = gameWorld.addGlobalSystem(new GlobalNetworkSystem());
|
|
||||||
|
|
||||||
// 获取全局System
|
|
||||||
const existingSystem = gameWorld.getGlobalSystem(GlobalNetworkSystem);
|
|
||||||
|
|
||||||
// 移除全局System
|
|
||||||
gameWorld.removeGlobalSystem(networkSystem);
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意**:全局System适用于World级别的业务逻辑(如网络管理、资源管理、全局状态管理等)。如果需要处理具体的实体和组件,请在Scene中使用EntitySystem。
|
|
||||||
|
|
||||||
### World状态监控
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取World状态
|
|
||||||
const status = gameWorld.getStatus();
|
|
||||||
console.log('World状态:', {
|
|
||||||
name: status.name,
|
|
||||||
isActive: status.isActive,
|
|
||||||
sceneCount: status.sceneCount,
|
|
||||||
activeSceneCount: status.activeSceneCount,
|
|
||||||
globalSystemCount: status.globalSystemCount,
|
|
||||||
scenes: status.scenes
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取World统计信息
|
|
||||||
const stats = gameWorld.getStats();
|
|
||||||
console.log('World统计:', {
|
|
||||||
totalEntities: stats.totalEntities,
|
|
||||||
totalSystems: stats.totalSystems,
|
|
||||||
memoryUsage: stats.memoryUsage
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## WorldManager(世界管理器)
|
|
||||||
|
|
||||||
WorldManager是单例模式的World管理器,负责管理多个World实例。
|
|
||||||
|
|
||||||
### WorldManager基本使用
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { WorldManager, IWorldManagerConfig } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 获取WorldManager实例
|
|
||||||
const worldManager = WorldManager.getInstance({
|
|
||||||
maxWorlds: 50,
|
|
||||||
autoCleanup: true,
|
|
||||||
debug: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 或者通过Core获取
|
|
||||||
Core.enableWorldManager();
|
|
||||||
const worldManager2 = Core.getWorldManager();
|
|
||||||
```
|
|
||||||
|
|
||||||
### World管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建World
|
|
||||||
const gameWorld = worldManager.createWorld('GameRoom_001', {
|
|
||||||
name: 'GameRoom_001',
|
|
||||||
maxScenes: 5,
|
|
||||||
autoCleanup: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取World
|
|
||||||
const existingWorld = worldManager.getWorld('GameRoom_001');
|
|
||||||
|
|
||||||
// 检查World是否存在
|
|
||||||
if (worldManager.getWorld('GameRoom_001')) {
|
|
||||||
console.log('World存在');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 销毁World
|
|
||||||
worldManager.removeWorld('GameRoom_001');
|
|
||||||
|
|
||||||
// 获取所有World ID
|
|
||||||
const worldIds = worldManager.getWorldIds();
|
|
||||||
console.log('所有World ID:', worldIds);
|
|
||||||
|
|
||||||
// 获取活跃World
|
|
||||||
const activeWorlds = worldManager.getActiveWorlds();
|
|
||||||
console.log('活跃World数量:', activeWorlds.length);
|
|
||||||
```
|
|
||||||
|
|
||||||
### WorldManager统计和监控
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取WorldManager状态
|
|
||||||
const managerStatus = worldManager.getStatus();
|
|
||||||
console.log('WorldManager状态:', {
|
|
||||||
totalWorlds: managerStatus.totalWorlds,
|
|
||||||
activeWorlds: managerStatus.activeWorlds,
|
|
||||||
maxWorlds: managerStatus.maxWorlds,
|
|
||||||
memoryUsage: managerStatus.memoryUsage
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取所有World的统计
|
|
||||||
const allStats = worldManager.getAllWorldStats();
|
|
||||||
allStats.forEach(stat => {
|
|
||||||
console.log(`World ${stat.worldName}:`, stat);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清理空闲World
|
|
||||||
const cleanedCount = worldManager.cleanup();
|
|
||||||
console.log(`清理了 ${cleanedCount} 个空闲World`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用场景
|
|
||||||
|
|
||||||
World和WorldManager适用于:
|
|
||||||
- **游戏服务器**:每个房间一个独立World
|
|
||||||
- **复杂客户端**:按功能分层管理Scene(游戏层、UI层、特效层)
|
|
||||||
- **并发世界**:需要同时运行多个独立游戏世界的场景
|
|
||||||
|
|
||||||
> **完整示例和最佳实践**:查看 [场景管理完整指南](scene-management-guide.md#world多场景管理) 了解详细的实现方案和架构设计
|
|
||||||
|
|
||||||
## System(系统)
|
|
||||||
|
|
||||||
系统处理实体集合,实现游戏的核心逻辑。
|
|
||||||
|
|
||||||
### EntitySystem
|
|
||||||
|
|
||||||
最常用的系统类型,处理实体集合:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.empty().all(MovementComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
for (const entity of entities) {
|
|
||||||
const movement = entity.getComponent(MovementComponent);
|
|
||||||
if (movement) {
|
|
||||||
movement.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ProcessingSystem
|
|
||||||
|
|
||||||
定期处理的系统:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ProcessingSystem, Time, Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class HealthRegenerationSystem extends ProcessingSystem {
|
|
||||||
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) {
|
|
||||||
health.currentHealth += 10 * Time.deltaTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### IntervalSystem
|
|
||||||
|
|
||||||
按时间间隔执行的系统:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { IntervalSystem, Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class SpawnSystem extends IntervalSystem {
|
|
||||||
constructor() {
|
|
||||||
// IntervalSystem需要Matcher和间隔时间
|
|
||||||
super(Matcher.empty(), 3.0); // 每3秒执行一次
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
// 生成敌人
|
|
||||||
const enemy = this.scene.createEntity("Enemy");
|
|
||||||
enemy.addComponent(new EnemyComponent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### PassiveSystem
|
|
||||||
|
|
||||||
被动系统,不自动处理实体:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { PassiveSystem, Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class CollisionSystem extends PassiveSystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
public checkCollisions() {
|
|
||||||
// 手动调用的碰撞检测逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Time(时间)
|
|
||||||
|
|
||||||
时间管理工具类,提供游戏时间相关功能:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Time } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 获取时间信息
|
|
||||||
console.log("帧时间:", Time.deltaTime);
|
|
||||||
console.log("总时间:", Time.totalTime);
|
|
||||||
console.log("帧数:", Time.frameCount);
|
|
||||||
console.log("时间缩放:", Time.timeScale);
|
|
||||||
|
|
||||||
// 设置时间缩放(慢动作效果)
|
|
||||||
Time.timeScale = 0.5;
|
|
||||||
|
|
||||||
// 检查时间间隔
|
|
||||||
if (Time.checkEvery(1.0, lastCheckTime)) {
|
|
||||||
// 每秒执行一次
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控
|
|
||||||
|
|
||||||
框架内置性能监控工具:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { PerformanceMonitor } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 获取性能监控实例
|
|
||||||
const monitor = PerformanceMonitor.instance;
|
|
||||||
|
|
||||||
// 查看性能数据
|
|
||||||
console.log("平均FPS:", monitor.averageFPS);
|
|
||||||
console.log("最小FPS:", monitor.minFPS);
|
|
||||||
console.log("最大FPS:", monitor.maxFPS);
|
|
||||||
console.log("内存使用:", monitor.memoryUsage);
|
|
||||||
|
|
||||||
// 重置性能数据
|
|
||||||
monitor.reset();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 对象池
|
|
||||||
|
|
||||||
内存管理优化工具:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Pool, IPoolable } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 定义可池化的对象(需要实现IPoolable接口)
|
|
||||||
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 Pool<Bullet>(() => new Bullet(), 100);
|
|
||||||
|
|
||||||
// 预热对象池
|
|
||||||
bulletPool.warmUp(20);
|
|
||||||
|
|
||||||
// 使用对象池
|
|
||||||
const bullet = bulletPool.obtain();
|
|
||||||
bullet.x = 100;
|
|
||||||
bullet.y = 200;
|
|
||||||
bullet.speed = 500;
|
|
||||||
|
|
||||||
// 使用完后归还到池中
|
|
||||||
bulletPool.free(bullet);
|
|
||||||
|
|
||||||
// 查看池统计信息
|
|
||||||
console.log(bulletPool.getStats());
|
|
||||||
|
|
||||||
// 清空对象池
|
|
||||||
bulletPool.clear();
|
|
||||||
|
|
||||||
// 使用静态方法(自动管理池)
|
|
||||||
const bullet2 = Pool.obtain(Bullet);
|
|
||||||
Pool.free(Bullet, bullet2);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 实体设计
|
|
||||||
|
|
||||||
- 实体只包含基本属性,功能通过组件实现
|
|
||||||
- 合理使用实体层级关系
|
|
||||||
- 及时销毁不需要的实体
|
|
||||||
|
|
||||||
### 2. 组件设计
|
|
||||||
|
|
||||||
- 组件保持单一职责
|
|
||||||
- 使用生命周期方法进行初始化和清理
|
|
||||||
- 避免组件间直接依赖
|
|
||||||
|
|
||||||
### 3. 系统设计
|
|
||||||
|
|
||||||
- 系统专注于特定逻辑处理
|
|
||||||
- 合理设置系统更新顺序
|
|
||||||
- 使用被动系统处理特殊逻辑
|
|
||||||
|
|
||||||
### 4. 性能优化
|
|
||||||
|
|
||||||
- 使用对象池减少内存分配
|
|
||||||
- 监控性能数据
|
|
||||||
- 合理使用时间缩放
|
|
||||||
|
|
||||||
## 高级性能优化功能
|
|
||||||
|
|
||||||
### 查询系统优化
|
|
||||||
|
|
||||||
框架内部已集成查询优化,无需手动配置。查询系统会自动使用最优的算法:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 查询系统会自动优化这些操作
|
|
||||||
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
|
||||||
const renderableEntities = scene.querySystem.queryAll(PositionComponent, RenderComponent);
|
|
||||||
|
|
||||||
// 获取查询统计信息
|
|
||||||
const queryStats = scene.querySystem.getStats();
|
|
||||||
console.log('查询统计:', queryStats);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量操作API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 批量创建实体 - 最高性能
|
|
||||||
const entities = scene.createEntities(10000, "Bullets");
|
|
||||||
|
|
||||||
// 批量查询优化
|
|
||||||
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
ECS Framework 提供了完整的实体组件系统架构:
|
|
||||||
|
|
||||||
- **Core** 管理游戏生命周期和全局功能,支持单Scene和多World模式
|
|
||||||
- **Entity** 作为游戏对象的基础容器
|
|
||||||
- **Component** 实现具体的功能模块,支持对象池优化
|
|
||||||
- **System** 处理游戏逻辑
|
|
||||||
- **Scene** 管理游戏世界状态,支持批量操作
|
|
||||||
- **World** 高级场景容器,支持多Scene管理和全局System
|
|
||||||
- **WorldManager** 管理多个World实例,适用于复杂架构
|
|
||||||
- **高级优化** 位掩码优化器、组件对象池、批量操作等
|
|
||||||
|
|
||||||
### 架构选择指南
|
|
||||||
|
|
||||||
- **单Scene模式**:适合简单游戏、单机游戏、原型开发
|
|
||||||
- **多World模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目
|
|
||||||
|
|
||||||
框架采用融合设计,确保向后兼容性的同时提供强大的扩展能力。通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
# 实体基础指南
|
|
||||||
|
|
||||||
本指南介绍实体(Entity)的基本概念和基础使用方法。
|
|
||||||
|
|
||||||
> 📖 **需要高级实体管理?** 请参考 [EntityManager 指南](entity-manager-example.md) 了解高性能查询和批量操作
|
|
||||||
|
|
||||||
## 实体概述
|
|
||||||
|
|
||||||
实体(Entity)是 ECS 架构中的核心概念之一,它作为组件的容器存在。实体本身不包含游戏逻辑,所有功能都通过添加不同的组件来实现。
|
|
||||||
|
|
||||||
### 实体的特点
|
|
||||||
|
|
||||||
- **轻量级容器**:实体只是组件的载体,不包含具体的游戏逻辑
|
|
||||||
- **唯一标识**:每个实体都有唯一的ID和名称
|
|
||||||
- **层次结构**:支持父子关系,可以构建复杂的实体层次
|
|
||||||
- **高性能查询**:基于位掩码的组件查询系统
|
|
||||||
- **生命周期管理**:完整的创建、更新、销毁流程
|
|
||||||
|
|
||||||
## 创建实体
|
|
||||||
|
|
||||||
### 基本创建方式
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 通过场景创建实体
|
|
||||||
const scene = new Scene();
|
|
||||||
const entity = scene.createEntity("Player");
|
|
||||||
|
|
||||||
console.log(entity.name); // "Player"
|
|
||||||
console.log(entity.id); // 唯一的数字ID
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量创建实体(推荐)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
const scene = new Scene();
|
|
||||||
|
|
||||||
// 批量创建1000个实体 - 高性能
|
|
||||||
const entities = scene.createEntities(1000, "Enemy");
|
|
||||||
|
|
||||||
// 批量配置
|
|
||||||
entities.forEach((entity, index) => {
|
|
||||||
entity.tag = 2; // 敌人标签
|
|
||||||
// 添加组件...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用流式API创建
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Core } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 使用ECS流式API
|
|
||||||
const entity = Core.ecsAPI
|
|
||||||
?.entity("Enemy")
|
|
||||||
.withComponent(new PositionComponent(100, 200))
|
|
||||||
.withComponent(new HealthComponent(50))
|
|
||||||
.withTag(2)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实体属性
|
|
||||||
|
|
||||||
### 基本属性
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 实体名称 - 用于调试和标识
|
|
||||||
entity.name = "Player";
|
|
||||||
|
|
||||||
// 实体ID - 只读,场景内唯一
|
|
||||||
console.log(entity.id); // 例如: 1
|
|
||||||
|
|
||||||
// 标签 - 用于分类和快速查询
|
|
||||||
entity.tag = 1; // 玩家标签
|
|
||||||
entity.tag = 2; // 敌人标签
|
|
||||||
|
|
||||||
// 更新顺序 - 控制实体在系统中的处理优先级
|
|
||||||
entity.updateOrder = 0; // 数值越小优先级越高
|
|
||||||
```
|
|
||||||
|
|
||||||
### 状态控制
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 启用状态 - 控制实体是否参与更新和处理
|
|
||||||
entity.enabled = true; // 启用实体
|
|
||||||
entity.enabled = false; // 禁用实体
|
|
||||||
|
|
||||||
// 激活状态 - 控制实体及其子实体的活跃状态
|
|
||||||
entity.active = true; // 激活实体
|
|
||||||
entity.active = false; // 停用实体
|
|
||||||
|
|
||||||
// 检查层次结构中的激活状态
|
|
||||||
if (entity.activeInHierarchy) {
|
|
||||||
// 实体在整个层次结构中都是激活的
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查销毁状态
|
|
||||||
if (entity.isDestroyed) {
|
|
||||||
// 实体已被销毁
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 更新间隔
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 控制实体更新频率
|
|
||||||
entity.updateInterval = 1; // 每帧更新
|
|
||||||
entity.updateInterval = 2; // 每2帧更新一次
|
|
||||||
entity.updateInterval = 5; // 每5帧更新一次
|
|
||||||
```
|
|
||||||
|
|
||||||
## 组件管理
|
|
||||||
|
|
||||||
### 添加组件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建并添加组件
|
|
||||||
const healthComponent = entity.addComponent(new HealthComponent(100));
|
|
||||||
|
|
||||||
// 使用工厂方法创建组件
|
|
||||||
const positionComponent = entity.createComponent(PositionComponent, 100, 200);
|
|
||||||
|
|
||||||
// 批量添加组件
|
|
||||||
const components = entity.addComponents([
|
|
||||||
new PositionComponent(0, 0),
|
|
||||||
new VelocityComponent(50, 0),
|
|
||||||
new HealthComponent(100)
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取组件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取单个组件
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (health) {
|
|
||||||
console.log(`当前生命值: ${health.currentHealth}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取或创建组件(如果不存在则创建)
|
|
||||||
const position = entity.getOrCreateComponent(PositionComponent, 0, 0);
|
|
||||||
|
|
||||||
// 获取多个同类型组件(如果组件可以重复添加)
|
|
||||||
const allHealthComponents = entity.getComponents(HealthComponent);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 检查组件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 检查是否拥有指定组件
|
|
||||||
if (entity.hasComponent(HealthComponent)) {
|
|
||||||
// 实体拥有生命值组件
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查组件掩码(高性能)
|
|
||||||
const mask = entity.componentMask;
|
|
||||||
console.log(`组件掩码: ${mask.toString(2)}`); // 二进制表示
|
|
||||||
```
|
|
||||||
|
|
||||||
### 移除组件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 移除指定组件实例
|
|
||||||
const healthComponent = entity.getComponent(HealthComponent);
|
|
||||||
if (healthComponent) {
|
|
||||||
entity.removeComponent(healthComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按类型移除组件
|
|
||||||
const removedHealth = entity.removeComponentByType(HealthComponent);
|
|
||||||
|
|
||||||
// 批量移除组件
|
|
||||||
const removedComponents = entity.removeComponentsByTypes([
|
|
||||||
HealthComponent,
|
|
||||||
VelocityComponent
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 移除所有组件
|
|
||||||
entity.removeAllComponents();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 层次结构管理
|
|
||||||
|
|
||||||
### 父子关系
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建父子实体
|
|
||||||
const player = scene.createEntity("Player");
|
|
||||||
const weapon = scene.createEntity("Weapon");
|
|
||||||
const shield = scene.createEntity("Shield");
|
|
||||||
|
|
||||||
// 添加子实体
|
|
||||||
player.addChild(weapon);
|
|
||||||
player.addChild(shield);
|
|
||||||
|
|
||||||
// 获取父实体
|
|
||||||
console.log(weapon.parent === player); // true
|
|
||||||
|
|
||||||
// 获取所有子实体
|
|
||||||
const children = player.children;
|
|
||||||
console.log(children.length); // 2
|
|
||||||
|
|
||||||
// 获取子实体数量
|
|
||||||
console.log(player.childCount); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查找子实体
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 按名称查找子实体
|
|
||||||
const weapon = player.findChild("Weapon");
|
|
||||||
|
|
||||||
// 递归查找子实体
|
|
||||||
const deepChild = player.findChild("DeepChild", true);
|
|
||||||
|
|
||||||
// 按标签查找子实体
|
|
||||||
const enemies = player.findChildrenByTag(2); // 查找所有敌人标签的子实体
|
|
||||||
|
|
||||||
// 递归按标签查找
|
|
||||||
const allEnemies = player.findChildrenByTag(2, true);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 层次结构操作
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 移除子实体
|
|
||||||
const removed = player.removeChild(weapon);
|
|
||||||
|
|
||||||
// 移除所有子实体
|
|
||||||
player.removeAllChildren();
|
|
||||||
|
|
||||||
// 获取根实体
|
|
||||||
const root = weapon.getRoot();
|
|
||||||
|
|
||||||
// 检查祖先关系
|
|
||||||
if (player.isAncestorOf(weapon)) {
|
|
||||||
// player 是 weapon 的祖先
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查后代关系
|
|
||||||
if (weapon.isDescendantOf(player)) {
|
|
||||||
// weapon 是 player 的后代
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取实体在层次结构中的深度
|
|
||||||
const depth = weapon.getDepth(); // 从根实体开始计算的深度
|
|
||||||
```
|
|
||||||
|
|
||||||
### 遍历子实体
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 遍历直接子实体
|
|
||||||
player.forEachChild((child, index) => {
|
|
||||||
console.log(`子实体 ${index}: ${child.name}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 递归遍历所有子实体
|
|
||||||
player.forEachChild((child, index) => {
|
|
||||||
console.log(`子实体 ${index}: ${child.name} (深度: ${child.getDepth()})`);
|
|
||||||
}, true);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实体生命周期
|
|
||||||
|
|
||||||
### 更新循环
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 手动更新实体(通常由场景自动调用)
|
|
||||||
entity.update();
|
|
||||||
|
|
||||||
// 实体会自动调用所有组件的update方法
|
|
||||||
class MyComponent extends Component {
|
|
||||||
public update(): void {
|
|
||||||
// 组件更新逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 销毁实体
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 销毁实体
|
|
||||||
entity.destroy();
|
|
||||||
|
|
||||||
// 检查是否已销毁
|
|
||||||
if (entity.isDestroyed) {
|
|
||||||
console.log("实体已被销毁");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 销毁实体时会自动:
|
|
||||||
// 1. 移除所有组件
|
|
||||||
// 2. 从父实体中移除
|
|
||||||
// 3. 销毁所有子实体
|
|
||||||
// 4. 从场景中移除
|
|
||||||
```
|
|
||||||
|
|
||||||
# 高级特性请参考其他指南
|
|
||||||
|
|
||||||
> **更多功能:**
|
|
||||||
> - **高性能查询和批量操作** → [EntityManager 指南](entity-manager-example.md)
|
|
||||||
> - **性能优化技术** → [性能优化指南](performance-optimization.md)
|
|
||||||
> - **组件索引和缓存** → [技术概念详解](concepts-explained.md)
|
|
||||||
|
|
||||||
## 基础最佳实践
|
|
||||||
|
|
||||||
### 1. 合理使用标签
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 定义标签常量
|
|
||||||
const EntityTags = {
|
|
||||||
PLAYER: 1,
|
|
||||||
ENEMY: 2,
|
|
||||||
PROJECTILE: 3,
|
|
||||||
PICKUP: 4
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// 使用标签进行分类
|
|
||||||
player.tag = EntityTags.PLAYER;
|
|
||||||
enemy.tag = EntityTags.ENEMY;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 正确的销毁处理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 确保正确销毁实体
|
|
||||||
if (!entity.isDestroyed) {
|
|
||||||
entity.destroy(); // 自动移除组件和层次关系
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查实体状态
|
|
||||||
if (entity.isDestroyed) {
|
|
||||||
return; // 避免操作已销毁的实体
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 组件生命周期
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 正确添加组件
|
|
||||||
const health = entity.addComponent(new HealthComponent(100));
|
|
||||||
|
|
||||||
// 安全获取组件
|
|
||||||
const healthComp = entity.getComponent(HealthComponent);
|
|
||||||
if (healthComp && healthComp.currentHealth <= 0) {
|
|
||||||
entity.destroy();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### Q: 实体如何实现位置、旋转等变换?
|
|
||||||
|
|
||||||
A: 通过添加相应的组件:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class TransformComponent extends Component {
|
|
||||||
public position = { x: 0, y: 0 };
|
|
||||||
public rotation = 0;
|
|
||||||
public scale = { x: 1, y: 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.addComponent(new TransformComponent());
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 实体可以在场景间移动吗?
|
|
||||||
|
|
||||||
A: 不可以。实体与场景绑定,需要在新场景中重新创建。
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
# EntityManager 使用指南
|
|
||||||
|
|
||||||
本文档详细介绍 EntityManager 的使用方法和最佳实践。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
1. [基础用法](#基础用法)
|
|
||||||
2. [查询系统](#查询系统)
|
|
||||||
3. [实体管理](#实体管理)
|
|
||||||
4. [性能监控](#性能监控)
|
|
||||||
5. [最佳实践](#最佳实践)
|
|
||||||
|
|
||||||
## 基础用法
|
|
||||||
|
|
||||||
### 创建 EntityManager
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntityManager, Scene } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建场景和实体管理器
|
|
||||||
const scene = new Scene();
|
|
||||||
const entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 批量创建实体(使用Scene方法)
|
|
||||||
const enemies = scene.createEntities(50, "Enemy");
|
|
||||||
|
|
||||||
// 为实体添加组件
|
|
||||||
enemies.forEach((enemy, index) => {
|
|
||||||
enemy.addComponent(new PositionComponent(
|
|
||||||
Math.random() * 800,
|
|
||||||
Math.random() * 600
|
|
||||||
));
|
|
||||||
enemy.addComponent(new HealthComponent(100));
|
|
||||||
enemy.addComponent(new VelocityComponent(
|
|
||||||
(Math.random() - 0.5) * 100,
|
|
||||||
(Math.random() - 0.5) * 100
|
|
||||||
));
|
|
||||||
enemy.tag = 2; // 敌人标签
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 查询系统
|
|
||||||
|
|
||||||
### 基础查询
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 按组件类型查询
|
|
||||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
|
||||||
|
|
||||||
// 按标签查询
|
|
||||||
const enemies = entityManager.getEntitiesByTag(2);
|
|
||||||
const players = entityManager.getEntitiesByTag(1);
|
|
||||||
|
|
||||||
// 按名称查询
|
|
||||||
const boss = entityManager.getEntityByName("BossEnemy");
|
|
||||||
|
|
||||||
// 获取所有实体
|
|
||||||
const allEntities = entityManager.getAllEntities();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 流式查询 API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 复杂查询条件
|
|
||||||
const movingEnemies = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent, VelocityComponent, HealthComponent)
|
|
||||||
.withTag(2) // 敌人标签
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// 查询活跃的玩家
|
|
||||||
const activePlayers = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent)
|
|
||||||
.withTag(1) // 玩家标签
|
|
||||||
.active() // 只查询活跃实体
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// 排除特定组件的实体
|
|
||||||
const nonCombatEntities = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent)
|
|
||||||
.without(WeaponComponent, HealthComponent)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// 自定义条件查询
|
|
||||||
const nearbyEnemies = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent)
|
|
||||||
.withTag(2)
|
|
||||||
.where(entity => {
|
|
||||||
const pos = entity.getComponent(PositionComponent);
|
|
||||||
return pos && Math.abs(pos.x - playerX) < 100;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实体管理
|
|
||||||
|
|
||||||
### 创建和销毁实体
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建单个实体
|
|
||||||
const player = entityManager.createEntity("Player");
|
|
||||||
player.addComponent(new PositionComponent(400, 300));
|
|
||||||
player.addComponent(new HealthComponent(100));
|
|
||||||
player.tag = 1;
|
|
||||||
|
|
||||||
// 销毁实体
|
|
||||||
entityManager.destroyEntity(player);
|
|
||||||
|
|
||||||
// 按名称销毁
|
|
||||||
entityManager.destroyEntity("Enemy_1");
|
|
||||||
|
|
||||||
// 按ID销毁
|
|
||||||
entityManager.destroyEntity(123);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实体查找
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 按ID查找
|
|
||||||
const entity = entityManager.getEntity(123);
|
|
||||||
|
|
||||||
// 按名称查找
|
|
||||||
const player = entityManager.getEntityByName("Player");
|
|
||||||
|
|
||||||
// 检查实体是否存在
|
|
||||||
if (entity && !entity.isDestroyed) {
|
|
||||||
// 实体有效
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控
|
|
||||||
|
|
||||||
### 基础统计
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取实体数量
|
|
||||||
console.log('总实体数:', entityManager.entityCount);
|
|
||||||
console.log('活跃实体数:', entityManager.activeEntityCount);
|
|
||||||
|
|
||||||
// 获取场景统计
|
|
||||||
const sceneStats = scene.getStats();
|
|
||||||
console.log('场景统计:', {
|
|
||||||
实体数量: sceneStats.entityCount,
|
|
||||||
系统数量: sceneStats.processorCount
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取查询系统统计
|
|
||||||
const queryStats = scene.querySystem.getStats();
|
|
||||||
console.log('查询统计:', queryStats);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 高效查询
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 好的做法:缓存查询结果
|
|
||||||
class CombatSystem extends EntitySystem {
|
|
||||||
private cachedEnemies: Entity[] = [];
|
|
||||||
private lastUpdateFrame = 0;
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// 每5帧更新一次缓存
|
|
||||||
if (Time.frameCount - this.lastUpdateFrame > 5) {
|
|
||||||
this.cachedEnemies = this.entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent, HealthComponent)
|
|
||||||
.withTag(2)
|
|
||||||
.execute();
|
|
||||||
this.lastUpdateFrame = Time.frameCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用缓存的结果
|
|
||||||
this.cachedEnemies.forEach(enemy => {
|
|
||||||
// 处理敌人逻辑
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 批量操作
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 好的做法:批量创建和配置
|
|
||||||
function createBulletWave(count: number): Entity[] {
|
|
||||||
// 使用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[] {
|
|
||||||
const attackerPos = attacker.getComponent(PositionComponent);
|
|
||||||
if (!attackerPos) return [];
|
|
||||||
|
|
||||||
// 先按标签快速筛选,再按距离过滤
|
|
||||||
return this.entityManager
|
|
||||||
.getEntitiesByTag(2) // 敌人标签
|
|
||||||
.filter(enemy => {
|
|
||||||
const enemyPos = enemy.getComponent(PositionComponent);
|
|
||||||
if (!enemyPos) return false;
|
|
||||||
|
|
||||||
const distance = Math.sqrt(
|
|
||||||
Math.pow(attackerPos.x - enemyPos.x, 2) +
|
|
||||||
Math.pow(attackerPos.y - enemyPos.y, 2)
|
|
||||||
);
|
|
||||||
return distance <= range;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 完整示例
|
|
||||||
|
|
||||||
```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 {
|
|
||||||
// 创建玩家
|
|
||||||
const player = this.entityManager.createEntity("Player");
|
|
||||||
player.addComponent(new PositionComponent(400, 300));
|
|
||||||
player.addComponent(new HealthComponent(100));
|
|
||||||
player.tag = 1;
|
|
||||||
|
|
||||||
// 创建敌人
|
|
||||||
const enemies = this.scene.createEntities(10, "Enemy");
|
|
||||||
enemies.forEach(enemy => {
|
|
||||||
enemy.addComponent(new PositionComponent(
|
|
||||||
Math.random() * 800,
|
|
||||||
Math.random() * 600
|
|
||||||
));
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
const healthEntities = this.scene.querySystem.queryAll(HealthComponent);
|
|
||||||
|
|
||||||
healthEntities.entities.forEach(entity => {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (health && health.currentHealth <= 0) {
|
|
||||||
console.log(`实体 ${entity.name} 死亡`);
|
|
||||||
entity.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动游戏
|
|
||||||
const game = new GameManager();
|
|
||||||
setInterval(() => game.update(), 16); // 60 FPS
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
EntityManager 提供了强大的实体管理功能:
|
|
||||||
|
|
||||||
- **创建管理**:`createEntity()`, `destroyEntity()`
|
|
||||||
- **查询功能**:`getEntitiesWithComponent()`, `getEntitiesByTag()`, `query()`
|
|
||||||
- **实体查找**:`getEntity()`, `getEntityByName()`
|
|
||||||
- **统计信息**:`entityCount`, `activeEntityCount`
|
|
||||||
|
|
||||||
通过合理使用这些API,可以构建高性能的游戏系统。记住要及时清理无用实体,缓存频繁查询的结果,并使用合适的查询方法来优化性能。
|
|
||||||
@@ -1,496 +0,0 @@
|
|||||||
# ECS事件系统使用指南
|
|
||||||
|
|
||||||
本文档介绍如何使用ECS框架的增强事件系统,包括类型安全的事件发布订阅、预定义的ECS事件类型和高级功能。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
1. [基础用法](#基础用法)
|
|
||||||
2. [预定义ECS事件](#预定义ecs事件)
|
|
||||||
3. [事件装饰器](#事件装饰器)
|
|
||||||
4. [高级功能](#高级功能)
|
|
||||||
5. [性能优化](#性能优化)
|
|
||||||
6. [最佳实践](#最佳实践)
|
|
||||||
|
|
||||||
## 基础用法
|
|
||||||
|
|
||||||
### 创建事件总线
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EventBus, GlobalEventBus } from './ECS';
|
|
||||||
|
|
||||||
// 方式1:创建独立的事件总线
|
|
||||||
const eventBus = new EventBus(true); // true启用调试模式
|
|
||||||
|
|
||||||
// 方式2:使用全局事件总线
|
|
||||||
const globalEventBus = GlobalEventBus.getInstance(true);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 基本事件发布订阅
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 定义事件数据类型
|
|
||||||
interface PlayerDiedEvent {
|
|
||||||
playerId: number;
|
|
||||||
cause: string;
|
|
||||||
position: { x: number; y: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听事件
|
|
||||||
const listenerId = eventBus.on<PlayerDiedEvent>('player:died', (data) => {
|
|
||||||
console.log(`Player ${data.playerId} died at (${data.position.x}, ${data.position.y})`);
|
|
||||||
console.log(`Cause: ${data.cause}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 发射事件
|
|
||||||
eventBus.emit('player:died', {
|
|
||||||
playerId: 123,
|
|
||||||
cause: 'enemy_attack',
|
|
||||||
position: { x: 100, y: 200 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// 移除监听器
|
|
||||||
eventBus.off('player:died', listenerId);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 一次性事件监听
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 只监听一次
|
|
||||||
eventBus.once<PlayerDiedEvent>('player:died', (data) => {
|
|
||||||
console.log('This will only be called once');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 异步事件处理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 异步事件监听
|
|
||||||
eventBus.onAsync<PlayerDiedEvent>('player:died', async (data) => {
|
|
||||||
await savePlayerDeathToDatabase(data);
|
|
||||||
await updateLeaderboard(data.playerId);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 异步事件发射
|
|
||||||
await eventBus.emitAsync('player:died', playerData);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 预定义ECS事件
|
|
||||||
|
|
||||||
框架提供了完整的ECS事件类型定义,支持实体、组件、系统等核心概念的事件。
|
|
||||||
|
|
||||||
### 实体事件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ECSEventType, IEntityEventData } from './ECS';
|
|
||||||
|
|
||||||
// 监听实体创建事件
|
|
||||||
eventBus.onEntityCreated((data: IEntityEventData) => {
|
|
||||||
console.log(`Entity created: ${data.entityName} (ID: ${data.entityId})`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听实体销毁事件
|
|
||||||
eventBus.on<IEntityEventData>(ECSEventType.ENTITY_DESTROYED, (data) => {
|
|
||||||
console.log(`Entity destroyed: ${data.entityName}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 手动发射实体事件
|
|
||||||
eventBus.emitEntityCreated({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
source: 'GameManager',
|
|
||||||
entityId: 123,
|
|
||||||
entityName: 'Player',
|
|
||||||
entityTag: 'player'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 组件事件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { IComponentEventData } from './ECS';
|
|
||||||
|
|
||||||
// 监听组件添加事件
|
|
||||||
eventBus.onComponentAdded((data: IComponentEventData) => {
|
|
||||||
console.log(`Component ${data.componentType} added to entity ${data.entityId}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听组件移除事件
|
|
||||||
eventBus.on<IComponentEventData>(ECSEventType.COMPONENT_REMOVED, (data) => {
|
|
||||||
console.log(`Component ${data.componentType} removed from entity ${data.entityId}`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 系统事件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ISystemEventData } from './ECS';
|
|
||||||
|
|
||||||
// 监听系统错误
|
|
||||||
eventBus.onSystemError((data: ISystemEventData) => {
|
|
||||||
console.error(`System error in ${data.systemName}: ${data.systemType}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听系统处理开始/结束
|
|
||||||
eventBus.on<ISystemEventData>(ECSEventType.SYSTEM_PROCESSING_START, (data) => {
|
|
||||||
console.log(`System ${data.systemName} started processing`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 性能事件
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { IPerformanceEventData } from './ECS';
|
|
||||||
|
|
||||||
// 监听性能警告
|
|
||||||
eventBus.onPerformanceWarning((data: IPerformanceEventData) => {
|
|
||||||
console.warn(`Performance warning: ${data.operation} took ${data.executionTime}ms`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听内存使用过高
|
|
||||||
eventBus.on<IPerformanceEventData>(ECSEventType.MEMORY_USAGE_HIGH, (data) => {
|
|
||||||
console.warn(`High memory usage: ${data.memoryUsage}MB`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 事件装饰器
|
|
||||||
|
|
||||||
使用装饰器可以自动注册事件监听器,简化代码编写。
|
|
||||||
|
|
||||||
### 基础装饰器
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EventHandler, AsyncEventHandler, EventPriority } from './ECS';
|
|
||||||
|
|
||||||
class GameManager {
|
|
||||||
@EventHandler(ECSEventType.ENTITY_CREATED, { priority: EventPriority.HIGH })
|
|
||||||
onEntityCreated(data: IEntityEventData) {
|
|
||||||
console.log(`New entity: ${data.entityName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AsyncEventHandler(ECSEventType.ENTITY_DESTROYED)
|
|
||||||
async onEntityDestroyed(data: IEntityEventData) {
|
|
||||||
await this.cleanupEntityResources(data.entityId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler('custom:game:event', { once: true })
|
|
||||||
onGameStart(data: any) {
|
|
||||||
console.log('Game started!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 需要手动调用初始化方法
|
|
||||||
constructor() {
|
|
||||||
// 如果类有initEventListeners方法,会自动注册装饰器定义的监听器
|
|
||||||
if (this.initEventListeners) {
|
|
||||||
this.initEventListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async cleanupEntityResources(entityId: number) {
|
|
||||||
// 清理实体相关资源
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 优先级和配置
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class SystemManager {
|
|
||||||
@EventHandler(ECSEventType.SYSTEM_ERROR, {
|
|
||||||
priority: EventPriority.CRITICAL,
|
|
||||||
context: this
|
|
||||||
})
|
|
||||||
handleSystemError(data: ISystemEventData) {
|
|
||||||
this.logError(data);
|
|
||||||
this.restartSystem(data.systemName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AsyncEventHandler(ECSEventType.PERFORMANCE_WARNING, {
|
|
||||||
priority: EventPriority.LOW,
|
|
||||||
async: true
|
|
||||||
})
|
|
||||||
async handlePerformanceWarning(data: IPerformanceEventData) {
|
|
||||||
await this.optimizePerformance(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private logError(data: ISystemEventData) {
|
|
||||||
// 错误日志记录
|
|
||||||
}
|
|
||||||
|
|
||||||
private restartSystem(systemName: string) {
|
|
||||||
// 重启系统
|
|
||||||
}
|
|
||||||
|
|
||||||
private async optimizePerformance(data: IPerformanceEventData) {
|
|
||||||
// 性能优化逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 高级功能
|
|
||||||
|
|
||||||
### 事件批处理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 设置批处理配置
|
|
||||||
eventBus.setBatchConfig('entity:update', 100, 16); // 批量100个,延迟16ms
|
|
||||||
|
|
||||||
// 发射事件(会被批处理)
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
eventBus.emit('entity:update', { entityId: i, data: 'update' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动刷新批处理队列
|
|
||||||
eventBus.flushBatch('entity:update');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 事件统计和监控
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取单个事件统计
|
|
||||||
const stats = eventBus.getStats('entity:created');
|
|
||||||
console.log(`Event triggered ${stats.triggerCount} times`);
|
|
||||||
console.log(`Average execution time: ${stats.averageExecutionTime}ms`);
|
|
||||||
|
|
||||||
// 获取所有事件统计
|
|
||||||
const allStats = eventBus.getStats();
|
|
||||||
if (allStats instanceof Map) {
|
|
||||||
allStats.forEach((stat, eventType) => {
|
|
||||||
console.log(`${eventType}: ${stat.triggerCount} triggers`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置统计
|
|
||||||
eventBus.resetStats('entity:created');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 事件类型验证
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EventTypeValidator } from './ECS';
|
|
||||||
|
|
||||||
// 检查事件类型是否有效
|
|
||||||
if (EventTypeValidator.isValid('entity:created')) {
|
|
||||||
eventBus.emit('entity:created', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加自定义事件类型
|
|
||||||
EventTypeValidator.addCustomType('game:custom:event');
|
|
||||||
|
|
||||||
// 获取所有有效事件类型
|
|
||||||
const validTypes = EventTypeValidator.getAllValidTypes();
|
|
||||||
console.log('Valid event types:', validTypes);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 调试和日志
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 启用调试模式
|
|
||||||
eventBus.setDebugMode(true);
|
|
||||||
|
|
||||||
// 设置最大监听器数量
|
|
||||||
eventBus.setMaxListeners(50);
|
|
||||||
|
|
||||||
// 检查是否有监听器
|
|
||||||
if (eventBus.hasListeners('entity:created')) {
|
|
||||||
console.log('Has listeners for entity:created');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取监听器数量
|
|
||||||
const count = eventBus.getListenerCount('entity:created');
|
|
||||||
console.log(`${count} listeners for entity:created`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能优化
|
|
||||||
|
|
||||||
### 事件过滤和条件监听
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 使用条件过滤减少不必要的事件处理
|
|
||||||
eventBus.on<IEntityEventData>(ECSEventType.ENTITY_CREATED, (data) => {
|
|
||||||
// 只处理玩家实体
|
|
||||||
if (data.entityTag === 'player') {
|
|
||||||
handlePlayerCreated(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更好的方式:使用具体的事件类型
|
|
||||||
eventBus.on<IEntityEventData>('entity:player:created', handlePlayerCreated);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 内存管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class EventManager {
|
|
||||||
private listeners: string[] = [];
|
|
||||||
|
|
||||||
public setupListeners() {
|
|
||||||
// 保存监听器ID以便清理
|
|
||||||
this.listeners.push(
|
|
||||||
eventBus.on('event1', this.handler1.bind(this)),
|
|
||||||
eventBus.on('event2', this.handler2.bind(this))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public cleanup() {
|
|
||||||
// 清理所有监听器
|
|
||||||
this.listeners.forEach(id => {
|
|
||||||
eventBus.off('event1', id);
|
|
||||||
eventBus.off('event2', id);
|
|
||||||
});
|
|
||||||
this.listeners.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handler1(data: any) { /* ... */ }
|
|
||||||
private handler2(data: any) { /* ... */ }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 异步事件优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 使用Promise.all并行处理多个异步事件
|
|
||||||
const promises = [
|
|
||||||
eventBus.emitAsync('save:player', playerData),
|
|
||||||
eventBus.emitAsync('update:leaderboard', scoreData),
|
|
||||||
eventBus.emitAsync('notify:friends', notificationData)
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 事件命名规范
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐的事件命名格式:模块:对象:动作
|
|
||||||
const EVENT_NAMES = {
|
|
||||||
// 实体相关
|
|
||||||
ENTITY_PLAYER_CREATED: 'entity:player:created',
|
|
||||||
ENTITY_ENEMY_DESTROYED: 'entity:enemy:destroyed',
|
|
||||||
|
|
||||||
// 游戏逻辑相关
|
|
||||||
GAME_LEVEL_COMPLETED: 'game:level:completed',
|
|
||||||
GAME_SCORE_UPDATED: 'game:score:updated',
|
|
||||||
|
|
||||||
// UI相关
|
|
||||||
UI_MENU_OPENED: 'ui:menu:opened',
|
|
||||||
UI_BUTTON_CLICKED: 'ui:button:clicked'
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 类型安全
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 定义强类型的事件数据接口
|
|
||||||
interface GameEvents {
|
|
||||||
'player:levelup': { playerId: number; newLevel: number; experience: number };
|
|
||||||
'inventory:item:added': { itemId: string; quantity: number; playerId: number };
|
|
||||||
'combat:damage:dealt': { attackerId: number; targetId: number; damage: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建类型安全的事件发射器
|
|
||||||
class TypedEventBus {
|
|
||||||
private eventBus = new EventBus();
|
|
||||||
|
|
||||||
emit<K extends keyof GameEvents>(eventType: K, data: GameEvents[K]) {
|
|
||||||
this.eventBus.emit(eventType, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
on<K extends keyof GameEvents>(
|
|
||||||
eventType: K,
|
|
||||||
handler: (data: GameEvents[K]) => void
|
|
||||||
) {
|
|
||||||
return this.eventBus.on(eventType, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 错误处理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 在事件处理器中添加错误处理
|
|
||||||
eventBus.on<IEntityEventData>(ECSEventType.ENTITY_CREATED, (data) => {
|
|
||||||
try {
|
|
||||||
processEntityCreation(data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error processing entity creation:', error);
|
|
||||||
// 发射错误事件
|
|
||||||
eventBus.emit(ECSEventType.ERROR_OCCURRED, {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
source: 'EntityCreationHandler',
|
|
||||||
error: error.message,
|
|
||||||
context: data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 模块化事件管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 为不同模块创建专门的事件管理器
|
|
||||||
class PlayerEventManager {
|
|
||||||
constructor(private eventBus: EventBus) {
|
|
||||||
this.setupListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupListeners() {
|
|
||||||
this.eventBus.onEntityCreated(this.onPlayerCreated.bind(this));
|
|
||||||
this.eventBus.on('player:levelup', this.onPlayerLevelUp.bind(this));
|
|
||||||
this.eventBus.on('player:died', this.onPlayerDied.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPlayerCreated(data: IEntityEventData) {
|
|
||||||
if (data.entityTag === 'player') {
|
|
||||||
// 处理玩家创建逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPlayerLevelUp(data: any) {
|
|
||||||
// 处理玩家升级逻辑
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPlayerDied(data: any) {
|
|
||||||
// 处理玩家死亡逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 与EntityManager集成
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntityManager } from './ECS';
|
|
||||||
|
|
||||||
// EntityManager会自动设置事件总线
|
|
||||||
const entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 获取事件总线实例
|
|
||||||
const eventBus = entityManager.eventBus;
|
|
||||||
|
|
||||||
// 监听自动发射的ECS事件
|
|
||||||
eventBus.onEntityCreated((data) => {
|
|
||||||
console.log('Entity created automatically:', data);
|
|
||||||
});
|
|
||||||
|
|
||||||
eventBus.onComponentAdded((data) => {
|
|
||||||
console.log('Component added automatically:', data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建实体时会自动发射事件
|
|
||||||
const entity = entityManager.createEntity('Player');
|
|
||||||
|
|
||||||
// 添加组件时会自动发射事件
|
|
||||||
entity.addComponent(new HealthComponent(100));
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
ECS框架的事件系统提供了:
|
|
||||||
|
|
||||||
- **类型安全**:完整的TypeScript类型支持
|
|
||||||
- **高性能**:批处理、缓存和优化机制
|
|
||||||
- **易用性**:装饰器、预定义事件类型
|
|
||||||
- **可扩展**:自定义事件类型和验证
|
|
||||||
- **调试友好**:详细的统计信息和调试模式
|
|
||||||
|
|
||||||
通过合理使用事件系统,可以实现松耦合的模块化架构,提高代码的可维护性和扩展性。
|
|
||||||
3
docs/examples/index.md
Normal file
3
docs/examples/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 示例
|
||||||
|
|
||||||
|
代码示例。
|
||||||
@@ -1,689 +0,0 @@
|
|||||||
# 快速入门
|
|
||||||
|
|
||||||
本指南将帮助您快速上手 ECS Framework,这是一个专业级的实体组件系统框架,采用现代化架构设计,专为高性能游戏开发打造。
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @esengine/ecs-framework
|
|
||||||
```
|
|
||||||
|
|
||||||
## 更新机制说明
|
|
||||||
|
|
||||||
ECS框架需要在游戏引擎的更新循环中调用,并传入deltaTime:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 统一的更新方式:让外部引擎传入deltaTime
|
|
||||||
Core.update(deltaTime);
|
|
||||||
```
|
|
||||||
|
|
||||||
**不同平台的集成方式:**
|
|
||||||
- **Laya引擎**:使用 `Laya.timer.delta / 1000`
|
|
||||||
- **Cocos Creator**:使用组件的 `update(deltaTime)` 参数
|
|
||||||
- **原生浏览器**:自己计算deltaTime
|
|
||||||
- **Node.js服务器**:自己计算deltaTime
|
|
||||||
|
|
||||||
**优势:**
|
|
||||||
- 与引擎时间系统完全同步
|
|
||||||
- 支持引擎的时间缩放和暂停功能
|
|
||||||
- 更精确的时间控制
|
|
||||||
- 统一的API,简化集成
|
|
||||||
|
|
||||||
## Core配置
|
|
||||||
|
|
||||||
### 基础配置
|
|
||||||
|
|
||||||
ECS框架提供了灵活的配置选项来满足不同项目需求。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Core, ICoreConfig } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 方式1:简化配置(向后兼容)
|
|
||||||
Core.create(true); // 启用调试模式
|
|
||||||
Core.create(false); // 发布模式
|
|
||||||
Core.create(); // 默认调试模式
|
|
||||||
|
|
||||||
// 方式2:详细配置(推荐)
|
|
||||||
const config: ICoreConfig = {
|
|
||||||
debug: true, // 启用调试模式
|
|
||||||
enableEntitySystems: true, // 启用实体系统(默认true)
|
|
||||||
debugConfig: { // 可选:远程调试配置
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080',
|
|
||||||
autoReconnect: true,
|
|
||||||
updateInterval: 1000, // 调试数据更新间隔(毫秒)
|
|
||||||
channels: { // 调试数据通道
|
|
||||||
entities: true, // 实体信息
|
|
||||||
systems: true, // 系统信息
|
|
||||||
performance: true, // 性能数据
|
|
||||||
components: true, // 组件信息
|
|
||||||
scenes: true // 场景信息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const core = Core.create(config);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 架构说明
|
|
||||||
|
|
||||||
ECS框架支持两种使用模式:
|
|
||||||
|
|
||||||
1. **单Scene模式(默认,向后兼容)**:
|
|
||||||
```typescript
|
|
||||||
// 传统用法,无需任何修改
|
|
||||||
const scene = new Scene();
|
|
||||||
Core.setScene(scene);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **多World模式(高级功能)**:
|
|
||||||
```typescript
|
|
||||||
// 启用World管理器,支持多World/多Scene架构
|
|
||||||
Core.enableWorldManager();
|
|
||||||
const roomWorld = Core.getWorldManager().createWorld('Room_001');
|
|
||||||
const battleScene = roomWorld.createScene('battle');
|
|
||||||
```
|
|
||||||
|
|
||||||
**使用场景:**
|
|
||||||
- **单Scene模式**:适合简单游戏、单机游戏、原型开发
|
|
||||||
- **多World模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目
|
|
||||||
|
|
||||||
> **详细了解World系统**:查看 [场景管理完整指南](scene-management-guide.md) 获取完整的多World架构示例和最佳实践
|
|
||||||
|
|
||||||
### 调试功能
|
|
||||||
|
|
||||||
ECS框架内置了强大的调试功能,支持运行时监控和远程调试:
|
|
||||||
|
|
||||||
#### Cocos Creator专用调试插件
|
|
||||||
|
|
||||||
** 对于Cocos Creator用户,我们提供了专门的可视化调试插件:**
|
|
||||||
|
|
||||||
- **插件地址**:[cocos-ecs-framework 调试插件](https://store.cocos.com/app/detail/7823)
|
|
||||||
- **插件版本**:v1.0.0
|
|
||||||
- **支持版本**:Cocos Creator v3.0.0+
|
|
||||||
- **支持平台**:Android | iOS | HTML5
|
|
||||||
|
|
||||||
这个插件提供了完整的ECS可视化调试界面,包括实体查看器、组件编辑器、系统监控、性能分析等功能。
|
|
||||||
|
|
||||||
#### 通用调试配置
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 运行时启用调试
|
|
||||||
Core.enableDebug({
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080',
|
|
||||||
autoReconnect: true,
|
|
||||||
updateInterval: 500,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: false, // 可以选择性禁用某些通道
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取调试数据
|
|
||||||
const debugData = Core.getDebugData();
|
|
||||||
console.log('当前实体数量:', debugData?.entities?.totalEntities);
|
|
||||||
|
|
||||||
// 禁用调试
|
|
||||||
Core.disableDebug();
|
|
||||||
|
|
||||||
// 检查调试状态
|
|
||||||
if (Core.isDebugEnabled) {
|
|
||||||
console.log('调试模式已启用');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生产环境配置建议
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 开发环境 - Cocos Creator
|
|
||||||
const devConfigForCocos: ICoreConfig = {
|
|
||||||
debug: true,
|
|
||||||
enableEntitySystems: true,
|
|
||||||
debugConfig: {
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080', // 连接Cocos插件
|
|
||||||
autoReconnect: true,
|
|
||||||
updateInterval: 1000,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: true,
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开发环境 - 其他平台
|
|
||||||
const devConfig: ICoreConfig = {
|
|
||||||
debug: true,
|
|
||||||
enableEntitySystems: true,
|
|
||||||
debugConfig: {
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080',
|
|
||||||
autoReconnect: true,
|
|
||||||
updateInterval: 1000,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: true,
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生产环境
|
|
||||||
const prodConfig: ICoreConfig = {
|
|
||||||
debug: false, // 关闭调试以提升性能
|
|
||||||
enableEntitySystems: true,
|
|
||||||
// debugConfig 可以省略或设为 undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
||||||
Core.create(isDevelopment ? devConfig : prodConfig);
|
|
||||||
```
|
|
||||||
|
|
||||||
** 调试功能说明:**
|
|
||||||
- **Cocos Creator**:推荐使用[官方调试插件](https://store.cocos.com/app/detail/7823)获得最佳调试体验
|
|
||||||
- **其他平台**:可以通过WebSocket连接自定义调试工具
|
|
||||||
- **生产环境**:建议关闭调试功能以获得最佳性能
|
|
||||||
|
|
||||||
## 平台集成
|
|
||||||
|
|
||||||
### Laya引擎
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene as LayaScene } from "laya/display/Scene";
|
|
||||||
import { Core, Scene as ECSScene, EntityManager, EntitySystem } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class LayaECSGame extends LayaScene {
|
|
||||||
private ecsScene: ECSScene;
|
|
||||||
private entityManager: EntityManager;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// 初始化ECS框架(简化方式)
|
|
||||||
Core.create(true); // 启用调试模式
|
|
||||||
// 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} })
|
|
||||||
|
|
||||||
this.ecsScene = new ECSScene();
|
|
||||||
this.ecsScene.name = "LayaGameScene";
|
|
||||||
Core.setScene(this.ecsScene);
|
|
||||||
|
|
||||||
this.entityManager = new EntityManager();
|
|
||||||
this.setupSystems();
|
|
||||||
}
|
|
||||||
|
|
||||||
onAwake(): void {
|
|
||||||
super.onAwake();
|
|
||||||
// 使用Laya的帧循环更新ECS
|
|
||||||
Laya.timer.frameLoop(1, this, this.updateECS);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(): void {
|
|
||||||
Laya.timer.clear(this, this.updateECS);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateECS(): void {
|
|
||||||
// 使用Laya的deltaTime更新ECS
|
|
||||||
const deltaTime = Laya.timer.delta / 1000; // 转换为秒
|
|
||||||
Core.update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupSystems(): void {
|
|
||||||
this.ecsScene.addEntityProcessor(new LayaRenderSystem(this));
|
|
||||||
this.ecsScene.addEntityProcessor(new MovementSystem());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Laya渲染系统
|
|
||||||
class LayaRenderSystem extends EntitySystem {
|
|
||||||
private layaScene: LayaScene;
|
|
||||||
|
|
||||||
constructor(layaScene: LayaScene) {
|
|
||||||
super(Matcher.all(PositionComponent, SpriteComponent));
|
|
||||||
this.layaScene = layaScene;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]): void {
|
|
||||||
entities.forEach(entity => {
|
|
||||||
const pos = entity.getComponent(PositionComponent)!;
|
|
||||||
const sprite = entity.getComponent(SpriteComponent)!;
|
|
||||||
|
|
||||||
if (sprite.layaSprite) {
|
|
||||||
sprite.layaSprite.x = pos.x;
|
|
||||||
sprite.layaSprite.y = pos.y;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用方法
|
|
||||||
Laya.Scene.open("GameScene.scene", false, null, null, LayaECSGame);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cocos Creator
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Component as CocosComponent, _decorator, Node } from 'cc';
|
|
||||||
import { Core, Scene as ECSScene, EntityManager, EntitySystem } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
@ccclass('ECSGameManager')
|
|
||||||
export class ECSGameManager extends CocosComponent {
|
|
||||||
private ecsScene: ECSScene;
|
|
||||||
private entityManager: EntityManager;
|
|
||||||
|
|
||||||
start() {
|
|
||||||
// 初始化ECS框架(简化方式)
|
|
||||||
Core.create(true); // 启用调试模式
|
|
||||||
// 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} })
|
|
||||||
|
|
||||||
this.ecsScene = new ECSScene();
|
|
||||||
this.ecsScene.name = "CocosGameScene";
|
|
||||||
Core.setScene(this.ecsScene);
|
|
||||||
|
|
||||||
this.entityManager = new EntityManager();
|
|
||||||
this.setupSystems();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime: number) {
|
|
||||||
// 使用Cocos Creator的deltaTime更新ECS
|
|
||||||
Core.update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
if (this.ecsScene) {
|
|
||||||
this.ecsScene.onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupSystems(): void {
|
|
||||||
this.ecsScene.addEntityProcessor(new CocosRenderSystem(this.node));
|
|
||||||
this.ecsScene.addEntityProcessor(new MovementSystem());
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEntityManager(): EntityManager {
|
|
||||||
return this.entityManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cocos渲染系统
|
|
||||||
class CocosRenderSystem extends EntitySystem {
|
|
||||||
private rootNode: Node;
|
|
||||||
|
|
||||||
constructor(rootNode: Node) {
|
|
||||||
super(Matcher.all(PositionComponent, SpriteComponent));
|
|
||||||
this.rootNode = rootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]): void {
|
|
||||||
entities.forEach(entity => {
|
|
||||||
const pos = entity.getComponent(PositionComponent)!;
|
|
||||||
const sprite = entity.getComponent(SpriteComponent)!;
|
|
||||||
|
|
||||||
if (sprite.cocosNode) {
|
|
||||||
sprite.cocosNode.setPosition(pos.x, pos.y);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将ECSGameManager脚本挂载到场景根节点
|
|
||||||
```
|
|
||||||
|
|
||||||
** Cocos Creator调试提示:**
|
|
||||||
为了获得最佳的ECS调试体验,建议安装我们的专用调试插件:
|
|
||||||
- 插件地址:[https://store.cocos.com/app/detail/7823](https://store.cocos.com/app/detail/7823)
|
|
||||||
- 支持Cocos Creator v3.0.0+
|
|
||||||
- 提供实体查看器、组件编辑器、系统监控等功能
|
|
||||||
|
|
||||||
### Node.js后端
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Core, Scene, EntityManager, EntitySystem, Time, World } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class ServerGameManager {
|
|
||||||
private scene: Scene;
|
|
||||||
private entityManager: EntityManager;
|
|
||||||
private isRunning: boolean = false;
|
|
||||||
private tickRate: number = 60; // 60 TPS
|
|
||||||
private lastUpdate: number = Date.now();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 初始化ECS框架(简化方式)
|
|
||||||
Core.create(true); // 启用调试模式
|
|
||||||
// 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} })
|
|
||||||
|
|
||||||
// 单Scene模式(简单场景)
|
|
||||||
this.scene = new Scene();
|
|
||||||
this.scene.name = "ServerScene";
|
|
||||||
Core.setScene(this.scene);
|
|
||||||
|
|
||||||
this.entityManager = new EntityManager();
|
|
||||||
this.setupSystems();
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(): void {
|
|
||||||
this.isRunning = true;
|
|
||||||
console.log(`游戏服务器启动,TPS: ${this.tickRate}`);
|
|
||||||
this.gameLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop(): void {
|
|
||||||
this.isRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private gameLoop(): void {
|
|
||||||
if (!this.isRunning) return;
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const deltaTime = (now - this.lastUpdate) / 1000;
|
|
||||||
this.lastUpdate = now;
|
|
||||||
|
|
||||||
// 使用计算出的deltaTime更新ECS
|
|
||||||
Core.update(deltaTime);
|
|
||||||
|
|
||||||
const frameTime = 1000 / this.tickRate;
|
|
||||||
const processingTime = Date.now() - now;
|
|
||||||
const delay = Math.max(0, frameTime - processingTime);
|
|
||||||
|
|
||||||
setTimeout(() => this.gameLoop(), delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupSystems(): void {
|
|
||||||
this.scene.addEntityProcessor(new ServerMovementSystem());
|
|
||||||
this.scene.addEntityProcessor(new NetworkSyncSystem());
|
|
||||||
this.scene.addEntityProcessor(new AISystem());
|
|
||||||
}
|
|
||||||
|
|
||||||
public handlePlayerInput(playerId: string, input: any): void {
|
|
||||||
const playerEntity = this.findPlayerEntity(playerId);
|
|
||||||
if (playerEntity) {
|
|
||||||
const inputComp = playerEntity.getComponent(InputComponent);
|
|
||||||
if (inputComp) {
|
|
||||||
inputComp.updateInput(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorldState(): any {
|
|
||||||
const entities = this.entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent, NetworkComponent)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return entities.map(entity => ({
|
|
||||||
id: entity.id,
|
|
||||||
position: entity.getComponent(PositionComponent),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private findPlayerEntity(playerId: string): Entity | null {
|
|
||||||
const players = this.entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PlayerComponent)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return players.find(player =>
|
|
||||||
player.getComponent(PlayerComponent).playerId === playerId
|
|
||||||
) || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动服务器
|
|
||||||
const server = new ServerGameManager();
|
|
||||||
server.start();
|
|
||||||
```
|
|
||||||
|
|
||||||
> **多房间游戏服务器示例**:查看 [场景管理完整指南](scene-management-guide.md#world多场景管理) 了解如何使用多World架构实现复杂的多房间游戏服务器
|
|
||||||
|
|
||||||
### 原生浏览器
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Core, Scene, EntityManager, EntitySystem } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class BrowserGame {
|
|
||||||
private scene: Scene;
|
|
||||||
private entityManager: EntityManager;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 初始化ECS框架(简化方式)
|
|
||||||
Core.create(true); // 启用调试模式
|
|
||||||
// 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} })
|
|
||||||
|
|
||||||
this.scene = new Scene();
|
|
||||||
this.scene.name = "BrowserScene";
|
|
||||||
Core.setScene(this.scene);
|
|
||||||
|
|
||||||
this.entityManager = new EntityManager();
|
|
||||||
this.setupSystems();
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(): void {
|
|
||||||
this.createEntities();
|
|
||||||
this.gameLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private gameLoop(): void {
|
|
||||||
let lastTime = 0;
|
|
||||||
const update = (currentTime: number) => {
|
|
||||||
// 计算deltaTime并更新ECS(原生浏览器环境)
|
|
||||||
const deltaTime = lastTime > 0 ? (currentTime - lastTime) / 1000 : 0.016;
|
|
||||||
lastTime = currentTime;
|
|
||||||
Core.update(deltaTime);
|
|
||||||
requestAnimationFrame(update);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupSystems(): void {
|
|
||||||
this.scene.addEntityProcessor(new MovementSystem());
|
|
||||||
this.scene.addEntityProcessor(new RenderSystem());
|
|
||||||
}
|
|
||||||
|
|
||||||
private createEntities(): void {
|
|
||||||
const player = this.entityManager.createEntity("Player");
|
|
||||||
player.addComponent(new PositionComponent(400, 300));
|
|
||||||
player.addComponent(new VelocityComponent(0, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const game = new BrowserGame();
|
|
||||||
game.start();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 基础组件定义
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 位置组件
|
|
||||||
class PositionComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [x = 0, y = 0] = args as [number?, number?];
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset() {
|
|
||||||
this.x = 0;
|
|
||||||
this.y = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 速度组件
|
|
||||||
class VelocityComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [x = 0, y = 0] = args as [number?, number?];
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset() {
|
|
||||||
this.x = 0;
|
|
||||||
this.y = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命值组件
|
|
||||||
class HealthComponent extends Component {
|
|
||||||
public maxHealth: number = 100;
|
|
||||||
public currentHealth: number = 100;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [maxHealth = 100] = args as [number?];
|
|
||||||
this.maxHealth = maxHealth;
|
|
||||||
this.currentHealth = maxHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset() {
|
|
||||||
this.maxHealth = 100;
|
|
||||||
this.currentHealth = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
public takeDamage(damage: number): void {
|
|
||||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public heal(amount: number): void {
|
|
||||||
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isDead(): boolean {
|
|
||||||
return this.currentHealth <= 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 基础系统创建
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(PositionComponent, VelocityComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]): void {
|
|
||||||
entities.forEach(entity => {
|
|
||||||
const position = entity.getComponent(PositionComponent)!;
|
|
||||||
const velocity = entity.getComponent(VelocityComponent)!;
|
|
||||||
|
|
||||||
position.x += velocity.x * Time.deltaTime;
|
|
||||||
position.y += velocity.y * Time.deltaTime;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HealthSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(HealthComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override process(entities: Entity[]): void {
|
|
||||||
entities.forEach(entity => {
|
|
||||||
const health = entity.getComponent(HealthComponent)!;
|
|
||||||
if (health.currentHealth <= 0) {
|
|
||||||
entity.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实体管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 创建实体
|
|
||||||
const player = entityManager.createEntity("Player");
|
|
||||||
player.addComponent(new PositionComponent(100, 100));
|
|
||||||
player.addComponent(new VelocityComponent(5, 0));
|
|
||||||
player.addComponent(new HealthComponent(100));
|
|
||||||
|
|
||||||
// 批量创建实体
|
|
||||||
const enemies = scene.createEntities(50, "Enemy");
|
|
||||||
enemies.forEach(enemy => {
|
|
||||||
enemy.addComponent(new PositionComponent(Math.random() * 800, Math.random() * 600));
|
|
||||||
enemy.addComponent(new HealthComponent(50));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 查询实体 - 推荐方式
|
|
||||||
const movingEntities = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent, VelocityComponent)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// 简单查询方式
|
|
||||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
|
||||||
const taggedEntities = entityManager.getEntitiesByTag(2);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 事件系统
|
|
||||||
|
|
||||||
推荐使用Scene的事件系统或EntityManager的事件系统:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 使用EntityManager的事件系统(推荐)
|
|
||||||
const eventBus = entityManager.eventBus;
|
|
||||||
|
|
||||||
// 监听ECS事件
|
|
||||||
eventBus.onEntityCreated((data) => {
|
|
||||||
console.log(`实体创建: ${data.entityName}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
eventBus.onComponentAdded((data) => {
|
|
||||||
console.log(`组件添加: ${data.componentType}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 发射自定义事件
|
|
||||||
eventBus.emit('player:died', { player: entity, score: 1000 });
|
|
||||||
|
|
||||||
// 使用装饰器自动注册事件监听器
|
|
||||||
import { EventHandler } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class GameSystem {
|
|
||||||
@EventHandler('player:died')
|
|
||||||
onPlayerDied(data: { player: Entity; score: number }) {
|
|
||||||
console.log(`玩家死亡,得分: ${data.score}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取场景统计
|
|
||||||
const sceneStats = scene.getStats();
|
|
||||||
console.log('实体数量:', sceneStats.entityCount);
|
|
||||||
console.log('系统数量:', sceneStats.processorCount);
|
|
||||||
|
|
||||||
// 获取查询统计
|
|
||||||
const queryStats = scene.querySystem.getStats();
|
|
||||||
console.log('查询统计:', queryStats);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 下一步
|
|
||||||
|
|
||||||
- [EntityManager 使用指南](entity-manager-example.md) - 详细了解实体管理器的高级功能
|
|
||||||
- [性能优化指南](performance-optimization.md) - 深入了解三大性能优化系统
|
|
||||||
- [核心概念](core-concepts.md) - 深入了解 ECS 架构和设计原理
|
|
||||||
- [查询系统使用指南](query-system-usage.md) - 学习高性能查询系统的详细用法
|
|
||||||
359
docs/guide/component.md
Normal file
359
docs/guide/component.md
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
# 组件系统
|
||||||
|
|
||||||
|
在 ECS 架构中,组件(Component)是数据和行为的载体。组件定义了实体具有的属性和功能,是 ECS 架构的核心构建块。
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
组件是继承自 `Component` 抽象基类的具体类,用于:
|
||||||
|
- 存储实体的数据(如位置、速度、健康值等)
|
||||||
|
- 定义与数据相关的行为方法
|
||||||
|
- 提供生命周期回调钩子
|
||||||
|
- 支持序列化和调试
|
||||||
|
|
||||||
|
## 创建组件
|
||||||
|
|
||||||
|
### 基础组件定义
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Health')
|
||||||
|
class Health extends Component {
|
||||||
|
current: number;
|
||||||
|
max: number;
|
||||||
|
|
||||||
|
constructor(max: number = 100) {
|
||||||
|
super();
|
||||||
|
this.max = max;
|
||||||
|
this.current = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件可以包含行为方法
|
||||||
|
takeDamage(damage: number): void {
|
||||||
|
this.current = Math.max(0, this.current - damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
heal(amount: number): void {
|
||||||
|
this.current = Math.min(this.max, this.current + amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDead(): boolean {
|
||||||
|
return this.current <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 组件装饰器
|
||||||
|
|
||||||
|
**必须使用 `@ECSComponent` 装饰器**,这确保了:
|
||||||
|
- 组件在代码混淆后仍能正确识别
|
||||||
|
- 提供稳定的类型名称用于序列化和调试
|
||||||
|
- 框架能正确管理组件注册
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 正确的用法
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
class Velocity extends Component {
|
||||||
|
dx: number = 0;
|
||||||
|
dy: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 错误的用法 - 没有装饰器
|
||||||
|
class BadComponent extends Component {
|
||||||
|
// 这样定义的组件可能在生产环境出现问题
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件生命周期
|
||||||
|
|
||||||
|
组件提供了生命周期钩子,可以重写来执行特定的逻辑:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSComponent('ExampleComponent')
|
||||||
|
class ExampleComponent extends Component {
|
||||||
|
private resource: SomeResource | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件被添加到实体时调用
|
||||||
|
* 用于初始化资源、建立引用等
|
||||||
|
*/
|
||||||
|
onAddedToEntity(): void {
|
||||||
|
console.log(`组件 ${this.constructor.name} 被添加到实体 ${this.entity.name}`);
|
||||||
|
this.resource = new SomeResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件从实体移除时调用
|
||||||
|
* 用于清理资源、断开引用等
|
||||||
|
*/
|
||||||
|
onRemovedFromEntity(): void {
|
||||||
|
console.log(`组件 ${this.constructor.name} 从实体 ${this.entity.name} 移除`);
|
||||||
|
if (this.resource) {
|
||||||
|
this.resource.cleanup();
|
||||||
|
this.resource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 访问实体
|
||||||
|
|
||||||
|
组件可以通过 `this.entity` 访问其所属的实体:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSComponent('Damage')
|
||||||
|
class Damage extends Component {
|
||||||
|
damage: number;
|
||||||
|
|
||||||
|
constructor(damage: number) {
|
||||||
|
super();
|
||||||
|
this.damage = damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在组件方法中访问实体和其他组件
|
||||||
|
applyDamage(): void {
|
||||||
|
const health = this.entity.getComponent(Health);
|
||||||
|
if (health) {
|
||||||
|
health.takeDamage(this.damage);
|
||||||
|
|
||||||
|
// 如果生命值为0,销毁实体
|
||||||
|
if (health.isDead()) {
|
||||||
|
this.entity.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件属性
|
||||||
|
|
||||||
|
每个组件都有一些内置属性:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSComponent('ExampleComponent')
|
||||||
|
class ExampleComponent extends Component {
|
||||||
|
someData: string = "example";
|
||||||
|
|
||||||
|
showComponentInfo(): void {
|
||||||
|
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
|
||||||
|
console.log(`所属实体: ${this.entity.name}`); // 所属实体引用
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 复杂组件示例
|
||||||
|
|
||||||
|
### 状态机组件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enum EntityState {
|
||||||
|
Idle,
|
||||||
|
Moving,
|
||||||
|
Attacking,
|
||||||
|
Dead
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('StateMachine')
|
||||||
|
class StateMachine extends Component {
|
||||||
|
private _currentState: EntityState = EntityState.Idle;
|
||||||
|
private _previousState: EntityState = EntityState.Idle;
|
||||||
|
private _stateTimer: number = 0;
|
||||||
|
|
||||||
|
get currentState(): EntityState {
|
||||||
|
return this._currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
get previousState(): EntityState {
|
||||||
|
return this._previousState;
|
||||||
|
}
|
||||||
|
|
||||||
|
get stateTimer(): number {
|
||||||
|
return this._stateTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeState(newState: EntityState): void {
|
||||||
|
if (this._currentState !== newState) {
|
||||||
|
this._previousState = this._currentState;
|
||||||
|
this._currentState = newState;
|
||||||
|
this._stateTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimer(deltaTime: number): void {
|
||||||
|
this._stateTimer += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInState(state: EntityState): boolean {
|
||||||
|
return this._currentState === state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置数据组件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface WeaponData {
|
||||||
|
damage: number;
|
||||||
|
range: number;
|
||||||
|
fireRate: number;
|
||||||
|
ammo: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('WeaponConfig')
|
||||||
|
class WeaponConfig extends Component {
|
||||||
|
data: WeaponData;
|
||||||
|
|
||||||
|
constructor(weaponData: WeaponData) {
|
||||||
|
super();
|
||||||
|
this.data = { ...weaponData }; // 深拷贝避免共享引用
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供便捷的访问方法
|
||||||
|
getDamage(): number {
|
||||||
|
return this.data.damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
canFire(): boolean {
|
||||||
|
return this.data.ammo > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeAmmo(): boolean {
|
||||||
|
if (this.data.ammo > 0) {
|
||||||
|
this.data.ammo--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 保持组件简单
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的组件设计 - 单一职责
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
class Velocity extends Component {
|
||||||
|
dx: number = 0;
|
||||||
|
dy: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 避免的组件设计 - 职责过多
|
||||||
|
@ECSComponent('GameObject')
|
||||||
|
class GameObject extends Component {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
dx: number;
|
||||||
|
dy: number;
|
||||||
|
health: number;
|
||||||
|
damage: number;
|
||||||
|
sprite: string;
|
||||||
|
// 太多不相关的属性
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用构造函数初始化
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSComponent('Transform')
|
||||||
|
class Transform extends Component {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
rotation: number;
|
||||||
|
scale: number;
|
||||||
|
|
||||||
|
constructor(x = 0, y = 0, rotation = 0, scale = 1) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.rotation = rotation;
|
||||||
|
this.scale = scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 明确的类型定义
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface InventoryItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
type: 'weapon' | 'consumable' | 'misc';
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Inventory')
|
||||||
|
class Inventory extends Component {
|
||||||
|
items: InventoryItem[] = [];
|
||||||
|
maxSlots: number;
|
||||||
|
|
||||||
|
constructor(maxSlots: number = 20) {
|
||||||
|
super();
|
||||||
|
this.maxSlots = maxSlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(item: InventoryItem): boolean {
|
||||||
|
if (this.items.length < this.maxSlots) {
|
||||||
|
this.items.push(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(itemId: string): InventoryItem | null {
|
||||||
|
const index = this.items.findIndex(item => item.id === itemId);
|
||||||
|
if (index !== -1) {
|
||||||
|
return this.items.splice(index, 1)[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 避免在组件中存储实体引用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ 避免:在组件中存储其他实体的引用
|
||||||
|
@ECSComponent('BadFollower')
|
||||||
|
class BadFollower extends Component {
|
||||||
|
target: Entity; // 直接引用可能导致内存泄漏
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 推荐:存储实体ID,通过场景查找
|
||||||
|
@ECSComponent('Follower')
|
||||||
|
class Follower extends Component {
|
||||||
|
targetId: number;
|
||||||
|
followDistance: number = 50;
|
||||||
|
|
||||||
|
constructor(targetId: number) {
|
||||||
|
super();
|
||||||
|
this.targetId = targetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTarget(): Entity | null {
|
||||||
|
return this.entity.scene?.findEntityById(this.targetId) || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。
|
||||||
288
docs/guide/entity.md
Normal file
288
docs/guide/entity.md
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
# 实体类
|
||||||
|
|
||||||
|
在 ECS 架构中,实体(Entity)是游戏世界中的基本对象。实体本身不包含游戏逻辑或数据,它只是一个容器,用来组合不同的组件来实现各种功能。
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
实体是一个轻量级的对象,主要用于:
|
||||||
|
- 作为组件的容器
|
||||||
|
- 提供唯一标识(ID)
|
||||||
|
- 管理组件的生命周期
|
||||||
|
|
||||||
|
## 创建实体
|
||||||
|
|
||||||
|
**重要提示:实体必须通过场景创建,不支持手动创建!**
|
||||||
|
|
||||||
|
实体必须通过场景的 `createEntity()` 方法来创建,这样才能确保:
|
||||||
|
- 实体被正确添加到场景的实体管理系统中
|
||||||
|
- 实体被添加到查询系统中,供系统使用
|
||||||
|
- 实体获得正确的场景引用
|
||||||
|
- 触发相关的生命周期事件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 正确的方式:通过场景创建实体
|
||||||
|
const player = scene.createEntity("Player");
|
||||||
|
|
||||||
|
// ❌ 错误的方式:手动创建实体
|
||||||
|
// const entity = new Entity("MyEntity", 1); // 这样创建的实体系统无法管理
|
||||||
|
```
|
||||||
|
|
||||||
|
## 添加组件
|
||||||
|
|
||||||
|
实体通过添加组件来获得功能:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 定义位置组件
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义健康组件
|
||||||
|
@ECSComponent('Health')
|
||||||
|
class Health extends Component {
|
||||||
|
current: number = 100;
|
||||||
|
max: number = 100;
|
||||||
|
|
||||||
|
constructor(max: number = 100) {
|
||||||
|
super();
|
||||||
|
this.max = max;
|
||||||
|
this.current = max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给实体添加组件
|
||||||
|
const player = scene.createEntity("Player");
|
||||||
|
player.addComponent(new Position(100, 200));
|
||||||
|
player.addComponent(new Health(150));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 获取组件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取组件(传入组件类,不是实例)
|
||||||
|
const position = player.getComponent(Position); // 返回 Position | null
|
||||||
|
const health = player.getComponent(Health); // 返回 Health | null
|
||||||
|
|
||||||
|
// 检查组件是否存在
|
||||||
|
if (position) {
|
||||||
|
console.log(`玩家位置: x=${position.x}, y=${position.y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有某个组件
|
||||||
|
if (player.hasComponent(Position)) {
|
||||||
|
console.log("玩家有位置组件");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有组件实例(直接访问 components 属性)
|
||||||
|
const allComponents = player.components; // Component[]
|
||||||
|
|
||||||
|
// 获取指定类型的所有组件(支持同类型多组件)
|
||||||
|
const allHealthComponents = player.getComponents(Health); // Health[]
|
||||||
|
|
||||||
|
// 获取或创建组件(如果不存在则自动创建)
|
||||||
|
const position = player.getOrCreateComponent(Position, 0, 0); // 传入构造参数
|
||||||
|
const health = player.getOrCreateComponent(Health, 100); // 如果存在则返回现有的,不存在则创建新的
|
||||||
|
```
|
||||||
|
|
||||||
|
## 移除组件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 方式1:通过组件类型移除
|
||||||
|
const removedHealth = player.removeComponentByType(Health);
|
||||||
|
if (removedHealth) {
|
||||||
|
console.log("健康组件已被移除");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2:通过组件实例移除
|
||||||
|
const healthComponent = player.getComponent(Health);
|
||||||
|
if (healthComponent) {
|
||||||
|
player.removeComponent(healthComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量移除多种组件类型
|
||||||
|
const removedComponents = player.removeComponentsByTypes([Position, Health]);
|
||||||
|
|
||||||
|
// 检查组件是否被移除
|
||||||
|
if (!player.hasComponent(Health)) {
|
||||||
|
console.log("健康组件已被移除");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实体查找
|
||||||
|
|
||||||
|
场景提供了多种方式来查找实体:
|
||||||
|
|
||||||
|
### 通过名称查找
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 查找单个实体
|
||||||
|
const player = scene.findEntity("Player");
|
||||||
|
// 或使用别名方法
|
||||||
|
const player2 = scene.getEntityByName("Player");
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
console.log("找到玩家实体");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通过 ID 查找
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 通过实体 ID 查找
|
||||||
|
const entity = scene.findEntityById(123);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通过标签查找
|
||||||
|
|
||||||
|
实体支持标签系统,用于快速分类和查找:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 设置标签
|
||||||
|
player.tag = 1; // 玩家标签
|
||||||
|
enemy.tag = 2; // 敌人标签
|
||||||
|
|
||||||
|
// 通过标签查找所有相关实体
|
||||||
|
const players = scene.findEntitiesByTag(1);
|
||||||
|
const enemies = scene.findEntitiesByTag(2);
|
||||||
|
// 或使用别名方法
|
||||||
|
const allPlayers = scene.getEntitiesByTag(1);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 实体生命周期
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 销毁实体
|
||||||
|
player.destroy();
|
||||||
|
|
||||||
|
// 检查实体是否已销毁
|
||||||
|
if (player.isDestroyed) {
|
||||||
|
console.log("实体已被销毁");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实体事件
|
||||||
|
|
||||||
|
实体的组件变化会触发事件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 监听组件添加事件
|
||||||
|
scene.eventSystem.on('component:added', (data) => {
|
||||||
|
console.log('组件已添加:', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听实体创建事件
|
||||||
|
scene.eventSystem.on('entity:created', (data) => {
|
||||||
|
console.log('实体已创建:', data.entityName);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
|
||||||
|
### 批量创建实体
|
||||||
|
|
||||||
|
框架提供了高性能的批量创建方法:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 批量创建 100 个子弹实体(高性能版本)
|
||||||
|
const bullets = scene.createEntities(100, "Bullet");
|
||||||
|
|
||||||
|
// 为每个子弹添加组件
|
||||||
|
bullets.forEach((bullet, index) => {
|
||||||
|
bullet.addComponent(new Position(Math.random() * 800, Math.random() * 600));
|
||||||
|
bullet.addComponent(new Velocity(Math.random() * 100 - 50, Math.random() * 100 - 50));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`createEntities()` 方法会:
|
||||||
|
- 批量分配实体 ID
|
||||||
|
- 批量添加到实体列表
|
||||||
|
- 优化查询系统更新
|
||||||
|
- 减少系统缓存清理次数
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 合理的组件粒度
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 好的做法:功能单一的组件
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
class Velocity extends Component {
|
||||||
|
dx: number = 0;
|
||||||
|
dy: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免:功能过于复杂的组件
|
||||||
|
@ECSComponent('Player')
|
||||||
|
class Player extends Component {
|
||||||
|
// 避免在一个组件中包含太多不相关的属性
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
health: number;
|
||||||
|
inventory: Item[];
|
||||||
|
skills: Skill[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用装饰器
|
||||||
|
|
||||||
|
始终使用 `@ECSComponent` 装饰器:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSComponent('Transform')
|
||||||
|
class Transform extends Component {
|
||||||
|
// 组件实现
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 合理命名
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 清晰的实体命名
|
||||||
|
const mainCharacter = scene.createEntity("MainCharacter");
|
||||||
|
const enemy1 = scene.createEntity("Goblin_001");
|
||||||
|
const collectible = scene.createEntity("HealthPotion");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 及时清理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 不再需要的实体应该及时销毁
|
||||||
|
if (enemy.getComponent(Health).current <= 0) {
|
||||||
|
enemy.destroy();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 调试实体
|
||||||
|
|
||||||
|
框架提供了调试功能来帮助开发:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取实体调试信息
|
||||||
|
const debugInfo = entity.getDebugInfo();
|
||||||
|
console.log('实体信息:', debugInfo);
|
||||||
|
|
||||||
|
// 列出实体的所有组件
|
||||||
|
entity.components.forEach(component => {
|
||||||
|
console.log('组件:', component.constructor.name);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
实体是 ECS 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。
|
||||||
595
docs/guide/event-system.md
Normal file
595
docs/guide/event-system.md
Normal file
@@ -0,0 +1,595 @@
|
|||||||
|
# 事件系统
|
||||||
|
|
||||||
|
ECS 框架内置了强大的类型安全事件系统,支持同步/异步事件、优先级、批处理等高级功能。事件系统是实现组件间通信、系统间协作的核心机制。
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
事件系统提供了发布-订阅模式的实现,包含以下核心概念:
|
||||||
|
- **事件发布者**:发射事件的对象
|
||||||
|
- **事件监听者**:监听并处理特定事件的对象
|
||||||
|
- **事件类型**:字符串标识,用于区分不同类型的事件
|
||||||
|
- **事件数据**:事件携带的相关信息
|
||||||
|
|
||||||
|
## 事件系统架构
|
||||||
|
|
||||||
|
框架提供了两层事件系统:
|
||||||
|
|
||||||
|
1. **TypeSafeEventSystem** - 底层高性能事件系统
|
||||||
|
2. **EventBus** - 上层增强事件总线,提供更多便利功能
|
||||||
|
|
||||||
|
## 基本使用
|
||||||
|
|
||||||
|
### 在场景中使用事件系统
|
||||||
|
|
||||||
|
每个场景都有内置的事件系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 监听事件
|
||||||
|
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
|
||||||
|
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
|
||||||
|
this.eventSystem.on('score_changed', this.onScoreChanged.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlayerDied(data: { player: Entity, cause: string }): void {
|
||||||
|
console.log(`玩家死亡,原因: ${data.cause}`);
|
||||||
|
// 处理玩家死亡逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEnemySpawned(data: { enemy: Entity, position: { x: number, y: number } }): void {
|
||||||
|
console.log('敌人生成于:', data.position);
|
||||||
|
// 处理敌人生成逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private onScoreChanged(data: { newScore: number, oldScore: number }): void {
|
||||||
|
console.log(`分数变化: ${data.oldScore} -> ${data.newScore}`);
|
||||||
|
// 更新UI显示
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在系统中发射事件
|
||||||
|
someGameLogic(): void {
|
||||||
|
// 发射同步事件
|
||||||
|
this.eventSystem.emitSync('score_changed', {
|
||||||
|
newScore: 1000,
|
||||||
|
oldScore: 800
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在系统中使用事件
|
||||||
|
|
||||||
|
系统可以方便地监听和发送事件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Combat')
|
||||||
|
class CombatSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Health, Combat));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onInitialize(): void {
|
||||||
|
// 使用系统提供的事件监听方法(自动清理)
|
||||||
|
this.addEventListener('player_attack', this.onPlayerAttack.bind(this));
|
||||||
|
this.addEventListener('enemy_death', this.onEnemyDeath.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlayerAttack(data: { damage: number, target: Entity }): void {
|
||||||
|
// 处理玩家攻击事件
|
||||||
|
const health = data.target.getComponent(Health);
|
||||||
|
if (health) {
|
||||||
|
health.current -= data.damage;
|
||||||
|
|
||||||
|
if (health.current <= 0) {
|
||||||
|
// 发送敌人死亡事件
|
||||||
|
this.scene?.eventSystem.emitSync('enemy_death', {
|
||||||
|
enemy: data.target,
|
||||||
|
killer: 'player'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEnemyDeath(data: { enemy: Entity, killer: string }): void {
|
||||||
|
// 处理敌人死亡
|
||||||
|
data.enemy.destroy();
|
||||||
|
|
||||||
|
// 发送经验奖励事件
|
||||||
|
this.scene?.eventSystem.emitSync('experience_gained', {
|
||||||
|
amount: 100,
|
||||||
|
source: 'enemy_kill'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const combat = entity.getComponent(Combat);
|
||||||
|
if (combat && combat.shouldAttack()) {
|
||||||
|
// 发射攻击事件
|
||||||
|
this.scene?.eventSystem.emitSync('player_attack', {
|
||||||
|
damage: combat.damage,
|
||||||
|
target: combat.target
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级功能
|
||||||
|
|
||||||
|
### 一次性监听器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 只监听一次的事件
|
||||||
|
this.eventSystem.once('game_start', this.onGameStart.bind(this));
|
||||||
|
|
||||||
|
// 或者使用配置对象
|
||||||
|
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this), {
|
||||||
|
once: true // 只执行一次
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onGameStart(): void {
|
||||||
|
console.log('游戏开始!');
|
||||||
|
// 这个方法只会被调用一次
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLevelComplete(): void {
|
||||||
|
console.log('关卡完成!');
|
||||||
|
// 这个方法也只会被调用一次
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优先级控制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 高优先级监听器先执行
|
||||||
|
this.eventSystem.on('damage_dealt', this.onDamageDealt.bind(this), {
|
||||||
|
priority: 100 // 高优先级
|
||||||
|
});
|
||||||
|
|
||||||
|
// 普通优先级
|
||||||
|
this.eventSystem.on('damage_dealt', this.updateUI.bind(this), {
|
||||||
|
priority: 0 // 默认优先级
|
||||||
|
});
|
||||||
|
|
||||||
|
// 低优先级最后执行
|
||||||
|
this.eventSystem.on('damage_dealt', this.logDamage.bind(this), {
|
||||||
|
priority: -100 // 低优先级
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDamageDealt(data: any): void {
|
||||||
|
// 最先执行 - 处理核心游戏逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUI(data: any): void {
|
||||||
|
// 中等优先级 - 更新界面
|
||||||
|
}
|
||||||
|
|
||||||
|
private logDamage(data: any): void {
|
||||||
|
// 最后执行 - 记录日志
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步事件处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 监听异步事件
|
||||||
|
this.eventSystem.onAsync('save_game', this.onSaveGame.bind(this));
|
||||||
|
this.eventSystem.onAsync('load_data', this.onLoadData.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onSaveGame(data: { saveSlot: number }): Promise<void> {
|
||||||
|
console.log(`开始保存游戏到槽位 ${data.saveSlot}`);
|
||||||
|
|
||||||
|
// 模拟异步保存操作
|
||||||
|
await this.saveGameData(data.saveSlot);
|
||||||
|
|
||||||
|
console.log('游戏保存完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onLoadData(data: { url: string }): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(data.url);
|
||||||
|
const gameData = await response.json();
|
||||||
|
// 处理加载的数据
|
||||||
|
} catch (error) {
|
||||||
|
console.error('数据加载失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveGameData(slot: number): Promise<void> {
|
||||||
|
// 模拟保存操作
|
||||||
|
return new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发射异步事件
|
||||||
|
public async triggerSave(): Promise<void> {
|
||||||
|
// 使用 emit 而不是 emitSync 来触发异步监听器
|
||||||
|
await this.eventSystem.emit('save_game', { saveSlot: 1 });
|
||||||
|
console.log('所有异步保存操作完成');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件统计和调试
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
this.eventSystem.on('debug_event', this.onDebugEvent.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDebugEvent(): void {
|
||||||
|
// 处理调试事件
|
||||||
|
}
|
||||||
|
|
||||||
|
public showEventStats(): void {
|
||||||
|
// 获取特定事件的统计信息
|
||||||
|
const stats = this.eventSystem.getStats('debug_event') as any;
|
||||||
|
if (stats) {
|
||||||
|
console.log('事件统计:');
|
||||||
|
console.log(`- 事件类型: ${stats.eventType}`);
|
||||||
|
console.log(`- 监听器数量: ${stats.listenerCount}`);
|
||||||
|
console.log(`- 触发次数: ${stats.triggerCount}`);
|
||||||
|
console.log(`- 平均执行时间: ${stats.averageExecutionTime.toFixed(2)}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有事件统计
|
||||||
|
const allStats = this.eventSystem.getStats() as Map<string, any>;
|
||||||
|
console.log(`总共有 ${allStats.size} 种事件类型`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetEventStats(): void {
|
||||||
|
// 重置特定事件的统计
|
||||||
|
this.eventSystem.resetStats('debug_event');
|
||||||
|
|
||||||
|
// 或重置所有统计
|
||||||
|
this.eventSystem.resetStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全局事件总线
|
||||||
|
|
||||||
|
对于跨场景的事件通信,可以使用全局事件总线:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { GlobalEventBus } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameManager {
|
||||||
|
private eventBus = GlobalEventBus.getInstance();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setupGlobalEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupGlobalEvents(): void {
|
||||||
|
// 监听全局事件
|
||||||
|
this.eventBus.on('player_level_up', this.onPlayerLevelUp.bind(this));
|
||||||
|
this.eventBus.on('achievement_unlocked', this.onAchievementUnlocked.bind(this));
|
||||||
|
this.eventBus.onAsync('upload_score', this.onUploadScore.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlayerLevelUp(data: { level: number, experience: number }): void {
|
||||||
|
console.log(`玩家升级到 ${data.level} 级!`);
|
||||||
|
// 处理全局升级逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAchievementUnlocked(data: { achievementId: string, name: string }): void {
|
||||||
|
console.log(`解锁成就: ${data.name}`);
|
||||||
|
// 显示成就通知
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onUploadScore(data: { score: number, playerName: string }): Promise<void> {
|
||||||
|
// 异步上传分数到服务器
|
||||||
|
try {
|
||||||
|
await this.uploadToServer(data);
|
||||||
|
console.log('分数上传成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('分数上传失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerGlobalEvent(): void {
|
||||||
|
// 发射全局事件
|
||||||
|
this.eventBus.emit('player_level_up', {
|
||||||
|
level: 10,
|
||||||
|
experience: 2500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadToServer(data: any): Promise<void> {
|
||||||
|
// 模拟服务器上传
|
||||||
|
return new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 事件装饰器
|
||||||
|
|
||||||
|
使用装饰器自动注册事件监听器:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EventHandler, AsyncEventHandler } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class PlayerController {
|
||||||
|
constructor() {
|
||||||
|
// 自动调用事件监听器注册
|
||||||
|
this.initEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler('player_input')
|
||||||
|
private onPlayerInput(data: { action: string, value: number }): void {
|
||||||
|
console.log(`玩家输入: ${data.action} = ${data.value}`);
|
||||||
|
// 处理玩家输入
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler('player_attack', { priority: 100 })
|
||||||
|
private onPlayerAttack(data: { damage: number, target: string }): void {
|
||||||
|
console.log(`玩家攻击 ${data.target},造成 ${data.damage} 伤害`);
|
||||||
|
// 处理攻击逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
@AsyncEventHandler('save_progress')
|
||||||
|
private async onSaveProgress(data: { checkpointId: string }): Promise<void> {
|
||||||
|
console.log(`保存进度到检查点: ${data.checkpointId}`);
|
||||||
|
// 异步保存进度
|
||||||
|
await this.saveToCloud(data.checkpointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler('game_over', { once: true })
|
||||||
|
private onGameOver(): void {
|
||||||
|
console.log('游戏结束!');
|
||||||
|
// 这个方法只会被调用一次
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveToCloud(checkpointId: string): Promise<void> {
|
||||||
|
// 模拟云端保存
|
||||||
|
return new Promise(resolve => setTimeout(resolve, 1500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 批处理事件
|
||||||
|
|
||||||
|
对于高频事件,可以使用批处理来提升性能:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
protected onInitialize(): void {
|
||||||
|
// 设置位置更新事件的批处理
|
||||||
|
this.scene?.eventSystem.setBatchConfig('position_updated', {
|
||||||
|
batchSize: 50, // 批处理大小
|
||||||
|
delay: 16, // 延迟时间(毫秒)
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听批处理事件
|
||||||
|
this.addEventListener('position_updated:batch', this.onPositionBatch.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPositionBatch(batchData: any): void {
|
||||||
|
console.log(`批处理位置更新,共 ${batchData.count} 个事件`);
|
||||||
|
|
||||||
|
// 批量处理所有位置更新
|
||||||
|
for (const event of batchData.events) {
|
||||||
|
this.updateMinimap(event.entityId, event.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(Position);
|
||||||
|
if (position && position.hasChanged) {
|
||||||
|
// 发射高频位置更新事件(会被批处理)
|
||||||
|
this.scene?.eventSystem.emitSync('position_updated', {
|
||||||
|
entityId: entity.id,
|
||||||
|
position: { x: position.x, y: position.y }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateMinimap(entityId: number, position: { x: number, y: number }): void {
|
||||||
|
// 更新小地图显示
|
||||||
|
}
|
||||||
|
|
||||||
|
public flushPositionUpdates(): void {
|
||||||
|
// 立即处理所有待处理的位置更新
|
||||||
|
this.scene?.eventSystem.flushBatch('position_updated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预定义的 ECS 事件
|
||||||
|
|
||||||
|
框架提供了一些预定义的 ECS 生命周期事件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ECSEventType } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class ECSMonitor {
|
||||||
|
private eventBus = GlobalEventBus.getInstance();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setupECSEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupECSEvents(): void {
|
||||||
|
// 监听实体生命周期事件
|
||||||
|
this.eventBus.onEntityCreated(this.onEntityCreated.bind(this));
|
||||||
|
this.eventBus.on(ECSEventType.ENTITY_DESTROYED, this.onEntityDestroyed.bind(this));
|
||||||
|
|
||||||
|
// 监听组件生命周期事件
|
||||||
|
this.eventBus.onComponentAdded(this.onComponentAdded.bind(this));
|
||||||
|
this.eventBus.on(ECSEventType.COMPONENT_REMOVED, this.onComponentRemoved.bind(this));
|
||||||
|
|
||||||
|
// 监听系统事件
|
||||||
|
this.eventBus.on(ECSEventType.SYSTEM_ADDED, this.onSystemAdded.bind(this));
|
||||||
|
this.eventBus.onSystemError(this.onSystemError.bind(this));
|
||||||
|
|
||||||
|
// 监听性能警告
|
||||||
|
this.eventBus.onPerformanceWarning(this.onPerformanceWarning.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEntityCreated(data: any): void {
|
||||||
|
console.log(`实体创建: ${data.entityName} (ID: ${data.entity.id})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEntityDestroyed(data: any): void {
|
||||||
|
console.log(`实体销毁: ${data.entity.name} (ID: ${data.entity.id})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onComponentAdded(data: any): void {
|
||||||
|
console.log(`组件添加: ${data.componentType} 到实体 ${data.entity.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onComponentRemoved(data: any): void {
|
||||||
|
console.log(`组件移除: ${data.componentType} 从实体 ${data.entity.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSystemAdded(data: any): void {
|
||||||
|
console.log(`系统添加: ${data.systemName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSystemError(data: any): void {
|
||||||
|
console.error(`系统错误: ${data.systemName}`, data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPerformanceWarning(data: any): void {
|
||||||
|
console.warn(`性能警告: ${data.systemName} 执行时间过长 (${data.executionTime}ms)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 事件命名规范
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的事件命名
|
||||||
|
this.eventSystem.emitSync('player:health_changed', data);
|
||||||
|
this.eventSystem.emitSync('enemy:spawned', data);
|
||||||
|
this.eventSystem.emitSync('ui:score_updated', data);
|
||||||
|
this.eventSystem.emitSync('game:paused', data);
|
||||||
|
|
||||||
|
// ❌ 避免的事件命名
|
||||||
|
this.eventSystem.emitSync('event1', data);
|
||||||
|
this.eventSystem.emitSync('update', data);
|
||||||
|
this.eventSystem.emitSync('change', data);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 类型安全的事件数据
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 定义事件数据接口
|
||||||
|
interface PlayerHealthChangedEvent {
|
||||||
|
entityId: number;
|
||||||
|
oldHealth: number;
|
||||||
|
newHealth: number;
|
||||||
|
cause: 'damage' | 'healing';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnemySpawnedEvent {
|
||||||
|
enemyType: string;
|
||||||
|
position: { x: number, y: number };
|
||||||
|
level: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用类型安全的事件
|
||||||
|
class HealthSystem extends EntitySystem {
|
||||||
|
private onHealthChanged(data: PlayerHealthChangedEvent): void {
|
||||||
|
// TypeScript 会提供完整的类型检查
|
||||||
|
console.log(`生命值变化: ${data.oldHealth} -> ${data.newHealth}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免事件循环
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ 避免:可能导致无限循环
|
||||||
|
class BadEventHandler {
|
||||||
|
private onScoreChanged(data: any): void {
|
||||||
|
// 在处理分数变化时又触发分数变化事件
|
||||||
|
this.scene?.eventSystem.emitSync('score_changed', newData); // 危险!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 正确:使用不同的事件类型或条件判断
|
||||||
|
class GoodEventHandler {
|
||||||
|
private isProcessingScore = false;
|
||||||
|
|
||||||
|
private onScoreChanged(data: any): void {
|
||||||
|
if (this.isProcessingScore) return; // 防止循环
|
||||||
|
|
||||||
|
this.isProcessingScore = true;
|
||||||
|
// 处理分数变化
|
||||||
|
this.updateUI(data);
|
||||||
|
this.isProcessingScore = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 及时清理事件监听器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TemporaryUI {
|
||||||
|
private listenerId: string;
|
||||||
|
|
||||||
|
constructor(scene: Scene) {
|
||||||
|
// 保存监听器ID用于后续清理
|
||||||
|
this.listenerId = scene.eventSystem.on('ui_update', this.onUpdate.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onUpdate(data: any): void {
|
||||||
|
// 处理UI更新
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
// 清理事件监听器
|
||||||
|
if (this.listenerId) {
|
||||||
|
scene.eventSystem.off('ui_update', this.listenerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 性能考虑
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class OptimizedEventHandler {
|
||||||
|
protected onInitialize(): void {
|
||||||
|
// 对于高频事件,使用批处理
|
||||||
|
this.scene?.eventSystem.setBatchConfig('movement_update', {
|
||||||
|
batchSize: 100,
|
||||||
|
delay: 16,
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 对于低频但重要的事件,使用高优先级
|
||||||
|
this.addEventListener('game_over', this.onGameOver.bind(this), {
|
||||||
|
priority: 1000
|
||||||
|
});
|
||||||
|
|
||||||
|
// 对于一次性事件,使用 once
|
||||||
|
this.addEventListener('level_start', this.onLevelStart.bind(this), {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
事件系统是 ECS 框架中实现松耦合架构的重要工具,正确使用事件系统能让你的游戏代码更加模块化、可维护和可扩展。
|
||||||
394
docs/guide/getting-started.md
Normal file
394
docs/guide/getting-started.md
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
# 快速开始
|
||||||
|
|
||||||
|
本指南将帮助你快速上手 ECS Framework,从安装到创建第一个 ECS 应用。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### NPM 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 npm
|
||||||
|
npm install @esengine/ecs-framework
|
||||||
|
```
|
||||||
|
|
||||||
|
## 初始化 Core
|
||||||
|
|
||||||
|
### 基础初始化
|
||||||
|
|
||||||
|
ECS Framework 的核心是 `Core` 类,它是一个单例模式,负责管理整个框架的生命周期。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Core } from '@esengine/ecs-framework'
|
||||||
|
|
||||||
|
// 方式1:使用配置对象(推荐)
|
||||||
|
const core = Core.create({
|
||||||
|
debug: true, // 启用调试模式,提供详细的日志和性能监控
|
||||||
|
enableEntitySystems: true, // 启用实体系统,这是ECS的核心功能
|
||||||
|
debugConfig: { // 可选:高级调试配置
|
||||||
|
enabled: false, // 是否启用WebSocket调试服务器
|
||||||
|
websocketUrl: 'ws://localhost:8080',
|
||||||
|
debugFrameRate: 30, // 调试数据发送帧率
|
||||||
|
channels: {
|
||||||
|
entities: true,
|
||||||
|
systems: true,
|
||||||
|
performance: true,
|
||||||
|
components: true,
|
||||||
|
scenes: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 方式2:简化创建(向后兼容)
|
||||||
|
const core = Core.create(true); // 等同于 { debug: true, enableEntitySystems: true }
|
||||||
|
|
||||||
|
// 方式3:生产环境配置
|
||||||
|
const core = Core.create({
|
||||||
|
debug: false, // 生产环境关闭调试
|
||||||
|
enableEntitySystems: true
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core 配置详解
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ICoreConfig {
|
||||||
|
/** 是否启用调试模式 - 影响日志级别和性能监控 */
|
||||||
|
debug?: boolean;
|
||||||
|
|
||||||
|
/** 是否启用实体系统 - 核心ECS功能开关 */
|
||||||
|
enableEntitySystems?: boolean;
|
||||||
|
|
||||||
|
/** 高级调试配置 - 用于开发工具集成 */
|
||||||
|
debugConfig?: {
|
||||||
|
enabled: boolean; // 是否启用调试服务器
|
||||||
|
websocketUrl: string; // WebSocket服务器地址
|
||||||
|
autoReconnect?: boolean; // 是否自动重连
|
||||||
|
debugFrameRate?: 60 | 30 | 15; // 调试数据发送帧率
|
||||||
|
channels: { // 数据通道配置
|
||||||
|
entities: boolean; // 实体数据
|
||||||
|
systems: boolean; // 系统数据
|
||||||
|
performance: boolean; // 性能数据
|
||||||
|
components: boolean; // 组件数据
|
||||||
|
scenes: boolean; // 场景数据
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core 实例管理
|
||||||
|
|
||||||
|
Core 采用单例模式,创建后可以通过静态属性获取:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 创建实例
|
||||||
|
const core = Core.create(true);
|
||||||
|
|
||||||
|
// 获取已创建的实例
|
||||||
|
const instance = Core.Instance; // 获取当前实例,如果未创建则为 null
|
||||||
|
```
|
||||||
|
|
||||||
|
### 游戏循环集成
|
||||||
|
|
||||||
|
**重要**: 在创建实体和系统之前,你需要先了解如何将 ECS Framework 集成到你的游戏引擎中。
|
||||||
|
|
||||||
|
`Core.update(deltaTime)` 是整个框架的心跳,必须在游戏引擎的每一帧中调用。它负责:
|
||||||
|
- 更新框架内置的 Time 类
|
||||||
|
- 更新所有全局管理器(定时器、对象池等)
|
||||||
|
- 更新所有场景中的实体系统
|
||||||
|
- 处理实体的创建和销毁
|
||||||
|
- 收集性能数据(调试模式下)
|
||||||
|
|
||||||
|
各引擎集成示例请参考:[与游戏引擎集成](#与游戏引擎集成)
|
||||||
|
|
||||||
|
## 创建第一个 ECS 应用
|
||||||
|
|
||||||
|
### 1. 定义组件
|
||||||
|
|
||||||
|
组件是纯数据容器,用于存储实体的状态:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component, ECSComponent } from '@esengine/ecs-framework'
|
||||||
|
|
||||||
|
// 位置组件
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
x: number = 0
|
||||||
|
y: number = 0
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super()
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 速度组件
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
class Velocity extends Component {
|
||||||
|
dx: number = 0
|
||||||
|
dy: number = 0
|
||||||
|
|
||||||
|
constructor(dx: number = 0, dy: number = 0) {
|
||||||
|
super()
|
||||||
|
this.dx = dx
|
||||||
|
this.dy = dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染组件
|
||||||
|
@ECSComponent('Sprite')
|
||||||
|
class Sprite extends Component {
|
||||||
|
texture: string = ''
|
||||||
|
width: number = 32
|
||||||
|
height: number = 32
|
||||||
|
|
||||||
|
constructor(texture: string, width: number = 32, height: number = 32) {
|
||||||
|
super()
|
||||||
|
this.texture = texture
|
||||||
|
this.width = width
|
||||||
|
this.height = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 创建实体系统
|
||||||
|
|
||||||
|
系统包含游戏逻辑,处理具有特定组件的实体。ECS Framework 提供了基于 Matcher 的实体过滤机制:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EntitySystem, Matcher, Time, ECSSystem } from '@esengine/ecs-framework'
|
||||||
|
|
||||||
|
// 移动系统 - 处理位置和速度
|
||||||
|
@ECSSystem('MovementSystem')
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 使用 Matcher 定义要处理的实体:必须同时拥有 Position 和 Velocity 组件
|
||||||
|
super(Matcher.empty().all(Position, Velocity))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// process 方法接收所有匹配的实体
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(Position)!
|
||||||
|
const velocity = entity.getComponent(Velocity)!
|
||||||
|
|
||||||
|
// 更新位置(使用框架的Time类)
|
||||||
|
position.x += velocity.dx * Time.deltaTime
|
||||||
|
position.y += velocity.dy * Time.deltaTime
|
||||||
|
|
||||||
|
// 边界检查示例
|
||||||
|
if (position.x < 0) position.x = 0
|
||||||
|
if (position.y < 0) position.y = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染系统 - 处理可见对象
|
||||||
|
@ECSSystem('RenderSystem')
|
||||||
|
class RenderSystem extends EntitySystem {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 必须有 Position 和 Sprite,可选 Velocity(用于方向判断)
|
||||||
|
super(Matcher.empty().all(Position, Sprite).any(Velocity))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(Position)!
|
||||||
|
const sprite = entity.getComponent(Sprite)!
|
||||||
|
const velocity = entity.getComponent(Velocity) // 可能为 null
|
||||||
|
|
||||||
|
// 根据速度方向翻转精灵(可选逻辑)
|
||||||
|
let flipX = false
|
||||||
|
if (velocity && velocity.dx < 0) {
|
||||||
|
flipX = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染逻辑(这里是伪代码)
|
||||||
|
this.drawSprite(sprite.texture, position.x, position.y, sprite.width, sprite.height, flipX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawSprite(texture: string, x: number, y: number, width: number, height: number, flipX: boolean = false) {
|
||||||
|
// 实际的渲染实现将取决于你使用的游戏引擎
|
||||||
|
const direction = flipX ? '←' : '→'
|
||||||
|
console.log(`渲染 ${texture} 在位置 (${x.toFixed(1)}, ${y.toFixed(1)}) 方向: ${direction}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 3. 创建场景
|
||||||
|
|
||||||
|
推荐继承 Scene 类来创建自定义场景:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Scene } from '@esengine/ecs-framework'
|
||||||
|
|
||||||
|
// 推荐:继承Scene创建自定义场景
|
||||||
|
class GameScene extends Scene {
|
||||||
|
|
||||||
|
initialize(): void {
|
||||||
|
// 场景初始化逻辑
|
||||||
|
this.name = "MainScene";
|
||||||
|
|
||||||
|
// 添加系统到场景
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
// 场景开始运行时的逻辑
|
||||||
|
console.log("游戏场景已启动");
|
||||||
|
}
|
||||||
|
|
||||||
|
unload(): void {
|
||||||
|
// 场景卸载时的清理逻辑
|
||||||
|
console.log("游戏场景已卸载");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建并设置场景
|
||||||
|
const gameScene = new GameScene();
|
||||||
|
Core.setScene(gameScene);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 创建实体
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 创建玩家实体
|
||||||
|
const player = gameScene.createEntity("Player");
|
||||||
|
player.addComponent(new Position(100, 100));
|
||||||
|
player.addComponent(new Velocity(50, 30)); // 每秒移动 50 像素(x方向),30 像素(y方向)
|
||||||
|
player.addComponent(new Sprite("player.png", 64, 64));
|
||||||
|
```
|
||||||
|
|
||||||
|
## World 概念
|
||||||
|
|
||||||
|
World 是 Scene 的容器,用于管理多个独立的游戏世界。这种设计特别适用于:
|
||||||
|
- 多人游戏房间(每个房间一个 World)
|
||||||
|
- 不同的游戏模式
|
||||||
|
- 独立的模拟环境
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { World, Scene } from '@esengine/ecs-framework'
|
||||||
|
|
||||||
|
// 创建游戏房间的World
|
||||||
|
const roomWorld = new World({ name: 'Room_001' });
|
||||||
|
|
||||||
|
// 在World中创建多个Scene
|
||||||
|
class GameScene extends Scene {
|
||||||
|
initialize(): void {
|
||||||
|
this.name = "GamePlay";
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UIScene extends Scene {
|
||||||
|
initialize(): void {
|
||||||
|
this.name = "UI";
|
||||||
|
// UI相关系统
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加Scene到World
|
||||||
|
const gameScene = roomWorld.createScene('game', new GameScene());
|
||||||
|
const uiScene = roomWorld.createScene('ui', new UIScene());
|
||||||
|
|
||||||
|
// 激活Scene
|
||||||
|
roomWorld.setSceneActive('game', true);
|
||||||
|
roomWorld.setSceneActive('ui', true);
|
||||||
|
|
||||||
|
// 启动World
|
||||||
|
roomWorld.start();
|
||||||
|
```
|
||||||
|
|
||||||
|
### World 生命周期
|
||||||
|
|
||||||
|
World 提供了完整的生命周期管理:
|
||||||
|
- `start()`: 启动 World 和所有全局系统
|
||||||
|
- `updateGlobalSystems()`: 更新全局系统(由 Core.update() 调用)
|
||||||
|
- `updateScenes()`: 更新所有激活的 Scene(由 Core.update() 调用)
|
||||||
|
- `stop()`: 停止 World
|
||||||
|
- `destroy()`: 销毁 World 和所有资源
|
||||||
|
|
||||||
|
## 与游戏引擎集成
|
||||||
|
|
||||||
|
### Laya 引擎集成
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Stage } from "laya/display/Stage"
|
||||||
|
import { Stat } from "laya/utils/Stat"
|
||||||
|
import { Laya } from "Laya"
|
||||||
|
|
||||||
|
// 初始化 Laya
|
||||||
|
Laya.init(800, 600).then(() => {
|
||||||
|
// 初始化 ECS
|
||||||
|
const core = Core.create(true)
|
||||||
|
|
||||||
|
// 设置场景...
|
||||||
|
|
||||||
|
// 启动游戏循环
|
||||||
|
Laya.timer.frameLoop(1, this, () => {
|
||||||
|
const deltaTime = Laya.timer.delta / 1000 // 转换为秒
|
||||||
|
Core.update(deltaTime)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cocos Creator 集成
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component, _decorator } from 'cc'
|
||||||
|
|
||||||
|
const { ccclass } = _decorator
|
||||||
|
|
||||||
|
@ccclass('ECSGameManager')
|
||||||
|
export class ECSGameManager extends Component {
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
// 初始化 ECS
|
||||||
|
const core = Core.create(true)
|
||||||
|
|
||||||
|
// 设置场景...
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime: number) {
|
||||||
|
// 更新 ECS
|
||||||
|
Core.update(deltaTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
现在你已经成功创建了第一个 ECS 应用!接下来可以:
|
||||||
|
|
||||||
|
- 查看完整的 [API 文档](/api/README)
|
||||||
|
- 探索更多[实际应用示例](/examples/)
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 为什么我的系统没有执行?
|
||||||
|
|
||||||
|
确保:
|
||||||
|
1. 系统已添加到场景:`this.addSystem(system)` (在 Scene 的 initialize 方法中)
|
||||||
|
2. 场景已设置为当前场景:`Core.setScene(scene)`
|
||||||
|
3. 游戏循环在调用:`Core.update(deltaTime)`
|
||||||
|
|
||||||
|
### 如何调试 ECS 应用?
|
||||||
|
|
||||||
|
启用调试模式:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
Core.create({ debug: true })
|
||||||
|
|
||||||
|
// 获取调试数据
|
||||||
|
const debugData = Core.getDebugData()
|
||||||
|
console.log(debugData)
|
||||||
|
```
|
||||||
26
docs/guide/index.md
Normal file
26
docs/guide/index.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 指南
|
||||||
|
|
||||||
|
欢迎使用 ECS Framework 指南。这里将详细介绍框架的各个核心概念和使用方法。
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### [实体类 (Entity)](./entity.md)
|
||||||
|
了解 ECS 架构的基础 - 实体类的使用方法、生命周期管理和最佳实践。
|
||||||
|
|
||||||
|
### [组件系统 (Component)](./component.md)
|
||||||
|
学习如何创建和使用组件,实现游戏功能的模块化设计。
|
||||||
|
|
||||||
|
### [系统架构 (System)](./system.md)
|
||||||
|
掌握系统的编写方法,实现游戏逻辑的处理。
|
||||||
|
|
||||||
|
### [场景管理 (Scene)](./scene.md)
|
||||||
|
了解场景的生命周期、系统管理和实体容器功能。
|
||||||
|
|
||||||
|
### [事件系统 (Event)](./event-system.md)
|
||||||
|
掌握类型安全的事件系统,实现组件间通信和系统协作。
|
||||||
|
|
||||||
|
### [时间和定时器 (Time)](./time-and-timers.md)
|
||||||
|
学习时间管理和定时器系统,实现游戏逻辑的精确时间控制。
|
||||||
|
|
||||||
|
### [日志系统 (Logger)](./logging.md)
|
||||||
|
掌握分级日志系统,用于调试、监控和错误追踪。
|
||||||
550
docs/guide/logging.md
Normal file
550
docs/guide/logging.md
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
# 日志系统
|
||||||
|
|
||||||
|
ECS 框架提供了功能强大的分级日志系统,支持多种日志级别、颜色输出、自定义前缀和灵活的配置选项。日志系统可以帮助开发者调试代码和监控应用运行状态。
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
日志系统包含以下核心概念:
|
||||||
|
- **日志级别**:Debug < Info < Warn < Error < Fatal < None
|
||||||
|
- **日志器**:具名的日志输出器,每个模块可以有自己的日志器
|
||||||
|
- **日志管理器**:全局管理所有日志器的单例
|
||||||
|
- **颜色配置**:支持控制台颜色输出
|
||||||
|
|
||||||
|
## 日志级别
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LogLevel } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 日志级别从低到高
|
||||||
|
LogLevel.Debug // 0 - 调试信息
|
||||||
|
LogLevel.Info // 1 - 一般信息
|
||||||
|
LogLevel.Warn // 2 - 警告信息
|
||||||
|
LogLevel.Error // 3 - 错误信息
|
||||||
|
LogLevel.Fatal // 4 - 致命错误
|
||||||
|
LogLevel.None // 5 - 不输出任何日志
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本使用
|
||||||
|
|
||||||
|
### 使用默认日志器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Logger } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameSystem extends EntitySystem {
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 输出不同级别的日志
|
||||||
|
Logger.debug('处理实体数量:', entities.length);
|
||||||
|
Logger.info('系统正常运行');
|
||||||
|
Logger.warn('检测到性能问题');
|
||||||
|
Logger.error('处理过程中发生错误', new Error('示例错误'));
|
||||||
|
Logger.fatal('致命错误,系统即将停止');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建命名日志器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
private logger = createLogger('MovementSystem');
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
this.logger.info(`处理 ${entities.length} 个移动实体`);
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(Position);
|
||||||
|
const velocity = entity.getComponent(Velocity);
|
||||||
|
|
||||||
|
if (position && velocity) {
|
||||||
|
position.x += velocity.dx * Time.deltaTime;
|
||||||
|
position.y += velocity.dy * Time.deltaTime;
|
||||||
|
|
||||||
|
this.logger.debug(`实体 ${entity.id} 移动到位置 (${position.x}, ${position.y})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAdded(entity: Entity): void {
|
||||||
|
this.logger.info(`实体 ${entity.name} 加入移动系统`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onRemoved(entity: Entity): void {
|
||||||
|
this.logger.warn(`实体 ${entity.name} 离开移动系统`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 系统内置日志器
|
||||||
|
|
||||||
|
框架的各个系统都有自己的日志器:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 框架内部使用示例
|
||||||
|
class Scene {
|
||||||
|
private static readonly _logger = createLogger('Scene');
|
||||||
|
|
||||||
|
public addSystem(system: EntitySystem): void {
|
||||||
|
Scene._logger.info(`添加系统: ${system.systemName}`);
|
||||||
|
// 系统添加逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeSystem(system: EntitySystem): void {
|
||||||
|
Scene._logger.warn(`移除系统: ${system.systemName}`);
|
||||||
|
// 系统移除逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 日志配置
|
||||||
|
|
||||||
|
### 设置全局日志级别
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { setGlobalLogLevel, LogLevel } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 在开发环境显示所有日志
|
||||||
|
setGlobalLogLevel(LogLevel.Debug);
|
||||||
|
|
||||||
|
// 在生产环境只显示警告及以上级别
|
||||||
|
setGlobalLogLevel(LogLevel.Warn);
|
||||||
|
|
||||||
|
// 完全禁用日志输出
|
||||||
|
setGlobalLogLevel(LogLevel.None);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建自定义配置的日志器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ConsoleLogger, LogLevel } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class CustomLoggerExample {
|
||||||
|
private debugLogger: ConsoleLogger;
|
||||||
|
private productionLogger: ConsoleLogger;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 开发环境日志器
|
||||||
|
this.debugLogger = new ConsoleLogger({
|
||||||
|
level: LogLevel.Debug,
|
||||||
|
enableTimestamp: true,
|
||||||
|
enableColors: true,
|
||||||
|
prefix: 'DEV'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生产环境日志器
|
||||||
|
this.productionLogger = new ConsoleLogger({
|
||||||
|
level: LogLevel.Error,
|
||||||
|
enableTimestamp: true,
|
||||||
|
enableColors: false,
|
||||||
|
prefix: 'PROD'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public logDevelopmentInfo(): void {
|
||||||
|
this.debugLogger.debug('这是调试信息');
|
||||||
|
this.debugLogger.info('开发环境信息');
|
||||||
|
}
|
||||||
|
|
||||||
|
public logProductionError(): void {
|
||||||
|
this.productionLogger.error('生产环境错误');
|
||||||
|
this.productionLogger.fatal('致命错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 颜色配置
|
||||||
|
|
||||||
|
### 使用预定义颜色
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Colors, setLoggerColors } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 自定义颜色方案
|
||||||
|
setLoggerColors({
|
||||||
|
debug: Colors.BRIGHT_BLACK,
|
||||||
|
info: Colors.BLUE,
|
||||||
|
warn: Colors.YELLOW,
|
||||||
|
error: Colors.RED,
|
||||||
|
fatal: Colors.BRIGHT_RED
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整颜色示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LoggerManager, Colors, LogLevel } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class ColorLoggerDemo {
|
||||||
|
private logger = createLogger('ColorDemo');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 设置自定义颜色
|
||||||
|
const manager = LoggerManager.getInstance();
|
||||||
|
manager.setGlobalColors({
|
||||||
|
debug: Colors.CYAN,
|
||||||
|
info: Colors.GREEN,
|
||||||
|
warn: Colors.YELLOW,
|
||||||
|
error: Colors.RED,
|
||||||
|
fatal: `${Colors.BOLD}${Colors.BRIGHT_RED}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public demonstrateColors(): void {
|
||||||
|
this.logger.debug('这是蓝绿色的调试信息');
|
||||||
|
this.logger.info('这是绿色的信息');
|
||||||
|
this.logger.warn('这是黄色的警告');
|
||||||
|
this.logger.error('这是红色的错误');
|
||||||
|
this.logger.fatal('这是加粗的亮红色致命错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetToDefaults(): void {
|
||||||
|
// 重置为默认颜色
|
||||||
|
LoggerManager.getInstance().resetColors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级功能
|
||||||
|
|
||||||
|
### 分层日志器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LoggerManager } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class HierarchicalLoggingExample {
|
||||||
|
private systemLogger = createLogger('GameSystems');
|
||||||
|
private movementLogger: ILogger;
|
||||||
|
private renderLogger: ILogger;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const manager = LoggerManager.getInstance();
|
||||||
|
|
||||||
|
// 创建子日志器
|
||||||
|
this.movementLogger = manager.createChildLogger('GameSystems', 'Movement');
|
||||||
|
this.renderLogger = manager.createChildLogger('GameSystems', 'Render');
|
||||||
|
}
|
||||||
|
|
||||||
|
public demonstrateHierarchy(): void {
|
||||||
|
this.systemLogger.info('游戏系统启动');
|
||||||
|
|
||||||
|
// 子日志器会显示完整路径:[GameSystems.Movement]
|
||||||
|
this.movementLogger.debug('移动系统初始化');
|
||||||
|
|
||||||
|
// 子日志器会显示完整路径:[GameSystems.Render]
|
||||||
|
this.renderLogger.info('渲染系统启动');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义输出
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ConsoleLogger, LogLevel } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class CustomOutputLogger {
|
||||||
|
private fileLogger: ConsoleLogger;
|
||||||
|
private networkLogger: ConsoleLogger;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 输出到文件的日志器(模拟)
|
||||||
|
this.fileLogger = new ConsoleLogger({
|
||||||
|
level: LogLevel.Info,
|
||||||
|
output: (level: LogLevel, message: string) => {
|
||||||
|
this.writeToFile(LogLevel[level], message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送到网络的日志器(模拟)
|
||||||
|
this.networkLogger = new ConsoleLogger({
|
||||||
|
level: LogLevel.Error,
|
||||||
|
output: (level: LogLevel, message: string) => {
|
||||||
|
this.sendToServer(LogLevel[level], message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private writeToFile(level: string, message: string): void {
|
||||||
|
// 模拟文件写入
|
||||||
|
console.log(`[FILE] ${level}: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendToServer(level: string, message: string): void {
|
||||||
|
// 模拟网络发送
|
||||||
|
console.log(`[NETWORK] ${level}: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logToFile(message: string): void {
|
||||||
|
this.fileLogger.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logCriticalError(error: Error): void {
|
||||||
|
this.networkLogger.error('Critical error occurred', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实际应用示例
|
||||||
|
|
||||||
|
### 游戏系统日志
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameWithLogging {
|
||||||
|
private gameLogger = createLogger('Game');
|
||||||
|
private performanceLogger = createLogger('Performance');
|
||||||
|
private networkLogger = createLogger('Network');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 在开发环境启用详细日志
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
setGlobalLogLevel(LogLevel.Debug);
|
||||||
|
} else {
|
||||||
|
setGlobalLogLevel(LogLevel.Warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public startGame(): void {
|
||||||
|
this.gameLogger.info('游戏开始启动');
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.initializeSystems();
|
||||||
|
this.loadResources();
|
||||||
|
this.startGameLoop();
|
||||||
|
|
||||||
|
this.gameLogger.info('游戏启动成功');
|
||||||
|
} catch (error) {
|
||||||
|
this.gameLogger.fatal('游戏启动失败', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeSystems(): void {
|
||||||
|
this.gameLogger.debug('初始化游戏系统');
|
||||||
|
|
||||||
|
const systems = [
|
||||||
|
new MovementSystem(),
|
||||||
|
new RenderSystem(),
|
||||||
|
new PhysicsSystem()
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const system of systems) {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
// 初始化系统
|
||||||
|
system.initialize();
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
this.performanceLogger.debug(
|
||||||
|
`系统 ${system.systemName} 初始化耗时: ${(endTime - startTime).toFixed(2)}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadResources(): void {
|
||||||
|
this.gameLogger.info('开始加载资源');
|
||||||
|
|
||||||
|
const resources = ['textures', 'sounds', 'data'];
|
||||||
|
for (const resource of resources) {
|
||||||
|
try {
|
||||||
|
this.loadResource(resource);
|
||||||
|
this.gameLogger.debug(`资源 ${resource} 加载成功`);
|
||||||
|
} catch (error) {
|
||||||
|
this.gameLogger.error(`资源 ${resource} 加载失败`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startGameLoop(): void {
|
||||||
|
this.gameLogger.info('启动游戏循环');
|
||||||
|
this.performanceLogger.debug('开始性能监控');
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadResource(name: string): void {
|
||||||
|
// 模拟资源加载
|
||||||
|
if (Math.random() < 0.1) {
|
||||||
|
throw new Error(`Failed to load ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleNetworkEvent(event: string, data: any): void {
|
||||||
|
this.networkLogger.info(`网络事件: ${event}`, data);
|
||||||
|
|
||||||
|
if (event === 'connection_lost') {
|
||||||
|
this.networkLogger.warn('网络连接丢失,尝试重连');
|
||||||
|
} else if (event === 'sync_error') {
|
||||||
|
this.networkLogger.error('数据同步错误', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误追踪和调试
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ErrorTrackingSystem extends EntitySystem {
|
||||||
|
private logger = createLogger('ErrorTracker');
|
||||||
|
private errorCounts = new Map<string, number>();
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
try {
|
||||||
|
this.processEntity(entity);
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(entity, error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processEntity(entity: Entity): void {
|
||||||
|
// 模拟可能出错的实体处理
|
||||||
|
if (Math.random() < 0.01) { // 1% 概率出错
|
||||||
|
throw new Error(`Processing error for entity ${entity.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`成功处理实体 ${entity.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(entity: Entity, error: Error): void {
|
||||||
|
const errorKey = error.message;
|
||||||
|
const count = this.errorCounts.get(errorKey) || 0;
|
||||||
|
this.errorCounts.set(errorKey, count + 1);
|
||||||
|
|
||||||
|
this.logger.error(
|
||||||
|
`实体 ${entity.id} 处理失败 (第${count + 1}次): ${error.message}`,
|
||||||
|
{
|
||||||
|
entityId: entity.id,
|
||||||
|
entityName: entity.name,
|
||||||
|
componentCount: entity.components.length,
|
||||||
|
errorStack: error.stack
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果同一类型错误发生太多次,升级为警告
|
||||||
|
if (count >= 5) {
|
||||||
|
this.logger.warn(`错误 "${errorKey}" 已发生 ${count + 1} 次,可能需要关注`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果错误次数过多,升级为致命错误
|
||||||
|
if (count >= 20) {
|
||||||
|
this.logger.fatal(`错误 "${errorKey}" 发生次数过多,系统可能存在严重问题`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getErrorSummary(): void {
|
||||||
|
this.logger.info('=== 错误统计 ===');
|
||||||
|
for (const [error, count] of this.errorCounts) {
|
||||||
|
this.logger.info(`${error}: ${count} 次`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 合理的日志级别选择
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class LoggingBestPractices {
|
||||||
|
private logger = createLogger('BestPractices');
|
||||||
|
|
||||||
|
public demonstrateLogLevels(): void {
|
||||||
|
// ✅ Debug - 详细的调试信息
|
||||||
|
this.logger.debug('变量值', { x: 10, y: 20 });
|
||||||
|
|
||||||
|
// ✅ Info - 重要的状态变化
|
||||||
|
this.logger.info('系统启动完成');
|
||||||
|
|
||||||
|
// ✅ Warn - 异常但不致命的情况
|
||||||
|
this.logger.warn('资源未找到,使用默认值');
|
||||||
|
|
||||||
|
// ✅ Error - 错误但程序可以继续
|
||||||
|
this.logger.error('保存失败,将重试', new Error('Network timeout'));
|
||||||
|
|
||||||
|
// ✅ Fatal - 致命错误,程序无法继续
|
||||||
|
this.logger.fatal('内存不足,程序即将退出');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 结构化日志数据
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class StructuredLogging {
|
||||||
|
private logger = createLogger('Structured');
|
||||||
|
|
||||||
|
public logWithStructuredData(): void {
|
||||||
|
// ✅ 提供结构化的上下文信息
|
||||||
|
this.logger.info('用户操作', {
|
||||||
|
userId: 12345,
|
||||||
|
action: 'move',
|
||||||
|
position: { x: 100, y: 200 },
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ 包含相关的错误上下文
|
||||||
|
this.logger.error('数据库查询失败', {
|
||||||
|
query: 'SELECT * FROM users',
|
||||||
|
parameters: { id: 123 },
|
||||||
|
connectionId: 'conn_456',
|
||||||
|
retryCount: 3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免日志性能问题
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class PerformanceConsciousLogging {
|
||||||
|
private logger = createLogger('Performance');
|
||||||
|
|
||||||
|
public efficientLogging(): void {
|
||||||
|
// ✅ 检查日志级别避免不必要的计算
|
||||||
|
if (this.logger.debug) {
|
||||||
|
const expensiveData = this.calculateExpensiveDebugInfo();
|
||||||
|
this.logger.debug('详细调试信息', expensiveData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 避免:总是计算昂贵的日志数据
|
||||||
|
// this.logger.debug('调试信息', this.calculateExpensiveDebugInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateExpensiveDebugInfo(): any {
|
||||||
|
// 模拟昂贵的计算
|
||||||
|
return { /* 复杂的调试数据 */ };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 日志配置管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class LoggingConfiguration {
|
||||||
|
public static setupLogging(): void {
|
||||||
|
// 根据环境配置日志级别
|
||||||
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
|
if (isDevelopment) {
|
||||||
|
setGlobalLogLevel(LogLevel.Debug);
|
||||||
|
setLoggerColors({
|
||||||
|
debug: Colors.CYAN,
|
||||||
|
info: Colors.GREEN,
|
||||||
|
warn: Colors.YELLOW,
|
||||||
|
error: Colors.RED,
|
||||||
|
fatal: Colors.BRIGHT_RED
|
||||||
|
});
|
||||||
|
} else if (isProduction) {
|
||||||
|
setGlobalLogLevel(LogLevel.Warn);
|
||||||
|
// 生产环境禁用颜色
|
||||||
|
LoggerManager.getInstance().resetColors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在应用启动时配置日志
|
||||||
|
LoggingConfiguration.setupLogging();
|
||||||
|
```
|
||||||
|
|
||||||
|
日志系统是调试和监控应用的重要工具,正确使用日志系统能大大提高开发效率和问题排查能力。
|
||||||
510
docs/guide/scene.md
Normal file
510
docs/guide/scene.md
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
# 场景管理
|
||||||
|
|
||||||
|
在 ECS 架构中,场景(Scene)是游戏世界的容器,负责管理实体、系统和组件的生命周期。场景提供了完整的 ECS 运行环境。
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
场景是 ECS 框架的核心容器,提供:
|
||||||
|
- 实体的创建、管理和销毁
|
||||||
|
- 系统的注册和执行调度
|
||||||
|
- 组件的存储和查询
|
||||||
|
- 事件系统支持
|
||||||
|
- 性能监控和调试信息
|
||||||
|
|
||||||
|
## 创建场景
|
||||||
|
|
||||||
|
### 继承 Scene 类
|
||||||
|
|
||||||
|
**推荐做法:继承 Scene 类来创建自定义场景**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Scene, EntitySystem } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 设置场景名称
|
||||||
|
this.name = "GameScene";
|
||||||
|
|
||||||
|
// 添加系统
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
this.addSystem(new PhysicsSystem());
|
||||||
|
|
||||||
|
// 创建初始实体
|
||||||
|
this.createInitialEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInitialEntities(): void {
|
||||||
|
// 创建玩家
|
||||||
|
const player = this.createEntity("Player");
|
||||||
|
player.addComponent(new Position(400, 300));
|
||||||
|
player.addComponent(new Health(100));
|
||||||
|
player.addComponent(new PlayerController());
|
||||||
|
|
||||||
|
// 创建敌人
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const enemy = this.createEntity(`Enemy_${i}`);
|
||||||
|
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
|
||||||
|
enemy.addComponent(new Health(50));
|
||||||
|
enemy.addComponent(new EnemyAI());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onStart(): void {
|
||||||
|
console.log("游戏场景已启动");
|
||||||
|
// 场景启动时的逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
public unload(): void {
|
||||||
|
console.log("游戏场景已卸载");
|
||||||
|
// 场景卸载时的清理逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用场景配置
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ISceneConfig } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
const config: ISceneConfig = {
|
||||||
|
name: "MainGame",
|
||||||
|
enableEntityDirectUpdate: false
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfiguredScene extends Scene {
|
||||||
|
constructor() {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景生命周期
|
||||||
|
|
||||||
|
场景提供了完整的生命周期管理:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ExampleScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 场景初始化:设置系统和初始实体
|
||||||
|
console.log("场景初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
public onStart(): void {
|
||||||
|
// 场景开始运行:游戏逻辑开始执行
|
||||||
|
console.log("场景开始运行");
|
||||||
|
}
|
||||||
|
|
||||||
|
public unload(): void {
|
||||||
|
// 场景卸载:清理资源
|
||||||
|
console.log("场景卸载");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用场景(由框架自动管理生命周期)
|
||||||
|
const scene = new ExampleScene();
|
||||||
|
// 场景的 initialize(), begin(), update(), end() 由框架自动调用
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实体管理
|
||||||
|
|
||||||
|
### 创建实体
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class EntityScene extends Scene {
|
||||||
|
createGameEntities(): void {
|
||||||
|
// 创建单个实体
|
||||||
|
const player = this.createEntity("Player");
|
||||||
|
|
||||||
|
// 批量创建实体(高性能)
|
||||||
|
const bullets = this.createEntities(100, "Bullet");
|
||||||
|
|
||||||
|
// 为批量创建的实体添加组件
|
||||||
|
bullets.forEach((bullet, index) => {
|
||||||
|
bullet.addComponent(new Position(index * 10, 100));
|
||||||
|
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查找实体
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SearchScene extends Scene {
|
||||||
|
findEntities(): void {
|
||||||
|
// 按名称查找
|
||||||
|
const player = this.findEntity("Player");
|
||||||
|
const player2 = this.getEntityByName("Player"); // 别名方法
|
||||||
|
|
||||||
|
// 按 ID 查找
|
||||||
|
const entity = this.findEntityById(123);
|
||||||
|
|
||||||
|
// 按标签查找
|
||||||
|
const enemies = this.findEntitiesByTag(2);
|
||||||
|
const enemies2 = this.getEntitiesByTag(2); // 别名方法
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
console.log(`找到玩家: ${player.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`找到 ${enemies.length} 个敌人`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 销毁实体
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class DestroyScene extends Scene {
|
||||||
|
cleanupEntities(): void {
|
||||||
|
// 销毁所有实体
|
||||||
|
this.destroyAllEntities();
|
||||||
|
|
||||||
|
// 单个实体的销毁通过实体本身
|
||||||
|
const enemy = this.findEntity("Enemy_1");
|
||||||
|
if (enemy) {
|
||||||
|
enemy.destroy(); // 实体会自动从场景中移除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统管理
|
||||||
|
|
||||||
|
### 添加和移除系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SystemScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 添加系统
|
||||||
|
const movementSystem = new MovementSystem();
|
||||||
|
this.addSystem(movementSystem);
|
||||||
|
|
||||||
|
// 设置系统更新顺序
|
||||||
|
movementSystem.updateOrder = 1;
|
||||||
|
|
||||||
|
// 添加更多系统
|
||||||
|
this.addSystem(new PhysicsSystem());
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeUnnecessarySystems(): void {
|
||||||
|
// 获取系统
|
||||||
|
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
|
||||||
|
|
||||||
|
// 移除系统
|
||||||
|
if (physicsSystem) {
|
||||||
|
this.removeSystem(physicsSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 系统访问
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SystemAccessScene extends Scene {
|
||||||
|
public pausePhysics(): void {
|
||||||
|
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
|
||||||
|
if (physicsSystem) {
|
||||||
|
physicsSystem.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllSystems(): EntitySystem[] {
|
||||||
|
return this.systems; // 获取所有系统
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 事件系统
|
||||||
|
|
||||||
|
场景内置了类型安全的事件系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class EventScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 监听事件
|
||||||
|
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
|
||||||
|
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
|
||||||
|
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlayerDied(data: any): void {
|
||||||
|
console.log('玩家死亡事件');
|
||||||
|
// 处理玩家死亡
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEnemySpawned(data: any): void {
|
||||||
|
console.log('敌人生成事件');
|
||||||
|
// 处理敌人生成
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLevelComplete(data: any): void {
|
||||||
|
console.log('关卡完成事件');
|
||||||
|
// 处理关卡完成
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerGameEvent(): void {
|
||||||
|
// 发送事件
|
||||||
|
this.eventSystem.emitSync('custom_event', {
|
||||||
|
message: "这是自定义事件",
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景统计和调试
|
||||||
|
|
||||||
|
### 获取场景统计
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class StatsScene extends Scene {
|
||||||
|
public showStats(): void {
|
||||||
|
const stats = this.getStats();
|
||||||
|
console.log(`实体数量: ${stats.entityCount}`);
|
||||||
|
console.log(`系统数量: ${stats.processorCount}`);
|
||||||
|
console.log('组件存储统计:', stats.componentStorageStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showDebugInfo(): void {
|
||||||
|
const debugInfo = this.getDebugInfo();
|
||||||
|
console.log('场景调试信息:', debugInfo);
|
||||||
|
|
||||||
|
// 显示所有实体信息
|
||||||
|
debugInfo.entities.forEach(entity => {
|
||||||
|
console.log(`实体 ${entity.name}(${entity.id}): ${entity.componentCount} 个组件`);
|
||||||
|
console.log('组件类型:', entity.componentTypes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示所有系统信息
|
||||||
|
debugInfo.processors.forEach(processor => {
|
||||||
|
console.log(`系统 ${processor.name}: 处理 ${processor.entityCount} 个实体`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景集成到框架
|
||||||
|
|
||||||
|
场景可以通过两种方式运行:
|
||||||
|
|
||||||
|
### 1. 简单的单场景应用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 创建游戏场景
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
this.name = "GameScene";
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动游戏
|
||||||
|
Core.create();
|
||||||
|
const gameScene = new GameScene();
|
||||||
|
Core.setScene(gameScene);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 复杂的多场景应用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { WorldManager } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 获取WorldManager实例
|
||||||
|
const worldManager = WorldManager.getInstance();
|
||||||
|
|
||||||
|
// 创建World
|
||||||
|
const gameWorld = worldManager.createWorld('game', {
|
||||||
|
name: 'MainGame',
|
||||||
|
maxScenes: 5
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在World中创建场景
|
||||||
|
const menuScene = gameWorld.createScene('menu', new MenuScene());
|
||||||
|
const gameScene = gameWorld.createScene('game', new GameScene());
|
||||||
|
|
||||||
|
// 激活场景
|
||||||
|
gameWorld.setSceneActive('menu', true);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多场景管理
|
||||||
|
|
||||||
|
在World中可以管理多个场景,通过激活/停用来切换:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameWorld extends World {
|
||||||
|
private menuScene: Scene;
|
||||||
|
private gameScene: Scene;
|
||||||
|
private gameOverScene: Scene;
|
||||||
|
|
||||||
|
public initialize(): void {
|
||||||
|
// 创建多个场景
|
||||||
|
this.menuScene = this.createScene('menu', new MenuScene());
|
||||||
|
this.gameScene = this.createScene('game', new GameScene());
|
||||||
|
this.gameOverScene = this.createScene('gameover', new GameOverScene());
|
||||||
|
|
||||||
|
// 设置初始场景
|
||||||
|
this.showMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public showMenu(): void {
|
||||||
|
this.deactivateAllScenes();
|
||||||
|
this.setSceneActive('menu', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public startGame(): void {
|
||||||
|
this.deactivateAllScenes();
|
||||||
|
this.setSceneActive('game', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showGameOver(): void {
|
||||||
|
this.deactivateAllScenes();
|
||||||
|
this.setSceneActive('gameover', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private deactivateAllScenes(): void {
|
||||||
|
this.setSceneActive('menu', false);
|
||||||
|
this.setSceneActive('game', false);
|
||||||
|
this.setSceneActive('gameover', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与 World 的关系
|
||||||
|
|
||||||
|
Scene 的运行架构层次:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Core -> WorldManager -> World -> Scene -> EntitySystem -> Entity -> Component
|
||||||
|
|
||||||
|
// 1. 简单应用:Core直接管理单个Scene
|
||||||
|
Core.setScene(new GameScene());
|
||||||
|
|
||||||
|
// 2. 复杂应用:WorldManager管理多个World,每个World管理多个Scene
|
||||||
|
const worldManager = WorldManager.getInstance();
|
||||||
|
const world = worldManager.createWorld('gameWorld');
|
||||||
|
const scene = world.createScene('mainScene', new GameScene());
|
||||||
|
world.setSceneActive('mainScene', true);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 场景职责分离
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的场景设计 - 职责清晰
|
||||||
|
class MenuScene extends Scene {
|
||||||
|
// 只处理菜单相关逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
|
// 只处理游戏玩法逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
class InventoryScene extends Scene {
|
||||||
|
// 只处理物品栏逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 避免的场景设计 - 职责混乱
|
||||||
|
class MegaScene extends Scene {
|
||||||
|
// 包含菜单、游戏、物品栏等所有逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 合理的系统组织
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class OrganizedScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 按功能和依赖关系添加系统
|
||||||
|
this.addInputSystems();
|
||||||
|
this.addLogicSystems();
|
||||||
|
this.addRenderSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private addInputSystems(): void {
|
||||||
|
this.addSystem(new InputSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private addLogicSystems(): void {
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
this.addSystem(new PhysicsSystem());
|
||||||
|
this.addSystem(new CollisionSystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private addRenderSystems(): void {
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
this.addSystem(new UISystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 资源管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ResourceScene extends Scene {
|
||||||
|
private textures: Map<string, any> = new Map();
|
||||||
|
private sounds: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
protected initialize(): void {
|
||||||
|
this.loadResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadResources(): void {
|
||||||
|
// 加载场景所需资源
|
||||||
|
}
|
||||||
|
|
||||||
|
public unload(): void {
|
||||||
|
// 清理资源
|
||||||
|
this.textures.clear();
|
||||||
|
this.sounds.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 事件处理规范
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class EventHandlingScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 集中管理事件监听
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupEventListeners(): void {
|
||||||
|
this.eventSystem.on('game_pause', this.onGamePause.bind(this));
|
||||||
|
this.eventSystem.on('game_resume', this.onGameResume.bind(this));
|
||||||
|
this.eventSystem.on('player_input', this.onPlayerInput.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onGamePause(): void {
|
||||||
|
// 暂停游戏逻辑
|
||||||
|
this.systems.forEach(system => {
|
||||||
|
if (system instanceof GameLogicSystem) {
|
||||||
|
system.enabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onGameResume(): void {
|
||||||
|
// 恢复游戏逻辑
|
||||||
|
this.systems.forEach(system => {
|
||||||
|
if (system instanceof GameLogicSystem) {
|
||||||
|
system.enabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlayerInput(data: any): void {
|
||||||
|
// 处理玩家输入
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。
|
||||||
596
docs/guide/system.md
Normal file
596
docs/guide/system.md
Normal file
@@ -0,0 +1,596 @@
|
|||||||
|
# 系统架构
|
||||||
|
|
||||||
|
在 ECS 架构中,系统(System)是处理业务逻辑的地方。系统负责对拥有特定组件组合的实体执行操作,是 ECS 架构的逻辑处理单元。
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
系统是继承自 `EntitySystem` 抽象基类的具体类,用于:
|
||||||
|
- 定义实体的处理逻辑(如移动、碰撞检测、渲染等)
|
||||||
|
- 根据组件组合筛选需要处理的实体
|
||||||
|
- 提供生命周期管理和性能监控
|
||||||
|
- 管理实体的添加、移除事件
|
||||||
|
|
||||||
|
## 系统类型
|
||||||
|
|
||||||
|
框架提供了几种不同类型的系统基类:
|
||||||
|
|
||||||
|
### EntitySystem - 基础系统
|
||||||
|
|
||||||
|
最基础的系统类,所有其他系统都继承自它:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EntitySystem, ECSSystem, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@ECSSystem('Movement')
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
// 使用 Matcher 定义需要处理的实体条件
|
||||||
|
super(Matcher.all(Position, Velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(Position);
|
||||||
|
const velocity = entity.getComponent(Velocity);
|
||||||
|
|
||||||
|
if (position && velocity) {
|
||||||
|
position.x += velocity.dx * Time.deltaTime;
|
||||||
|
position.y += velocity.dy * Time.deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ProcessingSystem - 处理系统
|
||||||
|
|
||||||
|
适用于不需要逐个处理实体的系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Physics')
|
||||||
|
class PhysicsSystem extends ProcessingSystem {
|
||||||
|
constructor() {
|
||||||
|
super(); // 不需要指定 Matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
public processSystem(): void {
|
||||||
|
// 执行物理世界步进
|
||||||
|
this.physicsWorld.step(Time.deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PassiveSystem - 被动系统
|
||||||
|
|
||||||
|
被动系统不进行主动处理,主要用于监听实体的添加和移除事件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('EntityTracker')
|
||||||
|
class EntityTrackerSystem extends PassiveSystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Health));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAdded(entity: Entity): void {
|
||||||
|
console.log(`生命值实体被添加: ${entity.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onRemoved(entity: Entity): void {
|
||||||
|
console.log(`生命值实体被移除: ${entity.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IntervalSystem - 间隔系统
|
||||||
|
|
||||||
|
按固定时间间隔执行的系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('AutoSave')
|
||||||
|
class AutoSaveSystem extends IntervalSystem {
|
||||||
|
constructor() {
|
||||||
|
// 每 5 秒执行一次
|
||||||
|
super(5.0, Matcher.all(SaveData));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
console.log('执行自动保存...');
|
||||||
|
// 保存游戏数据
|
||||||
|
this.saveGameData(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveGameData(entities: readonly Entity[]): void {
|
||||||
|
// 保存逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实体匹配器 (Matcher)
|
||||||
|
|
||||||
|
Matcher 用于定义系统需要处理哪些实体。它提供了灵活的条件组合:
|
||||||
|
|
||||||
|
### 基本匹配条件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 必须同时拥有 Position 和 Velocity 组件
|
||||||
|
const matcher1 = Matcher.all(Position, Velocity);
|
||||||
|
|
||||||
|
// 至少拥有 Health 或 Shield 组件之一
|
||||||
|
const matcher2 = Matcher.any(Health, Shield);
|
||||||
|
|
||||||
|
// 不能拥有 Dead 组件
|
||||||
|
const matcher3 = Matcher.none(Dead);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 复合匹配条件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 复杂的组合条件
|
||||||
|
const complexMatcher = Matcher.all(Position, Velocity)
|
||||||
|
.any(Player, Enemy)
|
||||||
|
.none(Dead, Disabled);
|
||||||
|
|
||||||
|
@ECSSystem('Combat')
|
||||||
|
class CombatSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(complexMatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 特殊匹配条件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 按标签匹配
|
||||||
|
const tagMatcher = Matcher.byTag(1); // 匹配标签为 1 的实体
|
||||||
|
|
||||||
|
// 按名称匹配
|
||||||
|
const nameMatcher = Matcher.byName("Player"); // 匹配名称为 "Player" 的实体
|
||||||
|
|
||||||
|
// 单组件匹配
|
||||||
|
const componentMatcher = Matcher.byComponent(Health); // 匹配拥有 Health 组件的实体
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统生命周期
|
||||||
|
|
||||||
|
系统提供了完整的生命周期回调:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Example')
|
||||||
|
class ExampleSystem extends EntitySystem {
|
||||||
|
protected onInitialize(): void {
|
||||||
|
console.log('系统初始化');
|
||||||
|
// 系统被添加到场景时调用,用于初始化资源
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onBegin(): void {
|
||||||
|
// 每帧处理开始前调用
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 主要的处理逻辑
|
||||||
|
for (const entity of entities) {
|
||||||
|
// 处理每个实体
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected lateProcess(entities: readonly Entity[]): void {
|
||||||
|
// 主处理之后的后期处理
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onEnd(): void {
|
||||||
|
// 每帧处理结束后调用
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDestroy(): void {
|
||||||
|
console.log('系统销毁');
|
||||||
|
// 系统从场景移除时调用,用于清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实体事件监听
|
||||||
|
|
||||||
|
系统可以监听实体的添加和移除事件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('EnemyManager')
|
||||||
|
class EnemyManagerSystem extends EntitySystem {
|
||||||
|
private enemyCount = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Enemy, Health));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAdded(entity: Entity): void {
|
||||||
|
this.enemyCount++;
|
||||||
|
console.log(`敌人加入战斗,当前敌人数量: ${this.enemyCount}`);
|
||||||
|
|
||||||
|
// 可以在这里为新敌人设置初始状态
|
||||||
|
const health = entity.getComponent(Health);
|
||||||
|
if (health) {
|
||||||
|
health.current = health.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onRemoved(entity: Entity): void {
|
||||||
|
this.enemyCount--;
|
||||||
|
console.log(`敌人被移除,剩余敌人数量: ${this.enemyCount}`);
|
||||||
|
|
||||||
|
// 检查是否所有敌人都被消灭
|
||||||
|
if (this.enemyCount === 0) {
|
||||||
|
this.scene?.eventSystem.emitSync('all_enemies_defeated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统属性和方法
|
||||||
|
|
||||||
|
### 重要属性
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Example')
|
||||||
|
class ExampleSystem extends EntitySystem {
|
||||||
|
showSystemInfo(): void {
|
||||||
|
console.log(`系统名称: ${this.systemName}`); // 系统名称
|
||||||
|
console.log(`更新顺序: ${this.updateOrder}`); // 更新时序
|
||||||
|
console.log(`是否启用: ${this.enabled}`); // 启用状态
|
||||||
|
console.log(`实体数量: ${this.entities.length}`); // 匹配的实体数量
|
||||||
|
console.log(`所属场景: ${this.scene?.name}`); // 所属场景
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实体访问
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 方式1:使用参数中的实体列表
|
||||||
|
for (const entity of entities) {
|
||||||
|
// 处理实体
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2:使用 this.entities 属性(与参数相同)
|
||||||
|
for (const entity of this.entities) {
|
||||||
|
// 处理实体
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 控制系统执行
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Conditional')
|
||||||
|
class ConditionalSystem extends EntitySystem {
|
||||||
|
private shouldProcess = true;
|
||||||
|
|
||||||
|
protected onCheckProcessing(): boolean {
|
||||||
|
// 返回 false 时跳过本次处理
|
||||||
|
return this.shouldProcess && this.entities.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(): void {
|
||||||
|
this.shouldProcess = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resume(): void {
|
||||||
|
this.shouldProcess = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 事件系统集成
|
||||||
|
|
||||||
|
系统可以方便地监听和发送事件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('GameLogic')
|
||||||
|
class GameLogicSystem extends EntitySystem {
|
||||||
|
protected onInitialize(): void {
|
||||||
|
// 添加事件监听器(系统销毁时自动清理)
|
||||||
|
this.addEventListener('player_died', this.onPlayerDied.bind(this));
|
||||||
|
this.addEventListener('level_complete', this.onLevelComplete.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlayerDied(data: any): void {
|
||||||
|
console.log('玩家死亡,重新开始游戏');
|
||||||
|
// 处理玩家死亡逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLevelComplete(data: any): void {
|
||||||
|
console.log('关卡完成,加载下一关');
|
||||||
|
// 处理关卡完成逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 在处理过程中发送事件
|
||||||
|
for (const entity of entities) {
|
||||||
|
const health = entity.getComponent(Health);
|
||||||
|
if (health && health.current <= 0) {
|
||||||
|
this.scene?.eventSystem.emitSync('entity_died', { entity });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能监控
|
||||||
|
|
||||||
|
系统内置了性能监控功能:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Performance')
|
||||||
|
class PerformanceSystem extends EntitySystem {
|
||||||
|
protected onEnd(): void {
|
||||||
|
// 获取性能数据
|
||||||
|
const perfData = this.getPerformanceData();
|
||||||
|
if (perfData) {
|
||||||
|
console.log(`执行时间: ${perfData.executionTime.toFixed(2)}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取性能统计
|
||||||
|
const stats = this.getPerformanceStats();
|
||||||
|
if (stats) {
|
||||||
|
console.log(`平均执行时间: ${stats.averageTime.toFixed(2)}ms`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetPerformance(): void {
|
||||||
|
this.resetPerformanceData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统管理
|
||||||
|
|
||||||
|
### 添加系统到场景
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在场景子类中添加系统
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 添加系统
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
this.addSystem(new RenderSystem());
|
||||||
|
this.addSystem(new PhysicsSystem());
|
||||||
|
|
||||||
|
// 设置系统更新顺序
|
||||||
|
const movementSystem = this.getSystem(MovementSystem);
|
||||||
|
if (movementSystem) {
|
||||||
|
movementSystem.updateOrder = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 系统更新顺序
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Input')
|
||||||
|
class InputSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(InputComponent));
|
||||||
|
this.updateOrder = -100; // 输入系统优先执行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSSystem('Physics')
|
||||||
|
class PhysicsSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(RigidBody));
|
||||||
|
this.updateOrder = 0; // 默认顺序
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSSystem('Render')
|
||||||
|
class RenderSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Sprite, Transform));
|
||||||
|
this.updateOrder = 100; // 渲染系统最后执行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 复杂系统示例
|
||||||
|
|
||||||
|
### 碰撞检测系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Collision')
|
||||||
|
class CollisionSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Transform, Collider));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 简单的 n² 碰撞检测
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
for (let j = i + 1; j < entities.length; j++) {
|
||||||
|
this.checkCollision(entities[i], entities[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkCollision(entityA: Entity, entityB: Entity): void {
|
||||||
|
const transformA = entityA.getComponent(Transform);
|
||||||
|
const transformB = entityB.getComponent(Transform);
|
||||||
|
const colliderA = entityA.getComponent(Collider);
|
||||||
|
const colliderB = entityB.getComponent(Collider);
|
||||||
|
|
||||||
|
if (this.isColliding(transformA, colliderA, transformB, colliderB)) {
|
||||||
|
// 发送碰撞事件
|
||||||
|
this.scene?.eventSystem.emitSync('collision', {
|
||||||
|
entityA,
|
||||||
|
entityB
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isColliding(transformA: Transform, colliderA: Collider,
|
||||||
|
transformB: Transform, colliderB: Collider): boolean {
|
||||||
|
// 碰撞检测逻辑
|
||||||
|
return false; // 简化示例
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态机系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('StateMachine')
|
||||||
|
class StateMachineSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(StateMachine));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const stateMachine = entity.getComponent(StateMachine);
|
||||||
|
if (stateMachine) {
|
||||||
|
stateMachine.updateTimer(Time.deltaTime);
|
||||||
|
this.updateState(entity, stateMachine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateState(entity: Entity, stateMachine: StateMachine): void {
|
||||||
|
switch (stateMachine.currentState) {
|
||||||
|
case EntityState.Idle:
|
||||||
|
this.handleIdleState(entity, stateMachine);
|
||||||
|
break;
|
||||||
|
case EntityState.Moving:
|
||||||
|
this.handleMovingState(entity, stateMachine);
|
||||||
|
break;
|
||||||
|
case EntityState.Attacking:
|
||||||
|
this.handleAttackingState(entity, stateMachine);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleIdleState(entity: Entity, stateMachine: StateMachine): void {
|
||||||
|
// 空闲状态逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMovingState(entity: Entity, stateMachine: StateMachine): void {
|
||||||
|
// 移动状态逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleAttackingState(entity: Entity, stateMachine: StateMachine): void {
|
||||||
|
// 攻击状态逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 系统单一职责
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的系统设计 - 职责单一
|
||||||
|
@ECSSystem('Movement')
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Position, Velocity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSSystem('Rendering')
|
||||||
|
class RenderingSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Sprite, Transform));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 避免的系统设计 - 职责过多
|
||||||
|
@ECSSystem('GameSystem')
|
||||||
|
class GameSystem extends EntitySystem {
|
||||||
|
// 一个系统处理移动、渲染、音效等多种逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用装饰器
|
||||||
|
|
||||||
|
**必须使用 `@ECSSystem` 装饰器**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 正确的用法
|
||||||
|
@ECSSystem('Physics')
|
||||||
|
class PhysicsSystem extends EntitySystem {
|
||||||
|
// 系统实现
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 错误的用法 - 没有装饰器
|
||||||
|
class BadSystem extends EntitySystem {
|
||||||
|
// 这样定义的系统可能在生产环境出现问题
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 合理的更新顺序
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 按逻辑顺序设置系统的更新时序
|
||||||
|
@ECSSystem('Input')
|
||||||
|
class InputSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.updateOrder = -100; // 最先处理输入
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSSystem('Logic')
|
||||||
|
class GameLogicSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.updateOrder = 0; // 处理游戏逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSSystem('Render')
|
||||||
|
class RenderSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.updateOrder = 100; // 最后进行渲染
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 避免在系统间直接引用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ 避免:系统间直接引用
|
||||||
|
@ECSSystem('Bad')
|
||||||
|
class BadSystem extends EntitySystem {
|
||||||
|
private otherSystem: SomeOtherSystem; // 避免直接引用其他系统
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 推荐:通过事件系统通信
|
||||||
|
@ECSSystem('Good')
|
||||||
|
class GoodSystem extends EntitySystem {
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 通过事件系统与其他系统通信
|
||||||
|
this.scene?.eventSystem.emitSync('data_updated', { entities });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 及时清理资源
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSSystem('Resource')
|
||||||
|
class ResourceSystem extends EntitySystem {
|
||||||
|
private resources: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
protected onDestroy(): void {
|
||||||
|
// 清理资源
|
||||||
|
for (const [key, resource] of this.resources) {
|
||||||
|
if (resource.dispose) {
|
||||||
|
resource.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.resources.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
系统是 ECS 架构的逻辑处理核心,正确设计和使用系统能让你的游戏代码更加模块化、高效和易于维护。
|
||||||
553
docs/guide/time-and-timers.md
Normal file
553
docs/guide/time-and-timers.md
Normal file
@@ -0,0 +1,553 @@
|
|||||||
|
# 时间和定时器系统
|
||||||
|
|
||||||
|
ECS 框架提供了完整的时间管理和定时器系统,包括时间缩放、帧时间计算和灵活的定时器调度功能。
|
||||||
|
|
||||||
|
## Time 类
|
||||||
|
|
||||||
|
Time 类是框架的时间管理核心,提供了游戏时间相关的所有功能。
|
||||||
|
|
||||||
|
### 基本时间属性
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Time } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameSystem extends EntitySystem {
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 获取帧时间(秒)
|
||||||
|
const deltaTime = Time.deltaTime;
|
||||||
|
|
||||||
|
// 获取未缩放的帧时间
|
||||||
|
const unscaledDelta = Time.unscaledDeltaTime;
|
||||||
|
|
||||||
|
// 获取游戏总时间
|
||||||
|
const totalTime = Time.totalTime;
|
||||||
|
|
||||||
|
// 获取当前帧数
|
||||||
|
const frameCount = Time.frameCount;
|
||||||
|
|
||||||
|
console.log(`第 ${frameCount} 帧,帧时间: ${deltaTime}s,总时间: ${totalTime}s`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 时间缩放
|
||||||
|
|
||||||
|
Time 类支持时间缩放功能,可以实现慢动作、快进等效果:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TimeControlSystem extends EntitySystem {
|
||||||
|
public enableSlowMotion(): void {
|
||||||
|
// 设置为慢动作(50%速度)
|
||||||
|
Time.timeScale = 0.5;
|
||||||
|
console.log('慢动作模式启用');
|
||||||
|
}
|
||||||
|
|
||||||
|
public enableFastForward(): void {
|
||||||
|
// 设置为快进(200%速度)
|
||||||
|
Time.timeScale = 2.0;
|
||||||
|
console.log('快进模式启用');
|
||||||
|
}
|
||||||
|
|
||||||
|
public pauseGame(): void {
|
||||||
|
// 暂停游戏(时间静止)
|
||||||
|
Time.timeScale = 0;
|
||||||
|
console.log('游戏暂停');
|
||||||
|
}
|
||||||
|
|
||||||
|
public resumeNormalSpeed(): void {
|
||||||
|
// 恢复正常速度
|
||||||
|
Time.timeScale = 1.0;
|
||||||
|
console.log('恢复正常速度');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// deltaTime 会受到 timeScale 影响
|
||||||
|
const scaledDelta = Time.deltaTime; // 受时间缩放影响
|
||||||
|
const realDelta = Time.unscaledDeltaTime; // 不受时间缩放影响
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
const movement = entity.getComponent(Movement);
|
||||||
|
if (movement) {
|
||||||
|
// 使用缩放时间进行游戏逻辑更新
|
||||||
|
movement.update(scaledDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ui = entity.getComponent(UIComponent);
|
||||||
|
if (ui) {
|
||||||
|
// UI 动画使用真实时间,不受游戏时间缩放影响
|
||||||
|
ui.update(realDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 时间检查工具
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class CooldownSystem extends EntitySystem {
|
||||||
|
private lastAttackTime = 0;
|
||||||
|
private lastSpawnTime = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(Weapon));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 检查攻击冷却
|
||||||
|
if (Time.checkEvery(1.5, this.lastAttackTime)) {
|
||||||
|
this.performAttack();
|
||||||
|
this.lastAttackTime = Time.totalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查生成间隔
|
||||||
|
if (Time.checkEvery(3.0, this.lastSpawnTime)) {
|
||||||
|
this.spawnEnemy();
|
||||||
|
this.lastSpawnTime = Time.totalTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private performAttack(): void {
|
||||||
|
console.log('执行攻击!');
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnEnemy(): void {
|
||||||
|
console.log('生成敌人!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core.schedule 定时器系统
|
||||||
|
|
||||||
|
Core 提供了强大的定时器调度功能,可以创建一次性或重复执行的定时器。
|
||||||
|
|
||||||
|
### 基本定时器使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 创建一次性定时器
|
||||||
|
this.createOneTimeTimers();
|
||||||
|
|
||||||
|
// 创建重复定时器
|
||||||
|
this.createRepeatingTimers();
|
||||||
|
|
||||||
|
// 创建带上下文的定时器
|
||||||
|
this.createContextTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createOneTimeTimers(): void {
|
||||||
|
// 2秒后执行一次
|
||||||
|
Core.schedule(2.0, false, null, (timer) => {
|
||||||
|
console.log('2秒延迟执行');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5秒后显示提示
|
||||||
|
Core.schedule(5.0, false, this, (timer) => {
|
||||||
|
const scene = timer.getContext<GameScene>();
|
||||||
|
scene.showTip('游戏提示:5秒已过!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createRepeatingTimers(): void {
|
||||||
|
// 每秒重复执行
|
||||||
|
const heartbeatTimer = Core.schedule(1.0, true, null, (timer) => {
|
||||||
|
console.log(`游戏心跳 - 总时间: ${Time.totalTime.toFixed(1)}s`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 可以保存定时器引用用于后续控制
|
||||||
|
this.saveTimerReference(heartbeatTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createContextTimers(): void {
|
||||||
|
const gameData = { score: 0, level: 1 };
|
||||||
|
|
||||||
|
// 每2秒增加分数
|
||||||
|
Core.schedule(2.0, true, gameData, (timer) => {
|
||||||
|
const data = timer.getContext<typeof gameData>();
|
||||||
|
data.score += 10;
|
||||||
|
console.log(`分数增加!当前分数: ${data.score}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveTimerReference(timer: any): void {
|
||||||
|
// 可以稍后停止定时器
|
||||||
|
setTimeout(() => {
|
||||||
|
timer.stop();
|
||||||
|
console.log('定时器已停止');
|
||||||
|
}, 10000); // 10秒后停止
|
||||||
|
}
|
||||||
|
|
||||||
|
private showTip(message: string): void {
|
||||||
|
console.log('提示:', message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定时器控制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TimerControlExample {
|
||||||
|
private attackTimer: any;
|
||||||
|
private spawnerTimer: any;
|
||||||
|
|
||||||
|
public startCombat(): void {
|
||||||
|
// 启动攻击定时器
|
||||||
|
this.attackTimer = Core.schedule(0.5, true, this, (timer) => {
|
||||||
|
const self = timer.getContext<TimerControlExample>();
|
||||||
|
self.performAttack();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动敌人生成定时器
|
||||||
|
this.spawnerTimer = Core.schedule(3.0, true, null, (timer) => {
|
||||||
|
this.spawnEnemy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopCombat(): void {
|
||||||
|
// 停止所有战斗相关定时器
|
||||||
|
if (this.attackTimer) {
|
||||||
|
this.attackTimer.stop();
|
||||||
|
console.log('攻击定时器已停止');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.spawnerTimer) {
|
||||||
|
this.spawnerTimer.stop();
|
||||||
|
console.log('生成定时器已停止');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetAttackTimer(): void {
|
||||||
|
// 重置攻击定时器
|
||||||
|
if (this.attackTimer) {
|
||||||
|
this.attackTimer.reset();
|
||||||
|
console.log('攻击定时器已重置');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private performAttack(): void {
|
||||||
|
console.log('执行攻击');
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnEnemy(): void {
|
||||||
|
console.log('生成敌人');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 复杂定时器场景
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class AdvancedTimerUsage {
|
||||||
|
private powerUpDuration = 0;
|
||||||
|
private powerUpActive = false;
|
||||||
|
|
||||||
|
public activatePowerUp(): void {
|
||||||
|
if (this.powerUpActive) {
|
||||||
|
console.log('能力提升已激活');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.powerUpActive = true;
|
||||||
|
this.powerUpDuration = 10; // 10秒持续时间
|
||||||
|
|
||||||
|
console.log('能力提升激活!');
|
||||||
|
|
||||||
|
// 每秒更新剩余时间
|
||||||
|
const countdownTimer = Core.schedule(1.0, true, this, (timer) => {
|
||||||
|
const self = timer.getContext<AdvancedTimerUsage>();
|
||||||
|
self.powerUpDuration--;
|
||||||
|
|
||||||
|
console.log(`能力提升剩余时间: ${self.powerUpDuration}秒`);
|
||||||
|
|
||||||
|
if (self.powerUpDuration <= 0) {
|
||||||
|
self.deactivatePowerUp();
|
||||||
|
timer.stop(); // 停止倒计时
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 能力提升结束定时器(备用)
|
||||||
|
Core.schedule(10.0, false, this, (timer) => {
|
||||||
|
const self = timer.getContext<AdvancedTimerUsage>();
|
||||||
|
if (self.powerUpActive) {
|
||||||
|
self.deactivatePowerUp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private deactivatePowerUp(): void {
|
||||||
|
this.powerUpActive = false;
|
||||||
|
this.powerUpDuration = 0;
|
||||||
|
console.log('能力提升结束');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建波次攻击定时器
|
||||||
|
public startWaveAttack(): void {
|
||||||
|
let waveCount = 0;
|
||||||
|
const maxWaves = 5;
|
||||||
|
|
||||||
|
const waveTimer = Core.schedule(2.0, true, { waveCount, maxWaves }, (timer) => {
|
||||||
|
const context = timer.getContext<{ waveCount: number, maxWaves: number }>();
|
||||||
|
context.waveCount++;
|
||||||
|
|
||||||
|
console.log(`第 ${context.waveCount} 波攻击!`);
|
||||||
|
|
||||||
|
if (context.waveCount >= context.maxWaves) {
|
||||||
|
console.log('所有波次攻击完成');
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建条件定时器
|
||||||
|
public startConditionalTimer(): void {
|
||||||
|
Core.schedule(0.1, true, this, (timer) => {
|
||||||
|
const self = timer.getContext<AdvancedTimerUsage>();
|
||||||
|
|
||||||
|
// 检查某个条件
|
||||||
|
if (self.shouldStopTimer()) {
|
||||||
|
console.log('条件满足,停止定时器');
|
||||||
|
timer.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续执行定时器逻辑
|
||||||
|
self.performTimerAction();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldStopTimer(): boolean {
|
||||||
|
// 检查停止条件
|
||||||
|
return Time.totalTime > 30; // 30秒后停止
|
||||||
|
}
|
||||||
|
|
||||||
|
private performTimerAction(): void {
|
||||||
|
console.log('执行定时器动作');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实际应用示例
|
||||||
|
|
||||||
|
### 技能冷却系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SkillCooldownSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.all(SkillComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const skill = entity.getComponent(SkillComponent);
|
||||||
|
|
||||||
|
// 更新技能冷却
|
||||||
|
if (skill.isOnCooldown) {
|
||||||
|
skill.cooldownRemaining -= Time.deltaTime;
|
||||||
|
|
||||||
|
if (skill.cooldownRemaining <= 0) {
|
||||||
|
skill.cooldownRemaining = 0;
|
||||||
|
skill.isOnCooldown = false;
|
||||||
|
console.log(`技能 ${skill.name} 冷却完成`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public useSkill(entity: Entity, skillName: string): boolean {
|
||||||
|
const skill = entity.getComponent(SkillComponent);
|
||||||
|
|
||||||
|
if (skill.isOnCooldown) {
|
||||||
|
console.log(`技能 ${skillName} 还在冷却中,剩余 ${skill.cooldownRemaining.toFixed(1)}秒`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行技能
|
||||||
|
this.executeSkill(entity, skill);
|
||||||
|
|
||||||
|
// 开始冷却
|
||||||
|
skill.isOnCooldown = true;
|
||||||
|
skill.cooldownRemaining = skill.cooldownDuration;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private executeSkill(entity: Entity, skill: SkillComponent): void {
|
||||||
|
console.log(`执行技能: ${skill.name}`);
|
||||||
|
// 技能效果逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 游戏状态定时器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GameStateManager {
|
||||||
|
private gamePhase = 'preparation';
|
||||||
|
private phaseTimer: any;
|
||||||
|
|
||||||
|
public startGame(): void {
|
||||||
|
this.startPreparationPhase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private startPreparationPhase(): void {
|
||||||
|
this.gamePhase = 'preparation';
|
||||||
|
console.log('准备阶段开始 - 10秒准备时间');
|
||||||
|
|
||||||
|
this.phaseTimer = Core.schedule(10.0, false, this, (timer) => {
|
||||||
|
const self = timer.getContext<GameStateManager>();
|
||||||
|
self.startCombatPhase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private startCombatPhase(): void {
|
||||||
|
this.gamePhase = 'combat';
|
||||||
|
console.log('战斗阶段开始 - 60秒战斗时间');
|
||||||
|
|
||||||
|
this.phaseTimer = Core.schedule(60.0, false, this, (timer) => {
|
||||||
|
const self = timer.getContext<GameStateManager>();
|
||||||
|
self.startResultPhase();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每10秒刷新一波敌人
|
||||||
|
Core.schedule(10.0, true, null, (timer) => {
|
||||||
|
if (this.gamePhase === 'combat') {
|
||||||
|
this.spawnEnemyWave();
|
||||||
|
} else {
|
||||||
|
timer.stop(); // 战斗阶段结束时停止刷新
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private startResultPhase(): void {
|
||||||
|
this.gamePhase = 'result';
|
||||||
|
console.log('结算阶段开始 - 5秒结算时间');
|
||||||
|
|
||||||
|
this.phaseTimer = Core.schedule(5.0, false, this, (timer) => {
|
||||||
|
const self = timer.getContext<GameStateManager>();
|
||||||
|
self.endGame();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private endGame(): void {
|
||||||
|
console.log('游戏结束');
|
||||||
|
this.gamePhase = 'ended';
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnEnemyWave(): void {
|
||||||
|
console.log('刷新敌人波次');
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentPhase(): string {
|
||||||
|
return this.gamePhase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 合理使用时间类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MovementSystem extends EntitySystem {
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const movement = entity.getComponent(Movement);
|
||||||
|
|
||||||
|
// ✅ 游戏逻辑使用缩放时间
|
||||||
|
movement.position.x += movement.velocity.x * Time.deltaTime;
|
||||||
|
|
||||||
|
// ✅ UI动画使用真实时间(不受游戏暂停影响)
|
||||||
|
const ui = entity.getComponent(UIAnimation);
|
||||||
|
if (ui) {
|
||||||
|
ui.update(Time.unscaledDeltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 定时器管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class TimerManager {
|
||||||
|
private timers: any[] = [];
|
||||||
|
|
||||||
|
public createManagedTimer(duration: number, repeats: boolean, callback: () => void): any {
|
||||||
|
const timer = Core.schedule(duration, repeats, null, callback);
|
||||||
|
this.timers.push(timer);
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopAllTimers(): void {
|
||||||
|
for (const timer of this.timers) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
this.timers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public cleanupCompletedTimers(): void {
|
||||||
|
this.timers = this.timers.filter(timer => !timer.isDone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免过多的定时器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ 避免:为每个实体创建定时器
|
||||||
|
class BadExample extends EntitySystem {
|
||||||
|
protected onAdded(entity: Entity): void {
|
||||||
|
Core.schedule(1.0, true, entity, (timer) => {
|
||||||
|
// 每个实体一个定时器,性能差
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 推荐:在系统中统一管理时间
|
||||||
|
class GoodExample extends EntitySystem {
|
||||||
|
private lastUpdateTime = 0;
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 每秒执行一次逻辑
|
||||||
|
if (Time.checkEvery(1.0, this.lastUpdateTime)) {
|
||||||
|
this.processAllEntities(entities);
|
||||||
|
this.lastUpdateTime = Time.totalTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processAllEntities(entities: readonly Entity[]): void {
|
||||||
|
// 批量处理所有实体
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 定时器上下文使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface TimerContext {
|
||||||
|
entityId: number;
|
||||||
|
duration: number;
|
||||||
|
onComplete: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContextualTimerExample {
|
||||||
|
public createEntityTimer(entityId: number, duration: number, onComplete: () => void): void {
|
||||||
|
const context: TimerContext = {
|
||||||
|
entityId,
|
||||||
|
duration,
|
||||||
|
onComplete
|
||||||
|
};
|
||||||
|
|
||||||
|
Core.schedule(duration, false, context, (timer) => {
|
||||||
|
const ctx = timer.getContext<TimerContext>();
|
||||||
|
console.log(`实体 ${ctx.entityId} 的定时器完成`);
|
||||||
|
ctx.onComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
时间和定时器系统是游戏开发中的重要工具,正确使用这些功能能让你的游戏逻辑更加精确和可控。
|
||||||
23
docs/index.md
Normal file
23
docs/index.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: "ECS Framework"
|
||||||
|
text: "高性能ECS框架"
|
||||||
|
tagline: "为Javascript游戏开发而设计"
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: 快速开始
|
||||||
|
link: /guide/getting-started
|
||||||
|
- theme: alt
|
||||||
|
text: 查看示例
|
||||||
|
link: https://github.com/esengine/lawn-mower-demo
|
||||||
|
|
||||||
|
features:
|
||||||
|
- title: 高性能
|
||||||
|
details: 支持大规模实体处理
|
||||||
|
- title: 类型安全
|
||||||
|
details: 完整的TypeScript支持,编译时类型检查
|
||||||
|
- title: 模块化设计
|
||||||
|
details: 核心功能独立打包,支持多平台
|
||||||
|
---
|
||||||
@@ -1,425 +0,0 @@
|
|||||||
# 性能优化指南
|
|
||||||
|
|
||||||
本文档介绍ECS框架的性能优化技术和最佳实践。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
1. [查询系统优化](#查询系统优化)
|
|
||||||
2. [实体管理优化](#实体管理优化)
|
|
||||||
3. [组件设计优化](#组件设计优化)
|
|
||||||
4. [系统设计优化](#系统设计优化)
|
|
||||||
5. [内存管理](#内存管理)
|
|
||||||
6. [性能监控](#性能监控)
|
|
||||||
|
|
||||||
## 查询系统优化
|
|
||||||
|
|
||||||
### 使用高效的查询方法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐:使用标签查询(快速)
|
|
||||||
const enemies = entityManager.getEntitiesByTag(2);
|
|
||||||
|
|
||||||
// 推荐:使用组件查询
|
|
||||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
|
||||||
|
|
||||||
// 推荐:使用Scene的查询系统
|
|
||||||
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
|
||||||
|
|
||||||
// 谨慎:自定义条件查询(较慢)
|
|
||||||
const nearbyEnemies = entityManager
|
|
||||||
.query()
|
|
||||||
.withAll(PositionComponent)
|
|
||||||
.where(entity => {
|
|
||||||
const pos = entity.getComponent(PositionComponent);
|
|
||||||
return pos && Math.abs(pos.x - playerX) < 100;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查询结果缓存
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class OptimizedCombatSystem extends EntitySystem {
|
|
||||||
private cachedEnemies: Entity[] = [];
|
|
||||||
private lastCacheUpdate = 0;
|
|
||||||
private cacheInterval = 5; // 每5帧更新一次
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// 缓存查询结果
|
|
||||||
if (Time.frameCount - this.lastCacheUpdate >= this.cacheInterval) {
|
|
||||||
this.cachedEnemies = this.entityManager.getEntitiesByTag(2);
|
|
||||||
this.lastCacheUpdate = Time.frameCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用缓存的结果
|
|
||||||
this.cachedEnemies.forEach(enemy => {
|
|
||||||
this.processEnemy(enemy);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private processEnemy(enemy: Entity): void {
|
|
||||||
// 处理敌人逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实体管理优化
|
|
||||||
|
|
||||||
### 批量创建实体
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐:使用Scene的批量创建
|
|
||||||
function createEnemyWave(count: number): Entity[] {
|
|
||||||
const enemies = scene.createEntities(count, "Enemy");
|
|
||||||
|
|
||||||
// 批量配置组件
|
|
||||||
enemies.forEach((enemy, index) => {
|
|
||||||
enemy.addComponent(new PositionComponent(
|
|
||||||
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
|
|
||||||
// 使用简单的实体复用策略
|
|
||||||
class EntityReusableManager {
|
|
||||||
private inactiveEntities: Entity[] = [];
|
|
||||||
private scene: Scene;
|
|
||||||
|
|
||||||
constructor(scene: Scene) {
|
|
||||||
this.scene = scene;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预创建实体
|
|
||||||
preCreateEntities(count: number, entityName: string): void {
|
|
||||||
const entities = this.scene.createEntities(count, entityName);
|
|
||||||
entities.forEach(entity => {
|
|
||||||
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.processAI(this.aiEntities[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
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 VelocityComponent());
|
|
||||||
}
|
|
||||||
return bullet;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyBullet(bullet: Entity): void {
|
|
||||||
this.bulletManager.recycleEntity(bullet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控
|
|
||||||
|
|
||||||
### 基础性能统计
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class PerformanceMonitor {
|
|
||||||
private scene: Scene;
|
|
||||||
private entityManager: EntityManager;
|
|
||||||
|
|
||||||
constructor(scene: Scene, entityManager: EntityManager) {
|
|
||||||
this.scene = scene;
|
|
||||||
this.entityManager = entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPerformanceReport(): any {
|
|
||||||
return {
|
|
||||||
// 实体统计
|
|
||||||
entities: {
|
|
||||||
total: this.entityManager.entityCount,
|
|
||||||
active: this.entityManager.activeEntityCount
|
|
||||||
},
|
|
||||||
|
|
||||||
// 场景统计
|
|
||||||
scene: this.scene.getStats(),
|
|
||||||
|
|
||||||
// 查询系统统计
|
|
||||||
querySystem: this.scene.querySystem.getStats(),
|
|
||||||
|
|
||||||
// 内存使用
|
|
||||||
memory: {
|
|
||||||
used: (performance as any).memory?.usedJSHeapSize || 0,
|
|
||||||
total: (performance as any).memory?.totalJSHeapSize || 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public logPerformance(): void {
|
|
||||||
const report = this.getPerformanceReport();
|
|
||||||
console.log('性能报告:', report);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 帧率监控
|
|
||||||
|
|
||||||
```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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践总结
|
|
||||||
|
|
||||||
### 查询优化
|
|
||||||
1. 优先使用标签查询和组件查询
|
|
||||||
2. 缓存频繁使用的查询结果
|
|
||||||
3. 避免过度使用自定义条件查询
|
|
||||||
4. 合理设置查询缓存更新频率
|
|
||||||
|
|
||||||
### 实体管理
|
|
||||||
1. 使用批量创建方法
|
|
||||||
2. 实现实体池化减少GC压力
|
|
||||||
3. 及时清理无用实体
|
|
||||||
4. 合理设置实体标签
|
|
||||||
|
|
||||||
### 组件设计
|
|
||||||
1. 保持组件数据紧凑
|
|
||||||
2. 避免在组件中分配大量对象
|
|
||||||
3. 使用组件池化
|
|
||||||
4. 分离数据和行为
|
|
||||||
|
|
||||||
### 系统设计
|
|
||||||
1. 合理安排系统更新顺序
|
|
||||||
2. 对重计算任务使用时间分片
|
|
||||||
3. 避免在系统中进行复杂查询
|
|
||||||
4. 缓存系统间的共享数据
|
|
||||||
|
|
||||||
### 内存管理
|
|
||||||
1. 定期清理无用实体和组件
|
|
||||||
2. 使用对象池减少GC
|
|
||||||
3. 监控内存使用情况
|
|
||||||
4. 避免内存泄漏
|
|
||||||
|
|
||||||
通过遵循这些最佳实践,可以显著提升ECS框架的性能表现。
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
# QuerySystem 使用指南
|
|
||||||
|
|
||||||
QuerySystem 是 ECS Framework 中的高性能实体查询系统,支持多级索引、智能缓存和类型安全的查询操作。
|
|
||||||
|
|
||||||
## 基本用法
|
|
||||||
|
|
||||||
### 1. 获取查询系统
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene, Entity } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建场景,查询系统会自动创建
|
|
||||||
const scene = new Scene();
|
|
||||||
const querySystem = scene.querySystem;
|
|
||||||
|
|
||||||
// 或者从Core获取当前场景的查询系统
|
|
||||||
import { Core } from '@esengine/ecs-framework';
|
|
||||||
const currentQuerySystem = Core.scene?.querySystem;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 基本查询操作
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 查询包含所有指定组件的实体
|
|
||||||
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
|
|
||||||
console.log(`找到 ${result.count} 个实体`);
|
|
||||||
|
|
||||||
// 查询包含任意指定组件的实体
|
|
||||||
const anyResult = querySystem.queryAny(HealthComponent, ManaComponent);
|
|
||||||
|
|
||||||
// 查询不包含指定组件的实体
|
|
||||||
const noneResult = querySystem.queryNone(DeadComponent);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 类型安全查询
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 类型安全的查询,返回实体和对应的组件
|
|
||||||
const typedResult = querySystem.queryAll(PositionComponent, VelocityComponent);
|
|
||||||
for (let i = 0; i < typedResult.entities.length; i++) {
|
|
||||||
const entity = typedResult.entities[i];
|
|
||||||
const [position, velocity] = typedResult.components[i];
|
|
||||||
// position 和 velocity 都是类型安全的
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询单个组件类型
|
|
||||||
const healthResult = querySystem.queryAll(HealthComponent);
|
|
||||||
for (const entity of healthResult.entities) {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (health) {
|
|
||||||
console.log(`实体 ${entity.name} 的生命值: ${health.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询两个组件类型
|
|
||||||
const movableResult = querySystem.queryAll(PositionComponent, VelocityComponent);
|
|
||||||
for (const entity of movableResult.entities) {
|
|
||||||
const position = entity.getComponent(PositionComponent);
|
|
||||||
const velocity = entity.getComponent(VelocityComponent);
|
|
||||||
if (position && velocity) {
|
|
||||||
// 更新位置
|
|
||||||
position.x += velocity.x;
|
|
||||||
position.y += velocity.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 使用查询构建器
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// QuerySystem不提供查询构建器,请使用Matcher进行复杂查询
|
|
||||||
// 推荐使用Matcher配合EntitySystem
|
|
||||||
|
|
||||||
import { Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建复杂查询条件
|
|
||||||
const visibleMatcher = Matcher.all(PositionComponent, RenderComponent)
|
|
||||||
.none(HiddenComponent);
|
|
||||||
|
|
||||||
// 通过QuerySystem执行查询
|
|
||||||
const visibleEntities = querySystem.query(visibleMatcher.getCondition());
|
|
||||||
|
|
||||||
// 过滤和排序需要手动处理
|
|
||||||
const sortedEntities = visibleEntities.entities
|
|
||||||
.filter(entity => entity.name.startsWith('Boss'))
|
|
||||||
.sort((a, b) => a.id - b.id)
|
|
||||||
.slice(0, 10); // 限制数量
|
|
||||||
|
|
||||||
// 迭代结果
|
|
||||||
sortedEntities.forEach((entity, index) => {
|
|
||||||
console.log(`敌人 ${index}: ${entity.name}`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 高级查询功能
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// QuerySystem主要提供基础查询方法
|
|
||||||
// 复杂查询推荐使用Matcher和EntitySystem
|
|
||||||
|
|
||||||
// 基本查询
|
|
||||||
const positionResult = querySystem.queryAll(PositionComponent, VelocityComponent);
|
|
||||||
const healthResult = querySystem.queryAll(HealthComponent);
|
|
||||||
const manaResult = querySystem.queryAll(ManaComponent);
|
|
||||||
|
|
||||||
// 排除死亡实体的移动实体
|
|
||||||
const aliveMovingEntities = positionResult.entities.filter(entity =>
|
|
||||||
!entity.hasComponent(DeadComponent)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果需要复杂查询,推荐在EntitySystem中使用Matcher
|
|
||||||
class ComplexQuerySystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(PositionComponent, VelocityComponent).none(DeadComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是过滤后的结果
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 场景级别的实体查询
|
|
||||||
|
|
||||||
除了使用QuerySystem,您还可以直接使用Scene提供的便捷查询方法:
|
|
||||||
|
|
||||||
### 基本场景查询
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 按名称查找实体
|
|
||||||
const player = scene.findEntity("Player");
|
|
||||||
const playerAlt = scene.getEntityByName("Player"); // 别名方法
|
|
||||||
|
|
||||||
// 按ID查找实体
|
|
||||||
const entity = scene.findEntityById(123);
|
|
||||||
|
|
||||||
// 按标签查找实体
|
|
||||||
const enemies = scene.findEntitiesByTag(2);
|
|
||||||
const enemiesAlt = scene.getEntitiesByTag(2); // 别名方法
|
|
||||||
|
|
||||||
// 获取所有实体
|
|
||||||
const allEntities = scene.entities.buffer;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能优化
|
|
||||||
|
|
||||||
### 1. 缓存管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 设置缓存配置
|
|
||||||
querySystem.setCacheConfig(200, 2000); // 最大200个缓存项,2秒超时
|
|
||||||
|
|
||||||
// 清空缓存
|
|
||||||
querySystem.clearCache();
|
|
||||||
|
|
||||||
// 预热常用查询(使用基础查询方法)
|
|
||||||
querySystem.queryAll(PositionComponent); // 预热Position查询
|
|
||||||
querySystem.queryAll(VelocityComponent); // 预热Velocity查询
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 索引优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 获取性能统计
|
|
||||||
const stats = querySystem.getStats();
|
|
||||||
console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`);
|
|
||||||
console.log(`实体数量: ${stats.entityCount}`);
|
|
||||||
|
|
||||||
// 获取详细性能报告
|
|
||||||
const report = querySystem.getPerformanceReport();
|
|
||||||
console.log(report);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 查询监听和快照
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// QuerySystem主要用于基础查询,高级功能请使用EntitySystem和事件系统
|
|
||||||
|
|
||||||
// 如需监听实体变化,使用事件系统
|
|
||||||
scene.entityManager.eventBus.on('entity:added', (entity) => {
|
|
||||||
if (entity.hasComponent(EnemyComponent)) {
|
|
||||||
console.log('新增敌人实体');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.entityManager.eventBus.on('entity:removed', (entity) => {
|
|
||||||
if (entity.hasComponent(EnemyComponent)) {
|
|
||||||
console.log('移除敌人实体');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 手动创建快照进行比较
|
|
||||||
const snapshot1 = querySystem.queryAll(PlayerComponent).entities.map(e => e.id);
|
|
||||||
// 稍后
|
|
||||||
const snapshot2 = querySystem.queryAll(PlayerComponent).entities.map(e => e.id);
|
|
||||||
|
|
||||||
// 比较快照
|
|
||||||
const added = snapshot2.filter(id => !snapshot1.includes(id));
|
|
||||||
const removed = snapshot1.filter(id => !snapshot2.includes(id));
|
|
||||||
console.log(`新增: ${added.length}, 移除: ${removed.length}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用Matcher进行高级查询
|
|
||||||
|
|
||||||
Matcher是一个优雅的查询封装器,提供流畅的API和强大的缓存机制。
|
|
||||||
|
|
||||||
### 基本Matcher用法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建Matcher查询条件
|
|
||||||
const movingMatcher = Matcher.all(PositionComponent, VelocityComponent);
|
|
||||||
// 在QuerySystem中需要使用基础查询方法
|
|
||||||
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities;
|
|
||||||
|
|
||||||
// 复合查询需要使用EntitySystem或手动过滤
|
|
||||||
const aliveEnemiesMatcher = Matcher.all(EnemyComponent, HealthComponent)
|
|
||||||
.any(WeaponComponent, MagicComponent)
|
|
||||||
.none(DeadComponent, StunnedComponent);
|
|
||||||
|
|
||||||
// 在EntitySystem中使用
|
|
||||||
class AliveEnemySystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(aliveEnemiesMatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是匹配的实体
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理存活的敌人
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 或者手动过滤
|
|
||||||
const enemyResult = scene.querySystem.queryAll(EnemyComponent, HealthComponent);
|
|
||||||
const aliveEnemies = enemyResult.entities.filter(entity => {
|
|
||||||
const hasWeaponOrMagic = entity.hasComponent(WeaponComponent) || entity.hasComponent(MagicComponent);
|
|
||||||
const isAlive = !entity.hasComponent(DeadComponent) && !entity.hasComponent(StunnedComponent);
|
|
||||||
return hasWeaponOrMagic && isAlive;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 单个实体检查
|
|
||||||
const playerResult = scene.querySystem.queryAll(PlayerComponent);
|
|
||||||
if (playerResult.entities.includes(someEntity)) {
|
|
||||||
console.log('这是玩家实体');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
console.log(`玩家数量: ${playerResult.count}`);
|
|
||||||
console.log(`是否有玩家: ${playerResult.count > 0}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 系统中使用Matcher
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
class MovementSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
// 在构造函数中直接传入Matcher
|
|
||||||
super(Matcher.all(PositionComponent, VelocityComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities参数已经是系统自动过滤后的实体
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 边界检查
|
|
||||||
if (position.x < 0 || position.x > 800) {
|
|
||||||
velocity.x = -velocity.x;
|
|
||||||
}
|
|
||||||
if (position.y < 0 || position.y > 600) {
|
|
||||||
velocity.y = -velocity.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 碰撞检测示例
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class CollisionSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
// 在构造函数中传入Matcher
|
|
||||||
super(Matcher.all(PositionComponent, ColliderComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是匹配的实体
|
|
||||||
const collidableEntities = entities;
|
|
||||||
|
|
||||||
// 检测碰撞
|
|
||||||
for (let i = 0; i < collidableEntities.length; i++) {
|
|
||||||
for (let j = i + 1; j < collidableEntities.length; j++) {
|
|
||||||
const entityA = collidableEntities[i];
|
|
||||||
const entityB = collidableEntities[j];
|
|
||||||
|
|
||||||
if (this.checkCollision(entityA, entityB)) {
|
|
||||||
this.handleCollision(entityA, entityB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkCollision(entityA: Entity, entityB: Entity): boolean {
|
|
||||||
const posA = entityA.getComponent(PositionComponent)!;
|
|
||||||
const posB = entityB.getComponent(PositionComponent)!;
|
|
||||||
const distance = Math.sqrt(
|
|
||||||
Math.pow(posA.x - posB.x, 2) + Math.pow(posA.y - posB.y, 2)
|
|
||||||
);
|
|
||||||
return distance < 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleCollision(entityA: Entity, entityB: Entity): void {
|
|
||||||
console.log(`碰撞检测: ${entityA.name} 与 ${entityB.name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生命值管理示例
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class HealthSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
// 在构造函数中传入Matcher
|
|
||||||
super(Matcher.all(HealthComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是匹配的实体
|
|
||||||
const healthEntities = entities;
|
|
||||||
const deadEntities: Entity[] = [];
|
|
||||||
|
|
||||||
for (const entity of healthEntities) {
|
|
||||||
const health = entity.getComponent(HealthComponent)!;
|
|
||||||
|
|
||||||
// 检查死亡
|
|
||||||
if (health.currentHealth <= 0) {
|
|
||||||
deadEntities.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除死亡实体
|
|
||||||
deadEntities.forEach(entity => {
|
|
||||||
console.log(`实体 ${entity.name} 已死亡`);
|
|
||||||
entity.destroy();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Matcher完整API参考
|
|
||||||
|
|
||||||
#### 静态创建方法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 基础静态方法
|
|
||||||
const allMatcher = Matcher.all(PositionComponent, VelocityComponent); // 必须包含所有组件
|
|
||||||
const anyMatcher = Matcher.any(WeaponComponent, MagicComponent); // 必须包含任意一个组件
|
|
||||||
const noneMatcher = Matcher.none(DeadComponent, DisabledComponent); // 不能包含任何指定组件
|
|
||||||
|
|
||||||
// 特殊查询静态方法
|
|
||||||
const tagMatcher = Matcher.byTag(1); // 按标签查询
|
|
||||||
const nameMatcher = Matcher.byName("Player"); // 按名称查询
|
|
||||||
const componentMatcher = Matcher.byComponent(HealthComponent); // 单组件查询
|
|
||||||
|
|
||||||
// 构建器方法
|
|
||||||
const complexMatcher = Matcher.complex(); // 创建复杂查询构建器
|
|
||||||
const emptyMatcher = Matcher.empty(); // 创建空匹配器
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 实例方法 - 条件构建
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 基础条件方法
|
|
||||||
const matcher = Matcher.empty()
|
|
||||||
.all(PositionComponent, VelocityComponent) // 必须包含所有组件
|
|
||||||
.any(WeaponComponent, MagicComponent) // 必须包含任意一个组件
|
|
||||||
.none(DeadComponent, StunnedComponent); // 不能包含任何指定组件
|
|
||||||
|
|
||||||
// 别名方法(提供更语义化的API)
|
|
||||||
const semanticMatcher = Matcher.empty()
|
|
||||||
.all(PositionComponent)
|
|
||||||
.exclude(DeadComponent) // exclude() 等同于 none()
|
|
||||||
.one(WeaponComponent, MagicComponent); // one() 等同于 any()
|
|
||||||
|
|
||||||
// 特殊条件方法
|
|
||||||
const advancedMatcher = Matcher.empty()
|
|
||||||
.all(EnemyComponent)
|
|
||||||
.withTag(2) // 指定标签
|
|
||||||
.withName("Boss") // 指定名称
|
|
||||||
.withComponent(HealthComponent); // 单组件条件
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 条件移除方法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 移除特殊条件
|
|
||||||
const matcher = Matcher.byTag(1)
|
|
||||||
.withName("Player")
|
|
||||||
.withComponent(HealthComponent);
|
|
||||||
|
|
||||||
// 移除各种条件
|
|
||||||
matcher.withoutTag(); // 移除标签条件
|
|
||||||
matcher.withoutName(); // 移除名称条件
|
|
||||||
matcher.withoutComponent(); // 移除单组件条件
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 实用工具方法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 检查和调试
|
|
||||||
const matcher = Matcher.all(PositionComponent, VelocityComponent)
|
|
||||||
.none(DeadComponent);
|
|
||||||
|
|
||||||
// 检查是否为空条件
|
|
||||||
if (matcher.isEmpty()) {
|
|
||||||
console.log('匹配器没有设置任何条件');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取条件信息(只读)
|
|
||||||
const condition = matcher.getCondition();
|
|
||||||
console.log('必须组件:', condition.all.map(c => c.name));
|
|
||||||
console.log('任选组件:', condition.any.map(c => c.name));
|
|
||||||
console.log('排除组件:', condition.none.map(c => c.name));
|
|
||||||
console.log('标签:', condition.tag);
|
|
||||||
console.log('名称:', condition.name);
|
|
||||||
console.log('单组件:', condition.component?.name);
|
|
||||||
|
|
||||||
// 调试输出
|
|
||||||
console.log('匹配器描述:', matcher.toString());
|
|
||||||
// 输出: "Matcher[all(PositionComponent, VelocityComponent) & none(DeadComponent)]"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 克隆和重置
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 克隆匹配器
|
|
||||||
const baseMatcher = Matcher.all(PositionComponent);
|
|
||||||
const livingMatcher = baseMatcher.clone().all(HealthComponent).none(DeadComponent);
|
|
||||||
const deadMatcher = baseMatcher.clone().all(DeadComponent);
|
|
||||||
|
|
||||||
// 重置匹配器
|
|
||||||
const reusableMatcher = Matcher.all(PositionComponent);
|
|
||||||
console.log(reusableMatcher.toString()); // "Matcher[all(PositionComponent)]"
|
|
||||||
|
|
||||||
reusableMatcher.reset(); // 清空所有条件
|
|
||||||
console.log(reusableMatcher.toString()); // "Matcher[]"
|
|
||||||
|
|
||||||
reusableMatcher.all(PlayerComponent); // 重新设置条件
|
|
||||||
console.log(reusableMatcher.toString()); // "Matcher[all(PlayerComponent)]"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 链式调用示例
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 复杂的链式调用
|
|
||||||
const complexMatcher = Matcher.empty()
|
|
||||||
.all(PositionComponent, RenderComponent) // 必须有位置和渲染组件
|
|
||||||
.any(PlayerComponent, NPCComponent) // 必须是玩家或NPC
|
|
||||||
.none(DeadComponent, HiddenComponent) // 不能死亡或隐藏
|
|
||||||
.withTag(1) // 标签为1
|
|
||||||
.exclude(DisabledComponent); // 不能被禁用
|
|
||||||
|
|
||||||
// 在EntitySystem中使用
|
|
||||||
class VisibleCharacterSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(complexMatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是符合所有条件的实体
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理可见角色的逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. Matcher使用建议
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐的用法:
|
|
||||||
const matcher = Matcher.all(Position, Velocity);
|
|
||||||
|
|
||||||
// 在系统中使用
|
|
||||||
class MySystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(RequiredComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是系统自动过滤的结果
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理逻辑...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 避免在process方法中重复查询
|
|
||||||
class InefficientSystem extends EntitySystem {
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// 不必要的额外查询,性能差
|
|
||||||
const condition = Matcher.all(RequiredComponent).getCondition();
|
|
||||||
const result = this.scene.querySystem.query(condition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Matcher API最佳实践
|
|
||||||
|
|
||||||
#### 选择合适的创建方式
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐:单一条件使用静态方法
|
|
||||||
const movingEntities = Matcher.all(PositionComponent, VelocityComponent);
|
|
||||||
const playerEntities = Matcher.byTag(PLAYER_TAG);
|
|
||||||
const specificEntity = Matcher.byName("Boss");
|
|
||||||
|
|
||||||
// 推荐:复杂条件使用链式调用
|
|
||||||
const complexMatcher = Matcher.empty()
|
|
||||||
.all(PositionComponent, HealthComponent)
|
|
||||||
.any(WeaponComponent, MagicComponent)
|
|
||||||
.none(DeadComponent);
|
|
||||||
|
|
||||||
// ❌ 不推荐:简单条件使用复杂语法
|
|
||||||
const simpleButBad = Matcher.empty().all(PositionComponent);
|
|
||||||
// 应该用: Matcher.all(PositionComponent)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 合理使用别名方法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 使用语义化的别名提高可读性
|
|
||||||
const combatUnits = Matcher.all(PositionComponent, HealthComponent)
|
|
||||||
.one(WeaponComponent, MagicComponent) // one() 比 any() 更语义化
|
|
||||||
.exclude(DeadComponent, PacifistComponent); // exclude() 比 none() 更直观
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 合理的克隆和重用
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐:基础匹配器重用
|
|
||||||
const livingEntityMatcher = Matcher.all(HealthComponent).none(DeadComponent);
|
|
||||||
const livingPlayerMatcher = livingEntityMatcher.clone().all(PlayerComponent);
|
|
||||||
const livingEnemyMatcher = livingEntityMatcher.clone().all(EnemyComponent);
|
|
||||||
|
|
||||||
// 推荐:重置匹配器重用
|
|
||||||
const reusableMatcher = Matcher.empty();
|
|
||||||
|
|
||||||
// 用于玩家系统
|
|
||||||
reusableMatcher.reset().all(PlayerComponent);
|
|
||||||
const playerSystem = new PlayerSystem(reusableMatcher.clone());
|
|
||||||
|
|
||||||
// 用于敌人系统
|
|
||||||
reusableMatcher.reset().all(EnemyComponent);
|
|
||||||
const enemySystem = new EnemySystem(reusableMatcher.clone());
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 调试和维护
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 在开发阶段添加调试信息
|
|
||||||
const debugMatcher = Matcher.all(ComplexComponent)
|
|
||||||
.any(VariantA, VariantB)
|
|
||||||
.none(DisabledComponent);
|
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
|
||||||
console.log('系统匹配条件:', debugMatcher.toString());
|
|
||||||
const condition = debugMatcher.getCondition();
|
|
||||||
console.log('预期匹配实体数:',
|
|
||||||
scene.querySystem.queryAll(...condition.all).count);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 查询优化
|
|
||||||
|
|
||||||
- **使用Matcher封装复杂查询**:提供更好的可读性和缓存
|
|
||||||
- **避免频繁创建查询**:在系统初始化时创建,重复使用
|
|
||||||
- **合理使用any()和none()条件**:减少不必要的实体遍历
|
|
||||||
- **利用Matcher的缓存机制**:自动优化重复查询性能
|
|
||||||
- **使用克隆方法复用基础条件**:避免重复定义相似的匹配条件
|
|
||||||
- **选择合适的静态方法**:单一条件优先使用对应的静态方法
|
|
||||||
|
|
||||||
### 3. 性能监控
|
|
||||||
|
|
||||||
- 定期检查查询性能报告
|
|
||||||
- 监控缓存命中率
|
|
||||||
- 优化频繁使用的查询
|
|
||||||
- 使用性能测试验证优化效果
|
|
||||||
|
|
||||||
### 4. 内存管理
|
|
||||||
|
|
||||||
- 及时清理不需要的查询监听器
|
|
||||||
- 合理设置缓存大小
|
|
||||||
- 避免创建过多的查询快照
|
|
||||||
- 适当使用Matcher的clone()和reset()方法
|
|
||||||
|
|
||||||
### 5. 代码组织
|
|
||||||
|
|
||||||
- **系统级别的Matcher**:在系统中创建和管理Matcher
|
|
||||||
- **查询逻辑封装**:将复杂查询封装到专门的方法中
|
|
||||||
- **条件复用**:使用clone()方法复用基础查询条件
|
|
||||||
- **清晰的命名**:给Matcher变量使用描述性的名称
|
|
||||||
|
|
||||||
### 6. 迁移指南
|
|
||||||
|
|
||||||
系统中Matcher的推荐用法:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 在EntitySystem中使用Matcher
|
|
||||||
class MySystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(ComponentA, ComponentB).none(ComponentC));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
// entities已经是系统自动过滤的结果
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用最佳实践
|
|
||||||
|
|
||||||
- 在EntitySystem构造函数中传入Matcher
|
|
||||||
- 使用`none()`来排除组件
|
|
||||||
- 使用`any()`来匹配任意组件
|
|
||||||
- 直接使用EntitySystem的entities参数,避免额外查询
|
|
||||||
- 定期检查查询性能和缓存命中率
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,343 +0,0 @@
|
|||||||
# SoA存储优化指南
|
|
||||||
|
|
||||||
SoA (Structure of Arrays) 存储模式是ECS框架中的高级性能优化特性,适用于大规模实体系统和批量操作场景。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
1. [什么是SoA存储](#什么是soa存储)
|
|
||||||
2. [适用场景](#适用场景)
|
|
||||||
3. [不适用场景](#不适用场景)
|
|
||||||
4. [装饰器使用指南](#装饰器使用指南)
|
|
||||||
5. [性能对比](#性能对比)
|
|
||||||
6. [最佳实践](#最佳实践)
|
|
||||||
7. [故障排除](#故障排除)
|
|
||||||
|
|
||||||
## 什么是SoA存储
|
|
||||||
|
|
||||||
### AoS vs SoA 对比
|
|
||||||
|
|
||||||
**传统AoS (Array of Structures):**
|
|
||||||
```typescript
|
|
||||||
// 数据在内存中的布局
|
|
||||||
[{x:1, y:2, z:3}, {x:4, y:5, z:6}, {x:7, y:8, z:9}]
|
|
||||||
// 内存布局: x1,y1,z1,x2,y2,z2,x3,y3,z3
|
|
||||||
```
|
|
||||||
|
|
||||||
**SoA (Structure of Arrays):**
|
|
||||||
```typescript
|
|
||||||
// 数据在内存中的布局
|
|
||||||
{
|
|
||||||
x: [1, 4, 7], // Float32Array
|
|
||||||
y: [2, 5, 8], // Float32Array
|
|
||||||
z: [3, 6, 9] // Float32Array
|
|
||||||
}
|
|
||||||
// 内存布局: x1,x2,x3,y1,y2,y3,z1,z2,z3
|
|
||||||
```
|
|
||||||
|
|
||||||
### SoA的优势
|
|
||||||
|
|
||||||
- **缓存友好**: 相同类型数据连续存储,提高缓存命中率
|
|
||||||
- **向量化优化**: 支持SIMD指令并行处理
|
|
||||||
- **内存局部性**: 批量操作时减少缓存miss
|
|
||||||
- **类型优化**: 针对不同数据类型使用最优存储格式
|
|
||||||
|
|
||||||
## 适用场景
|
|
||||||
|
|
||||||
### 推荐使用SoA的场景
|
|
||||||
|
|
||||||
1. **大规模实体系统**
|
|
||||||
```typescript
|
|
||||||
// 大量相似实体的物理系统
|
|
||||||
@EnableSoA
|
|
||||||
class PhysicsComponent extends Component {
|
|
||||||
@Float64 public x: number = 0;
|
|
||||||
@Float64 public y: number = 0;
|
|
||||||
@Float32 public velocityX: number = 0;
|
|
||||||
@Float32 public velocityY: number = 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **频繁批量更新操作**
|
|
||||||
```typescript
|
|
||||||
// 每帧更新大量实体位置
|
|
||||||
system.performVectorizedOperation((fields, indices) => {
|
|
||||||
const x = fields.get('x') as Float32Array;
|
|
||||||
const y = fields.get('y') as Float32Array;
|
|
||||||
const vx = fields.get('velocityX') as Float32Array;
|
|
||||||
const vy = fields.get('velocityY') as Float32Array;
|
|
||||||
|
|
||||||
// 向量化更新所有实体
|
|
||||||
for (let i = 0; i < indices.length; i++) {
|
|
||||||
const idx = indices[i];
|
|
||||||
x[idx] += vx[idx] * deltaTime;
|
|
||||||
y[idx] += vy[idx] * deltaTime;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **数值密集计算**
|
|
||||||
```typescript
|
|
||||||
@EnableSoA
|
|
||||||
class AIBrainComponent extends Component {
|
|
||||||
@Float32 public neuron1: number = 0;
|
|
||||||
@Float32 public neuron2: number = 0;
|
|
||||||
@Float32 public output: number = 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 不适用场景
|
|
||||||
|
|
||||||
### ❌ 不推荐使用SoA的场景
|
|
||||||
|
|
||||||
1. **小规模系统**
|
|
||||||
- SoA的开销大于收益
|
|
||||||
- 原始存储更简单高效
|
|
||||||
|
|
||||||
2. **随机访问为主**
|
|
||||||
```typescript
|
|
||||||
// 经常需要随机获取单个组件
|
|
||||||
const component = entityManager.getComponent(randomId, SomeComponent);
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **复杂对象为主的组件**
|
|
||||||
```typescript
|
|
||||||
// 大量复杂对象,序列化开销大
|
|
||||||
class UIComponent extends Component {
|
|
||||||
public domElement: HTMLElement;
|
|
||||||
public eventHandlers: Map<string, Function>;
|
|
||||||
public children: UIComponent[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **频繁增删实体**
|
|
||||||
- SoA在频繁增删时性能不如AoS
|
|
||||||
- 适合稳定的实体集合
|
|
||||||
|
|
||||||
## 装饰器使用指南
|
|
||||||
|
|
||||||
### 基础装饰器
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Component, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
@EnableSoA // 启用SoA优化
|
|
||||||
class GameComponent extends Component {
|
|
||||||
// 数值类型装饰器
|
|
||||||
@HighPrecision // 高精度数值,保持完整精度
|
|
||||||
public entityId: number = 0;
|
|
||||||
|
|
||||||
@Float64 // 64位浮点数 (8字节,高精度)
|
|
||||||
public precisePosition: number = 0;
|
|
||||||
|
|
||||||
@Int32 // 32位整数 (4字节,整数优化)
|
|
||||||
public health: number = 100;
|
|
||||||
|
|
||||||
// 普通数值 (默认Float32Array,4字节)
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
|
|
||||||
// 集合类型装饰器
|
|
||||||
@SerializeMap
|
|
||||||
public playerStats: Map<string, number> = new Map();
|
|
||||||
|
|
||||||
@SerializeSet
|
|
||||||
public achievements: Set<string> = new Set();
|
|
||||||
|
|
||||||
@SerializeArray
|
|
||||||
public inventory: any[] = [];
|
|
||||||
|
|
||||||
@DeepCopy
|
|
||||||
public config: any = { settings: {} };
|
|
||||||
|
|
||||||
// 未装饰的字段自动选择最优存储
|
|
||||||
public name: string = ''; // string[] 数组
|
|
||||||
public active: boolean = true; // Float32Array (0/1)
|
|
||||||
public metadata: any = null; // 复杂对象存储
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 装饰器选择指南
|
|
||||||
|
|
||||||
| 装饰器 | 用途 | 存储方式 | 开销 | 适用场景 |
|
|
||||||
|--------|------|----------|------|----------|
|
|
||||||
| `@HighPrecision` | 高精度数值 | 复杂对象 | 高 | ID、时间戳、大整数 |
|
|
||||||
| `@Float64` | 双精度浮点 | Float64Array | 中 | 精密计算 |
|
|
||||||
| `@Int32` | 32位整数 | Int32Array | 低 | 整数计数、枚举值 |
|
|
||||||
| `@SerializeMap` | Map序列化 | JSON字符串 | 高 | 配置映射、属性集合 |
|
|
||||||
| `@SerializeSet` | Set序列化 | JSON字符串 | 高 | 标签集合、ID集合 |
|
|
||||||
| `@SerializeArray` | Array序列化 | JSON字符串 | 中 | 列表数据、队列 |
|
|
||||||
| `@DeepCopy` | 深拷贝对象 | 复杂对象副本 | 高 | 嵌套配置、独立状态 |
|
|
||||||
|
|
||||||
## 性能对比
|
|
||||||
|
|
||||||
### 基准测试结果
|
|
||||||
|
|
||||||
```
|
|
||||||
测试场景: 2000个实体,包含位置、速度、生命值组件
|
|
||||||
|
|
||||||
创建性能:
|
|
||||||
- 原始存储: 12.45ms
|
|
||||||
- SoA存储: 15.72ms (慢26%)
|
|
||||||
|
|
||||||
随机访问性能:
|
|
||||||
- 原始存储: 8.33ms
|
|
||||||
- SoA存储: 14.20ms (慢70%)
|
|
||||||
|
|
||||||
批量更新性能:
|
|
||||||
- 原始存储: 25.67ms
|
|
||||||
- SoA存储: 8.91ms (快188%)
|
|
||||||
|
|
||||||
内存使用:
|
|
||||||
- 原始存储: ~45KB (对象开销)
|
|
||||||
- SoA存储: ~28KB (TypedArray优化)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 性能权衡总结
|
|
||||||
|
|
||||||
- **SoA优势**: 批量操作、内存效率、向量化计算
|
|
||||||
- **SoA劣势**: 随机访问、创建开销、复杂度增加
|
|
||||||
- **建议**: 大规模批量操作场景使用,小规模随机访问避免使用
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 合理的组件设计
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 好的设计:纯数值组件
|
|
||||||
@EnableSoA
|
|
||||||
class TransformComponent extends Component {
|
|
||||||
@Float64 public x: number = 0;
|
|
||||||
@Float64 public y: number = 0;
|
|
||||||
@Float32 public rotation: number = 0;
|
|
||||||
@Float32 public scaleX: number = 1;
|
|
||||||
@Float32 public scaleY: number = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ 不好的设计:混合复杂对象
|
|
||||||
@EnableSoA
|
|
||||||
class MixedComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public domElement: HTMLElement = null; // 复杂对象开销大
|
|
||||||
public callback: Function = null; // 无法序列化
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 批量操作优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 使用向量化操作
|
|
||||||
const storage = entityManager.getStorage(TransformComponent) as SoAStorage<TransformComponent>;
|
|
||||||
storage.performVectorizedOperation((fields, indices) => {
|
|
||||||
const x = fields.get('x') as Float64Array;
|
|
||||||
const y = fields.get('y') as Float64Array;
|
|
||||||
|
|
||||||
// 批量处理,利用缓存局部性
|
|
||||||
for (let i = 0; i < indices.length; i++) {
|
|
||||||
const idx = indices[i];
|
|
||||||
x[idx] += deltaX;
|
|
||||||
y[idx] += deltaY;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ❌ 避免逐个访问
|
|
||||||
for (const entity of entities) {
|
|
||||||
const transform = entity.getComponent(TransformComponent);
|
|
||||||
transform.x += deltaX;
|
|
||||||
transform.y += deltaY;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 组件分离策略
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 将频繁批量操作的数据分离
|
|
||||||
@EnableSoA
|
|
||||||
class PositionComponent extends Component {
|
|
||||||
@Float32 public x: number = 0;
|
|
||||||
@Float32 public y: number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复杂数据使用普通组件
|
|
||||||
class MetadataComponent extends Component {
|
|
||||||
public name: string = '';
|
|
||||||
public config: any = {};
|
|
||||||
public references: any[] = [];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 性能监控
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 监控SoA存储使用情况
|
|
||||||
const storage = entityManager.getStorage(MyComponent) as SoAStorage<MyComponent>;
|
|
||||||
const stats = storage.getStats();
|
|
||||||
|
|
||||||
console.log('SoA存储统计:', {
|
|
||||||
size: stats.size,
|
|
||||||
capacity: stats.capacity,
|
|
||||||
memoryUsage: stats.memoryUsage,
|
|
||||||
fragmentation: stats.fragmentation,
|
|
||||||
fieldStats: stats.fieldStats
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
### 常见问题
|
|
||||||
|
|
||||||
1. **精度丢失**
|
|
||||||
```typescript
|
|
||||||
// 问题:大整数精度丢失
|
|
||||||
public bigId: number = Number.MAX_SAFE_INTEGER;
|
|
||||||
|
|
||||||
// 解决:使用高精度装饰器
|
|
||||||
@HighPrecision
|
|
||||||
public bigId: number = Number.MAX_SAFE_INTEGER;
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **序列化失败**
|
|
||||||
```typescript
|
|
||||||
// 问题:循环引用导致序列化失败
|
|
||||||
@SerializeMap
|
|
||||||
public cyclicMap: Map<string, any> = new Map();
|
|
||||||
|
|
||||||
// 解决:避免循环引用或使用DeepCopy
|
|
||||||
@DeepCopy
|
|
||||||
public cyclicData: any = {};
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **性能反向优化**
|
|
||||||
```typescript
|
|
||||||
// 问题:小规模数据使用SoA
|
|
||||||
@EnableSoA // 只有10个实体,不需要SoA
|
|
||||||
class SmallComponent extends Component {}
|
|
||||||
|
|
||||||
// 解决:移除@EnableSoA装饰器
|
|
||||||
class SmallComponent extends Component {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 调试技巧
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 检查存储类型
|
|
||||||
const storage = entityManager.getStorage(MyComponent);
|
|
||||||
console.log('存储类型:', storage.constructor.name);
|
|
||||||
// 输出: 'SoAStorage' 或 'ComponentStorage'
|
|
||||||
|
|
||||||
// 检查字段存储方式
|
|
||||||
if (storage instanceof SoAStorage) {
|
|
||||||
const fieldArray = storage.getFieldArray('myField');
|
|
||||||
console.log('字段类型:', fieldArray?.constructor.name);
|
|
||||||
// 输出: 'Float32Array', 'Float64Array', 'Int32Array', 或 null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
SoA存储是一个强大的性能优化工具,但需要在合适的场景下使用:
|
|
||||||
|
|
||||||
- **适合**: 大规模、批量操作、数值密集的场景
|
|
||||||
- **不适合**: 小规模、随机访问、复杂对象为主的场景
|
|
||||||
- **关键**: 通过性能测试验证优化效果,避免过度优化
|
|
||||||
|
|
||||||
正确使用SoA存储可以显著提升ECS系统性能,但滥用会带来相反的效果。建议在实际项目中先进行基准测试,确认优化效果后再应用到生产环境。
|
|
||||||
@@ -1,662 +0,0 @@
|
|||||||
# 系统(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.all(HealthComponent));
|
|
||||||
// 或者使用链式语法
|
|
||||||
// super(Matcher.empty().all(HealthComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主要处理逻辑
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
// 直接使用传入的entities参数,已经是匹配的实体
|
|
||||||
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());
|
|
||||||
|
|
||||||
// 触发死亡事件
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.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 triggerVictory() {
|
|
||||||
console.log("游戏胜利!");
|
|
||||||
// 处理胜利逻辑
|
|
||||||
}
|
|
||||||
|
|
||||||
private triggerGameOver() {
|
|
||||||
console.log("游戏结束!");
|
|
||||||
// 处理游戏结束逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**适用场景:**
|
|
||||||
- 全局游戏逻辑系统
|
|
||||||
- 胜负判断系统
|
|
||||||
- UI更新系统
|
|
||||||
- 不依赖特定实体的处理
|
|
||||||
|
|
||||||
## AI系统示例
|
|
||||||
|
|
||||||
下面是一个完整的AI系统示例,展示EntitySystem的典型用法:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
enum AIState {
|
|
||||||
IDLE,
|
|
||||||
PATROL,
|
|
||||||
CHASE,
|
|
||||||
ATTACK
|
|
||||||
}
|
|
||||||
|
|
||||||
class AISystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
// 复杂匹配条件可以使用链式语法
|
|
||||||
super(Matcher.empty().all(AIComponent, PositionComponent));
|
|
||||||
// 或者使用简洁语法
|
|
||||||
// super(Matcher.all(AIComponent, PositionComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理所有匹配的实体
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理单个实体的逻辑(自定义方法)
|
|
||||||
private processEntity(entity: Entity) {
|
|
||||||
const ai = entity.getComponent(AIComponent);
|
|
||||||
const position = entity.getComponent(PositionComponent);
|
|
||||||
|
|
||||||
switch (ai.state) {
|
|
||||||
case AIState.IDLE:
|
|
||||||
this.processIdle(entity, ai);
|
|
||||||
break;
|
|
||||||
case AIState.PATROL:
|
|
||||||
this.processPatrol(entity, ai, position);
|
|
||||||
break;
|
|
||||||
case AIState.CHASE:
|
|
||||||
this.processChase(entity, ai, position);
|
|
||||||
break;
|
|
||||||
case AIState.ATTACK:
|
|
||||||
this.processAttack(entity, ai);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(2.0, Matcher.all(SpawnerComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 间隔执行的逻辑(重写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;
|
|
||||||
|
|
||||||
// 发送生成事件
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.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();
|
|
||||||
|
|
||||||
// 监听游戏事件(使用EntityManager的事件系统)
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.on('enemy:killed', this.onEnemyKilled, { context: this });
|
|
||||||
eventBus.on('item:collected', this.onItemCollected, { context: this });
|
|
||||||
eventBus.on('combo:broken', this.onComboBroken, { context: this });
|
|
||||||
}
|
|
||||||
|
|
||||||
// PassiveSystem被移除时清理
|
|
||||||
destroy() {
|
|
||||||
// 事件监听会在系统销毁时自动清理
|
|
||||||
// 如需手动清理,可以保存listenerId并调用eventBus.off()
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 发送分数更新事件
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.emit('score:updated', {
|
|
||||||
score: this.score,
|
|
||||||
points: points,
|
|
||||||
multiplier: this.multiplier,
|
|
||||||
combo: this.combo
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**适用场景:**
|
|
||||||
- 分数统计系统
|
|
||||||
- 音效播放系统
|
|
||||||
- UI更新系统
|
|
||||||
- 成就系统
|
|
||||||
|
|
||||||
## 系统生命周期方法
|
|
||||||
|
|
||||||
系统提供了多个生命周期方法,可以根据需要重写:
|
|
||||||
|
|
||||||
### 重要的生命周期方法
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class ExampleSystem extends EntitySystem {
|
|
||||||
/**
|
|
||||||
* 系统初始化回调 - 系统被添加到场景时调用
|
|
||||||
* 用于设置事件监听器、初始化资源等
|
|
||||||
* 注意:不要重写initialize()方法,而是重写onInitialize()
|
|
||||||
*/
|
|
||||||
protected onInitialize() {
|
|
||||||
// 设置事件监听
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.on('someEvent', this.handleEvent, { context: this });
|
|
||||||
|
|
||||||
console.log('系统已初始化');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧处理开始前调用
|
|
||||||
*/
|
|
||||||
protected onBegin() {
|
|
||||||
// 预处理逻辑,如重置计数器
|
|
||||||
this.frameCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主要处理逻辑 - 每帧调用
|
|
||||||
* @param entities 符合条件的实体列表
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]) {
|
|
||||||
for (const entity of entities) {
|
|
||||||
// 处理每个实体
|
|
||||||
this.processEntity(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 后期处理 - 在process之后调用
|
|
||||||
* @param entities 符合条件的实体列表
|
|
||||||
*/
|
|
||||||
protected lateProcess(entities: Entity[]) {
|
|
||||||
// 后期处理逻辑,如碰撞检测后的响应
|
|
||||||
this.handlePostProcessing();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧处理结束后调用
|
|
||||||
*/
|
|
||||||
protected onEnd() {
|
|
||||||
// 后处理逻辑,如统计数据更新
|
|
||||||
this.updateStatistics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生命周期执行顺序
|
|
||||||
|
|
||||||
系统的生命周期方法按以下顺序执行:
|
|
||||||
|
|
||||||
1. **initialize()** - 系统被添加到场景时执行一次(框架调用)
|
|
||||||
- **onInitialize()** - 用户可重写的初始化回调
|
|
||||||
2. 每帧循环:
|
|
||||||
- **onBegin()** - 帧开始前(用户可重写)
|
|
||||||
- **process(entities)** - 主要处理逻辑(用户必须实现)
|
|
||||||
- **lateProcess(entities)** - 后期处理(用户可重写)
|
|
||||||
- **onEnd()** - 帧结束后(用户可重写)
|
|
||||||
|
|
||||||
## 系统管理和注册
|
|
||||||
|
|
||||||
### 在场景中添加系统
|
|
||||||
|
|
||||||
```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.setScene(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) {
|
|
||||||
// 发送碰撞事件,让其他系统响应
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.emit('collision:detected', {
|
|
||||||
entity1: collider1,
|
|
||||||
entity2: collider2,
|
|
||||||
collisionPoint: point
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HealthSystem extends PassiveSystem {
|
|
||||||
initialize() {
|
|
||||||
super.initialize();
|
|
||||||
|
|
||||||
// 监听碰撞事件
|
|
||||||
const eventBus = this.scene.entityManager.eventBus;
|
|
||||||
eventBus.on('collision:detected', this.onCollision, { context: 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** - 不依赖特定实体的全局处理(游戏状态管理、全局逻辑)
|
|
||||||
- **IntervalSystem** - 不需要每帧执行的逻辑(生成器、自动保存)
|
|
||||||
- **PassiveSystem** - 事件响应系统(分数、音效、UI更新)
|
|
||||||
|
|
||||||
### Q: 系统可以访问其他系统吗?
|
|
||||||
|
|
||||||
A: 不建议直接访问。推荐使用事件系统进行系统间通信,保持松耦合。
|
|
||||||
|
|
||||||
### Q: 如何调试系统性能?
|
|
||||||
|
|
||||||
A: 使用框架内置的性能监控:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const monitor = PerformanceMonitor.instance;
|
|
||||||
monitor.startFrame('MovementSystem');
|
|
||||||
// 系统逻辑...
|
|
||||||
monitor.endFrame('MovementSystem');
|
|
||||||
|
|
||||||
// 查看性能报告
|
|
||||||
console.log(monitor.getReport());
|
|
||||||
```
|
|
||||||
|
|
||||||
通过合理使用这些系统类型,你可以构建出高性能、易维护的游戏逻辑!
|
|
||||||
@@ -1,653 +0,0 @@
|
|||||||
# 定时器系统使用指南
|
|
||||||
|
|
||||||
定时器系统是游戏开发中的重要工具,用于处理延迟执行、重复任务、倒计时等功能。本指南详细介绍如何使用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("⏰ 时间到!游戏结束");
|
|
||||||
|
|
||||||
// 触发游戏结束(需要在实际使用中获取EntityManager实例)
|
|
||||||
// 示例:entityManager.eventBus.emit('level:timeout');
|
|
||||||
console.log('触发关卡超时事件');
|
|
||||||
}
|
|
||||||
|
|
||||||
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} 分`);
|
|
||||||
// 触发时间奖励事件(需要在实际使用中获取EntityManager实例)
|
|
||||||
// 示例:entityManager.eventBus.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 = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
定时器是游戏开发中非常有用的工具,合理使用可以让你的游戏逻辑更加优雅和高效!
|
|
||||||
@@ -1,600 +0,0 @@
|
|||||||
# 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框架在不同场景下都发挥最佳性能。
|
|
||||||
Submodule examples/lawn-mower-demo updated: 8f217c95db...5a4976b192
2508
package-lock.json
generated
2508
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -48,11 +48,17 @@
|
|||||||
"publish:network-server": "cd packages/network-server && npm run publish:npm",
|
"publish:network-server": "cd packages/network-server && npm run publish:npm",
|
||||||
"publish:network-server:patch": "cd packages/network-server && npm run publish:patch",
|
"publish:network-server:patch": "cd packages/network-server && npm run publish:patch",
|
||||||
"publish": "lerna publish",
|
"publish": "lerna publish",
|
||||||
"version": "lerna version"
|
"version": "lerna version",
|
||||||
|
"docs:dev": "vitepress dev docs",
|
||||||
|
"docs:build": "npm run docs:api && vitepress build docs",
|
||||||
|
"docs:preview": "vitepress preview docs",
|
||||||
|
"docs:api": "typedoc",
|
||||||
|
"docs:api:watch": "typedoc --watch"
|
||||||
},
|
},
|
||||||
"author": "yhh",
|
"author": "yhh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@iconify/json": "^2.2.388",
|
||||||
"@rollup/plugin-commonjs": "^28.0.3",
|
"@rollup/plugin-commonjs": "^28.0.3",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
@@ -66,7 +72,11 @@
|
|||||||
"rollup-plugin-dts": "^6.2.1",
|
"rollup-plugin-dts": "^6.2.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"ts-jest": "^29.4.0",
|
"ts-jest": "^29.4.0",
|
||||||
"typescript": "^5.8.3"
|
"typedoc": "^0.28.13",
|
||||||
|
"typedoc-plugin-markdown": "^4.9.0",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"unplugin-icons": "^22.3.0",
|
||||||
|
"vitepress": "^1.6.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
63
typedoc.json
Normal file
63
typedoc.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"entryPoints": ["./packages/core/src/index.ts"],
|
||||||
|
"out": "./docs/api",
|
||||||
|
"plugin": ["typedoc-plugin-markdown"],
|
||||||
|
"readme": "none",
|
||||||
|
"excludePrivate": true,
|
||||||
|
"excludeProtected": false,
|
||||||
|
"excludeInternal": false,
|
||||||
|
"includeVersion": true,
|
||||||
|
"sort": ["source-order"],
|
||||||
|
"kindSortOrder": [
|
||||||
|
"Document",
|
||||||
|
"Project",
|
||||||
|
"Module",
|
||||||
|
"Namespace",
|
||||||
|
"Enum",
|
||||||
|
"EnumMember",
|
||||||
|
"Class",
|
||||||
|
"Interface",
|
||||||
|
"TypeAlias",
|
||||||
|
"Constructor",
|
||||||
|
"Property",
|
||||||
|
"Variable",
|
||||||
|
"Function",
|
||||||
|
"Accessor",
|
||||||
|
"Method",
|
||||||
|
"Parameter",
|
||||||
|
"TypeParameter",
|
||||||
|
"TypeLiteral",
|
||||||
|
"CallSignature",
|
||||||
|
"ConstructorSignature",
|
||||||
|
"IndexSignature",
|
||||||
|
"GetSignature",
|
||||||
|
"SetSignature"
|
||||||
|
],
|
||||||
|
"categorizeByGroup": false,
|
||||||
|
"defaultCategory": "其他",
|
||||||
|
"categoryOrder": [
|
||||||
|
"核心",
|
||||||
|
"ECS",
|
||||||
|
"组件",
|
||||||
|
"系统",
|
||||||
|
"工具",
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"searchInComments": true,
|
||||||
|
"cleanOutputDir": true,
|
||||||
|
"titleLink": "/",
|
||||||
|
"navigationLinks": {
|
||||||
|
"首页": "/",
|
||||||
|
"指南": "/guide/",
|
||||||
|
"示例": "/examples/"
|
||||||
|
},
|
||||||
|
"sidebarLinks": {
|
||||||
|
"GitHub": "https://github.com/esengine/ecs-framework",
|
||||||
|
"NPM": "https://www.npmjs.com/package/@esengine/ecs-framework"
|
||||||
|
},
|
||||||
|
"hideGenerator": true,
|
||||||
|
"githubPages": false,
|
||||||
|
"disableSources": false,
|
||||||
|
"name": "ECS Framework API",
|
||||||
|
"skipErrorChecking": true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user