更新v2.2.0文档
This commit is contained in:
@@ -80,6 +80,14 @@ export default defineConfig({
|
|||||||
{ text: '日志系统 (Logger)', link: '/guide/logging' }
|
{ text: '日志系统 (Logger)', link: '/guide/logging' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: '高级特性',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '服务容器 (Service Container)', link: '/guide/service-container' },
|
||||||
|
{ text: '插件系统 (Plugin System)', link: '/guide/plugin-system' }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: '平台适配器',
|
text: '平台适配器',
|
||||||
link: '/guide/platform-adapter',
|
link: '/guide/platform-adapter',
|
||||||
|
|||||||
@@ -63,14 +63,14 @@ class Health extends Component {
|
|||||||
- 框架能正确管理组件注册
|
- 框架能正确管理组件注册
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 正确的用法
|
// 正确的用法
|
||||||
@ECSComponent('Velocity')
|
@ECSComponent('Velocity')
|
||||||
class Velocity extends Component {
|
class Velocity extends Component {
|
||||||
dx: number = 0;
|
dx: number = 0;
|
||||||
dy: number = 0;
|
dy: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ 错误的用法 - 没有装饰器
|
// 错误的用法 - 没有装饰器
|
||||||
class BadComponent extends Component {
|
class BadComponent extends Component {
|
||||||
// 这样定义的组件可能在生产环境出现问题
|
// 这样定义的组件可能在生产环境出现问题
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ class ExampleComponent extends Component {
|
|||||||
* 用于初始化资源、建立引用等
|
* 用于初始化资源、建立引用等
|
||||||
*/
|
*/
|
||||||
onAddedToEntity(): void {
|
onAddedToEntity(): void {
|
||||||
console.log(`组件 ${this.constructor.name} 被添加到实体 ${this.entity.name}`);
|
console.log(`组件 ${this.constructor.name} 已添加,实体ID: ${this.entityId}`);
|
||||||
this.resource = new SomeResource();
|
this.resource = new SomeResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ class ExampleComponent extends Component {
|
|||||||
* 用于清理资源、断开引用等
|
* 用于清理资源、断开引用等
|
||||||
*/
|
*/
|
||||||
onRemovedFromEntity(): void {
|
onRemovedFromEntity(): void {
|
||||||
console.log(`组件 ${this.constructor.name} 从实体 ${this.entity.name} 移除`);
|
console.log(`组件 ${this.constructor.name} 已移除`);
|
||||||
if (this.resource) {
|
if (this.resource) {
|
||||||
this.resource.cleanup();
|
this.resource.cleanup();
|
||||||
this.resource = null;
|
this.resource = null;
|
||||||
@@ -108,30 +108,58 @@ class ExampleComponent extends Component {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 访问实体
|
## 组件与实体的关系
|
||||||
|
|
||||||
组件可以通过 `this.entity` 访问其所属的实体:
|
组件存储了所属实体的ID (`entityId`),而不是直接引用实体对象。这是ECS数据导向设计的体现,避免了循环引用。
|
||||||
|
|
||||||
|
在实际使用中,**应该在 System 中处理实体和组件的交互**,而不是在组件内部:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@ECSComponent('Damage')
|
@ECSComponent('Health')
|
||||||
class Damage extends Component {
|
class Health extends Component {
|
||||||
damage: number;
|
current: number;
|
||||||
|
max: number;
|
||||||
|
|
||||||
constructor(damage: number) {
|
constructor(max: number = 100) {
|
||||||
super();
|
super();
|
||||||
this.damage = damage;
|
this.max = max;
|
||||||
|
this.current = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在组件方法中访问实体和其他组件
|
isDead(): boolean {
|
||||||
applyDamage(): void {
|
return this.current <= 0;
|
||||||
const health = this.entity.getComponent(Health);
|
}
|
||||||
if (health) {
|
}
|
||||||
health.takeDamage(this.damage);
|
|
||||||
|
@ECSComponent('Damage')
|
||||||
|
class Damage extends Component {
|
||||||
|
value: number;
|
||||||
|
|
||||||
|
constructor(value: number) {
|
||||||
|
super();
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推荐:在 System 中处理逻辑
|
||||||
|
class DamageSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(new Matcher().all(Health, Damage));
|
||||||
|
}
|
||||||
|
|
||||||
|
process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const health = entity.getComponent(Health)!;
|
||||||
|
const damage = entity.getComponent(Damage)!;
|
||||||
|
|
||||||
|
health.current -= damage.value;
|
||||||
|
|
||||||
// 如果生命值为0,销毁实体
|
|
||||||
if (health.isDead()) {
|
if (health.isDead()) {
|
||||||
this.entity.destroy();
|
entity.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用伤害后移除 Damage 组件
|
||||||
|
entity.removeComponent(damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,9 +174,27 @@ class Damage extends Component {
|
|||||||
class ExampleComponent extends Component {
|
class ExampleComponent extends Component {
|
||||||
someData: string = "example";
|
someData: string = "example";
|
||||||
|
|
||||||
showComponentInfo(): void {
|
onAddedToEntity(): void {
|
||||||
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
|
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
|
||||||
console.log(`所属实体: ${this.entity.name}`); // 所属实体引用
|
console.log(`所属实体ID: ${this.entityId}`); // 所属实体的ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要访问实体对象,应该在 System 中进行:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ExampleSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(new Matcher().all(ExampleComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const comp = entity.getComponent(ExampleComponent)!;
|
||||||
|
console.log(`实体名称: ${entity.name}`);
|
||||||
|
console.log(`组件数据: ${comp.someData}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -245,7 +291,7 @@ class WeaponConfig extends Component {
|
|||||||
### 1. 保持组件简单
|
### 1. 保持组件简单
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 好的组件设计 - 单一职责
|
// 好的组件设计 - 单一职责
|
||||||
@ECSComponent('Position')
|
@ECSComponent('Position')
|
||||||
class Position extends Component {
|
class Position extends Component {
|
||||||
x: number = 0;
|
x: number = 0;
|
||||||
@@ -258,7 +304,7 @@ class Velocity extends Component {
|
|||||||
dy: number = 0;
|
dy: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ 避免的组件设计 - 职责过多
|
// 避免的组件设计 - 职责过多
|
||||||
@ECSComponent('GameObject')
|
@ECSComponent('GameObject')
|
||||||
class GameObject extends Component {
|
class GameObject extends Component {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -330,16 +376,11 @@ class Inventory extends Component {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 避免在组件中存储实体引用
|
### 4. 引用其他实体
|
||||||
|
|
||||||
|
当组件需要关联其他实体时(如父子关系、跟随目标等),**推荐方式是存储实体ID**,然后在 System 中查找:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 避免:在组件中存储其他实体的引用
|
|
||||||
@ECSComponent('BadFollower')
|
|
||||||
class BadFollower extends Component {
|
|
||||||
target: Entity; // 直接引用可能导致内存泄漏
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 推荐:存储实体ID,通过场景查找
|
|
||||||
@ECSComponent('Follower')
|
@ECSComponent('Follower')
|
||||||
class Follower extends Component {
|
class Follower extends Component {
|
||||||
targetId: number;
|
targetId: number;
|
||||||
@@ -349,11 +390,269 @@ class Follower extends Component {
|
|||||||
super();
|
super();
|
||||||
this.targetId = targetId;
|
this.targetId = targetId;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getTarget(): Entity | null {
|
// 在 System 中查找目标实体并处理逻辑
|
||||||
return this.entity.scene?.findEntityById(this.targetId) || null;
|
class FollowerSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(new Matcher().all(Follower, Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
process(entities: readonly Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const follower = entity.getComponent(Follower)!;
|
||||||
|
const position = entity.getComponent(Position)!;
|
||||||
|
|
||||||
|
// 通过场景查找目标实体
|
||||||
|
const target = entity.scene?.findEntityById(follower.targetId);
|
||||||
|
if (target) {
|
||||||
|
const targetPos = target.getComponent(Position);
|
||||||
|
if (targetPos) {
|
||||||
|
// 跟随逻辑
|
||||||
|
const dx = targetPos.x - position.x;
|
||||||
|
const dy = targetPos.y - position.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance > follower.followDistance) {
|
||||||
|
// 移动靠近目标
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
这种方式的优势:
|
||||||
|
- 组件保持简单,只存储基本数据类型
|
||||||
|
- 符合数据导向设计
|
||||||
|
- 在 System 中统一处理查找和逻辑
|
||||||
|
- 易于理解和维护
|
||||||
|
|
||||||
|
**避免在组件中直接存储实体引用**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 错误示范:直接存储实体引用
|
||||||
|
@ECSComponent('BadFollower')
|
||||||
|
class BadFollower extends Component {
|
||||||
|
target: Entity; // 实体销毁后仍持有引用,可能导致内存泄漏
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级特性
|
||||||
|
|
||||||
|
### EntityRef 装饰器 - 自动引用追踪
|
||||||
|
|
||||||
|
框架提供了 `@EntityRef` 装饰器用于**特殊场景**下安全地存储实体引用。这是一个高级特性,一般情况下推荐使用存储ID的方式。
|
||||||
|
|
||||||
|
#### 什么时候需要 EntityRef?
|
||||||
|
|
||||||
|
在以下场景中,`@EntityRef` 可以简化代码:
|
||||||
|
|
||||||
|
1. **父子关系**: 需要在组件中直接访问父实体或子实体
|
||||||
|
2. **复杂关联**: 实体之间有多个引用关系
|
||||||
|
3. **频繁访问**: 需要在多处访问引用的实体,使用ID查找会有性能开销
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
`@EntityRef` 装饰器通过 **ReferenceTracker** 自动追踪引用关系:
|
||||||
|
|
||||||
|
- 当被引用的实体销毁时,所有指向它的 `@EntityRef` 属性自动设为 `null`
|
||||||
|
- 防止跨场景引用(会输出警告并拒绝设置)
|
||||||
|
- 防止引用已销毁的实体(会输出警告并设为 `null`)
|
||||||
|
- 使用 WeakRef 避免内存泄漏(自动GC支持)
|
||||||
|
- 组件移除时自动清理引用注册
|
||||||
|
|
||||||
|
#### 基本用法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component, ECSComponent, EntityRef, Entity } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@ECSComponent('Parent')
|
||||||
|
class ParentComponent extends Component {
|
||||||
|
@EntityRef()
|
||||||
|
parent: Entity | null = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
const scene = new Scene();
|
||||||
|
const parent = scene.createEntity('Parent');
|
||||||
|
const child = scene.createEntity('Child');
|
||||||
|
|
||||||
|
const comp = child.addComponent(new ParentComponent());
|
||||||
|
comp.parent = parent;
|
||||||
|
|
||||||
|
console.log(comp.parent); // Entity { name: 'Parent' }
|
||||||
|
|
||||||
|
// 当 parent 被销毁时,comp.parent 自动变为 null
|
||||||
|
parent.destroy();
|
||||||
|
console.log(comp.parent); // null
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 多个引用属性
|
||||||
|
|
||||||
|
一个组件可以有多个 `@EntityRef` 属性:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@ECSComponent('Combat')
|
||||||
|
class CombatComponent extends Component {
|
||||||
|
@EntityRef()
|
||||||
|
target: Entity | null = null;
|
||||||
|
|
||||||
|
@EntityRef()
|
||||||
|
ally: Entity | null = null;
|
||||||
|
|
||||||
|
@EntityRef()
|
||||||
|
lastAttacker: Entity | null = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
const player = scene.createEntity('Player');
|
||||||
|
const enemy = scene.createEntity('Enemy');
|
||||||
|
const npc = scene.createEntity('NPC');
|
||||||
|
|
||||||
|
const combat = player.addComponent(new CombatComponent());
|
||||||
|
combat.target = enemy;
|
||||||
|
combat.ally = npc;
|
||||||
|
|
||||||
|
// enemy 销毁后,只有 target 变为 null,ally 仍然有效
|
||||||
|
enemy.destroy();
|
||||||
|
console.log(combat.target); // null
|
||||||
|
console.log(combat.ally); // Entity { name: 'NPC' }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 安全检查
|
||||||
|
|
||||||
|
`@EntityRef` 提供了多重安全检查:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const scene1 = new Scene();
|
||||||
|
const scene2 = new Scene();
|
||||||
|
|
||||||
|
const entity1 = scene1.createEntity('Entity1');
|
||||||
|
const entity2 = scene2.createEntity('Entity2');
|
||||||
|
|
||||||
|
const comp = entity1.addComponent(new ParentComponent());
|
||||||
|
|
||||||
|
// 跨场景引用会失败
|
||||||
|
comp.parent = entity2; // 输出错误日志,comp.parent 为 null
|
||||||
|
console.log(comp.parent); // null
|
||||||
|
|
||||||
|
// 引用已销毁的实体会失败
|
||||||
|
const entity3 = scene1.createEntity('Entity3');
|
||||||
|
entity3.destroy();
|
||||||
|
comp.parent = entity3; // 输出警告日志,comp.parent 为 null
|
||||||
|
console.log(comp.parent); // null
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 实现原理
|
||||||
|
|
||||||
|
`@EntityRef` 使用以下机制实现自动引用追踪:
|
||||||
|
|
||||||
|
1. **ReferenceTracker**: Scene 持有一个引用追踪器,记录所有实体引用关系
|
||||||
|
2. **WeakRef**: 使用弱引用存储组件,避免循环引用导致内存泄漏
|
||||||
|
3. **属性拦截**: 通过 `Object.defineProperty` 拦截 getter/setter
|
||||||
|
4. **自动清理**: 实体销毁时,ReferenceTracker 遍历所有引用并设为 null
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 简化的实现原理
|
||||||
|
class ReferenceTracker {
|
||||||
|
// entityId -> 引用该实体的所有组件记录
|
||||||
|
private _references: Map<number, Set<{ component: WeakRef<Component>, propertyKey: string }>>;
|
||||||
|
|
||||||
|
// 实体销毁时调用
|
||||||
|
clearReferencesTo(entityId: number): void {
|
||||||
|
const records = this._references.get(entityId);
|
||||||
|
if (records) {
|
||||||
|
for (const record of records) {
|
||||||
|
const component = record.component.deref();
|
||||||
|
if (component) {
|
||||||
|
// 将组件的引用属性设为 null
|
||||||
|
(component as any)[record.propertyKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._references.delete(entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 性能考虑
|
||||||
|
|
||||||
|
`@EntityRef` 会带来一些性能开销:
|
||||||
|
|
||||||
|
- **写入开销**: 每次设置引用时需要更新 ReferenceTracker
|
||||||
|
- **内存开销**: ReferenceTracker 需要维护引用映射表
|
||||||
|
- **销毁开销**: 实体销毁时需要遍历所有引用并清理
|
||||||
|
|
||||||
|
对于大多数场景,这些开销是可以接受的。但如果有**大量实体和频繁的引用变更**,存储ID可能更高效。
|
||||||
|
|
||||||
|
#### 最佳实践
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 推荐:适合使用 @EntityRef 的场景 - 父子关系
|
||||||
|
@ECSComponent('Transform')
|
||||||
|
class Transform extends Component {
|
||||||
|
@EntityRef()
|
||||||
|
parent: Entity | null = null;
|
||||||
|
|
||||||
|
position: { x: number, y: number } = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
// 可以直接访问父实体的组件
|
||||||
|
getWorldPosition(): { x: number, y: number } {
|
||||||
|
if (!this.parent) {
|
||||||
|
return { ...this.position };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentTransform = this.parent.getComponent(Transform);
|
||||||
|
if (parentTransform) {
|
||||||
|
const parentPos = parentTransform.getWorldPosition();
|
||||||
|
return {
|
||||||
|
x: parentPos.x + this.position.x,
|
||||||
|
y: parentPos.y + this.position.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...this.position };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不推荐:不适合使用 @EntityRef 的场景 - 大量动态目标
|
||||||
|
@ECSComponent('AITarget')
|
||||||
|
class AITarget extends Component {
|
||||||
|
@EntityRef()
|
||||||
|
target: Entity | null = null; // 如果目标频繁变化,用ID更好
|
||||||
|
|
||||||
|
updateCooldown: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推荐:这种场景用ID更好
|
||||||
|
@ECSComponent('AITarget')
|
||||||
|
class AITargetBetter extends Component {
|
||||||
|
targetId: number | null = null; // 存储ID
|
||||||
|
updateCooldown: number = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 调试支持
|
||||||
|
|
||||||
|
ReferenceTracker 提供了调试接口:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 查看某个实体被哪些组件引用
|
||||||
|
const references = scene.referenceTracker.getReferencesTo(entity.id);
|
||||||
|
console.log(`实体 ${entity.name} 被 ${references.length} 个组件引用`);
|
||||||
|
|
||||||
|
// 获取完整的调试信息
|
||||||
|
const debugInfo = scene.referenceTracker.getDebugInfo();
|
||||||
|
console.log(debugInfo);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 总结
|
||||||
|
|
||||||
|
- **推荐做法**: 大部分情况使用存储ID + System查找的方式
|
||||||
|
- **EntityRef 适用场景**: 父子关系、复杂关联、组件内需要直接访问引用实体的场景
|
||||||
|
- **核心优势**: 自动清理、防止悬空引用、代码更简洁
|
||||||
|
- **注意事项**: 有性能开销,不适合大量动态引用的场景
|
||||||
|
|
||||||
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。
|
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。
|
||||||
@@ -30,3 +30,11 @@
|
|||||||
|
|
||||||
### [平台适配器 (Platform Adapter)](./platform-adapter.md)
|
### [平台适配器 (Platform Adapter)](./platform-adapter.md)
|
||||||
了解如何为不同平台实现和注册平台适配器,支持浏览器、小游戏、Node.js等环境。
|
了解如何为不同平台实现和注册平台适配器,支持浏览器、小游戏、Node.js等环境。
|
||||||
|
|
||||||
|
## 高级特性
|
||||||
|
|
||||||
|
### [服务容器 (Service Container)](./service-container.md)
|
||||||
|
掌握依赖注入和服务管理,实现松耦合的架构设计。
|
||||||
|
|
||||||
|
### [插件系统 (Plugin System)](./plugin-system.md)
|
||||||
|
学习如何开发和使用插件,扩展框架功能,实现功能模块化。
|
||||||
643
docs/guide/plugin-system.md
Normal file
643
docs/guide/plugin-system.md
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
# 插件系统
|
||||||
|
|
||||||
|
插件系统允许你以模块化的方式扩展 ECS Framework 的功能。通过插件,你可以封装特定功能(如网络同步、物理引擎、调试工具等),并在多个项目中复用。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
### 什么是插件
|
||||||
|
|
||||||
|
插件是实现了 `IPlugin` 接口的类,可以在运行时动态安装到框架中。插件可以:
|
||||||
|
|
||||||
|
- 注册自定义服务到服务容器
|
||||||
|
- 添加系统到场景
|
||||||
|
- 注册自定义组件
|
||||||
|
- 扩展框架功能
|
||||||
|
|
||||||
|
### 插件的优势
|
||||||
|
|
||||||
|
- **模块化**: 将功能封装为独立模块,提高代码可维护性
|
||||||
|
- **可复用**: 同一个插件可以在多个项目中使用
|
||||||
|
- **解耦**: 核心框架与扩展功能分离
|
||||||
|
- **热插拔**: 运行时动态安装和卸载插件
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 创建第一个插件
|
||||||
|
|
||||||
|
创建一个简单的调试插件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class DebugPlugin implements IPlugin {
|
||||||
|
readonly name = 'debug-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
console.log('Debug plugin installed');
|
||||||
|
|
||||||
|
// 可以在这里注册服务、添加系统等
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
console.log('Debug plugin uninstalled');
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安装插件
|
||||||
|
|
||||||
|
使用 `Core.installPlugin()` 安装插件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 初始化Core
|
||||||
|
Core.create({ debug: true });
|
||||||
|
|
||||||
|
// 安装插件
|
||||||
|
await Core.installPlugin(new DebugPlugin());
|
||||||
|
|
||||||
|
// 检查插件是否已安装
|
||||||
|
if (Core.isPluginInstalled('debug-plugin')) {
|
||||||
|
console.log('Debug plugin is running');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 卸载插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 卸载插件
|
||||||
|
await Core.uninstallPlugin('debug-plugin');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取插件实例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取已安装的插件
|
||||||
|
const plugin = Core.getPlugin('debug-plugin');
|
||||||
|
if (plugin) {
|
||||||
|
console.log(`Plugin version: ${plugin.version}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 插件开发
|
||||||
|
|
||||||
|
### IPlugin 接口
|
||||||
|
|
||||||
|
所有插件必须实现 `IPlugin` 接口:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface IPlugin {
|
||||||
|
// 插件唯一名称
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
// 插件版本(建议遵循semver规范)
|
||||||
|
readonly version: string;
|
||||||
|
|
||||||
|
// 依赖的其他插件(可选)
|
||||||
|
readonly dependencies?: readonly string[];
|
||||||
|
|
||||||
|
// 安装插件时调用
|
||||||
|
install(core: Core, services: ServiceContainer): void | Promise<void>;
|
||||||
|
|
||||||
|
// 卸载插件时调用
|
||||||
|
uninstall(): void | Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件生命周期
|
||||||
|
|
||||||
|
#### install 方法
|
||||||
|
|
||||||
|
在插件安装时调用,用于初始化插件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyPlugin implements IPlugin {
|
||||||
|
readonly name = 'my-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
// 1. 注册服务
|
||||||
|
services.registerSingleton(MyService);
|
||||||
|
|
||||||
|
// 2. 访问当前场景
|
||||||
|
const scene = core.scene;
|
||||||
|
if (scene) {
|
||||||
|
// 3. 添加系统
|
||||||
|
scene.addSystem(new MySystem());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 其他初始化逻辑
|
||||||
|
console.log('Plugin initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 清理逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### uninstall 方法
|
||||||
|
|
||||||
|
在插件卸载时调用,用于清理资源:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyPlugin implements IPlugin {
|
||||||
|
readonly name = 'my-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
private myService?: MyService;
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
this.myService = new MyService();
|
||||||
|
services.registerInstance(MyService, this.myService);
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 清理服务
|
||||||
|
if (this.myService) {
|
||||||
|
this.myService.dispose();
|
||||||
|
this.myService = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除事件监听器
|
||||||
|
// 释放其他资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步插件
|
||||||
|
|
||||||
|
插件的 `install` 和 `uninstall` 方法都支持异步:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class AsyncPlugin implements IPlugin {
|
||||||
|
readonly name = 'async-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
async install(core: Core, services: ServiceContainer): Promise<void> {
|
||||||
|
// 异步加载资源
|
||||||
|
const config = await fetch('/plugin-config.json').then(r => r.json());
|
||||||
|
|
||||||
|
// 使用加载的配置初始化服务
|
||||||
|
const service = new MyService(config);
|
||||||
|
services.registerInstance(MyService, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall(): Promise<void> {
|
||||||
|
// 异步清理
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveState() {
|
||||||
|
// 保存插件状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
await Core.installPlugin(new AsyncPlugin());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注册服务
|
||||||
|
|
||||||
|
插件可以向服务容器注册自己的服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IService } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class NetworkService implements IService {
|
||||||
|
connect(url: string) {
|
||||||
|
console.log(`Connecting to ${url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
console.log('Network service disposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkPlugin implements IPlugin {
|
||||||
|
readonly name = 'network-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
// 注册网络服务
|
||||||
|
services.registerSingleton(NetworkService);
|
||||||
|
|
||||||
|
// 解析并使用服务
|
||||||
|
const network = services.resolve(NetworkService);
|
||||||
|
network.connect('ws://localhost:8080');
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 服务容器会自动调用服务的dispose方法
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加系统
|
||||||
|
|
||||||
|
插件可以向场景添加自定义系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class PhysicsSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(PhysicsBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 物理模拟逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhysicsPlugin implements IPlugin {
|
||||||
|
readonly name = 'physics-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
private physicsSystem?: PhysicsSystem;
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
const scene = core.scene;
|
||||||
|
if (scene) {
|
||||||
|
this.physicsSystem = new PhysicsSystem();
|
||||||
|
scene.addSystem(this.physicsSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 移除系统
|
||||||
|
if (this.physicsSystem) {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (scene) {
|
||||||
|
scene.removeSystem(this.physicsSystem);
|
||||||
|
}
|
||||||
|
this.physicsSystem = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 依赖管理
|
||||||
|
|
||||||
|
### 声明依赖
|
||||||
|
|
||||||
|
插件可以声明对其他插件的依赖:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class AdvancedPhysicsPlugin implements IPlugin {
|
||||||
|
readonly name = 'advanced-physics';
|
||||||
|
readonly version = '2.0.0';
|
||||||
|
|
||||||
|
// 声明依赖基础物理插件
|
||||||
|
readonly dependencies = ['physics-plugin'] as const;
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
// 可以安全地使用physics-plugin提供的服务
|
||||||
|
const physicsService = services.resolve(PhysicsService);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 清理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依赖检查
|
||||||
|
|
||||||
|
框架会自动检查依赖关系,如果依赖未满足会抛出错误:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 错误:physics-plugin 未安装
|
||||||
|
try {
|
||||||
|
await Core.installPlugin(new AdvancedPhysicsPlugin());
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error); // Plugin advanced-physics has unmet dependencies: physics-plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确:先安装依赖
|
||||||
|
await Core.installPlugin(new PhysicsPlugin());
|
||||||
|
await Core.installPlugin(new AdvancedPhysicsPlugin());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 卸载顺序
|
||||||
|
|
||||||
|
框架会检查依赖关系,防止卸载被其他插件依赖的插件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await Core.installPlugin(new PhysicsPlugin());
|
||||||
|
await Core.installPlugin(new AdvancedPhysicsPlugin());
|
||||||
|
|
||||||
|
// 错误:physics-plugin 被 advanced-physics 依赖
|
||||||
|
try {
|
||||||
|
await Core.uninstallPlugin('physics-plugin');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error); // Cannot uninstall plugin physics-plugin: it is required by advanced-physics
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确:先卸载依赖它的插件
|
||||||
|
await Core.uninstallPlugin('advanced-physics');
|
||||||
|
await Core.uninstallPlugin('physics-plugin');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 插件管理
|
||||||
|
|
||||||
|
### 通过 Core 管理
|
||||||
|
|
||||||
|
Core 类提供了便捷的插件管理方法:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 安装插件
|
||||||
|
await Core.installPlugin(myPlugin);
|
||||||
|
|
||||||
|
// 卸载插件
|
||||||
|
await Core.uninstallPlugin('plugin-name');
|
||||||
|
|
||||||
|
// 检查插件是否已安装
|
||||||
|
if (Core.isPluginInstalled('plugin-name')) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取插件实例
|
||||||
|
const plugin = Core.getPlugin('plugin-name');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通过 PluginManager 管理
|
||||||
|
|
||||||
|
也可以直接使用 PluginManager 服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const pluginManager = Core.services.resolve(PluginManager);
|
||||||
|
|
||||||
|
// 获取所有插件
|
||||||
|
const allPlugins = pluginManager.getAllPlugins();
|
||||||
|
console.log(`Total plugins: ${allPlugins.length}`);
|
||||||
|
|
||||||
|
// 获取插件元数据
|
||||||
|
const metadata = pluginManager.getMetadata('my-plugin');
|
||||||
|
if (metadata) {
|
||||||
|
console.log(`State: ${metadata.state}`);
|
||||||
|
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有插件元数据
|
||||||
|
const allMetadata = pluginManager.getAllMetadata();
|
||||||
|
for (const meta of allMetadata) {
|
||||||
|
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实用插件示例
|
||||||
|
|
||||||
|
### 网络同步插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class NetworkSyncService implements IService {
|
||||||
|
private ws?: WebSocket;
|
||||||
|
|
||||||
|
connect(url: string) {
|
||||||
|
this.ws = new WebSocket(url);
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
this.handleMessage(data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMessage(data: any) {
|
||||||
|
// 处理网络消息
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkSyncPlugin implements IPlugin {
|
||||||
|
readonly name = 'network-sync';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
// 注册网络服务
|
||||||
|
services.registerSingleton(NetworkSyncService);
|
||||||
|
|
||||||
|
// 自动连接
|
||||||
|
const network = services.resolve(NetworkSyncService);
|
||||||
|
network.connect('ws://localhost:8080');
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 服务会自动dispose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能分析插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class PerformanceAnalysisPlugin implements IPlugin {
|
||||||
|
readonly name = 'performance-analysis';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
private frameCount = 0;
|
||||||
|
private totalTime = 0;
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
const monitor = services.resolve(PerformanceMonitor);
|
||||||
|
monitor.enable();
|
||||||
|
|
||||||
|
// 定期输出性能报告
|
||||||
|
const timer = services.resolve(TimerManager);
|
||||||
|
timer.schedule(5.0, true, null, () => {
|
||||||
|
this.printReport(monitor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 清理
|
||||||
|
}
|
||||||
|
|
||||||
|
private printReport(monitor: PerformanceMonitor) {
|
||||||
|
console.log('=== Performance Report ===');
|
||||||
|
console.log(`FPS: ${monitor.getFPS()}`);
|
||||||
|
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
- 插件名称使用小写字母和连字符:`my-awesome-plugin`
|
||||||
|
- 版本号遵循语义化版本规范:`1.0.0`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyPlugin implements IPlugin {
|
||||||
|
readonly name = 'my-awesome-plugin'; // 好
|
||||||
|
readonly version = '1.0.0'; // 好
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理资源
|
||||||
|
|
||||||
|
始终在 `uninstall` 中清理插件创建的所有资源:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyPlugin implements IPlugin {
|
||||||
|
readonly name = 'my-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
private timerId?: number;
|
||||||
|
private listener?: () => void;
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
// 添加定时器
|
||||||
|
this.timerId = setInterval(() => {
|
||||||
|
// ...
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// 添加事件监听
|
||||||
|
this.listener = () => {};
|
||||||
|
window.addEventListener('resize', this.listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 清理定时器
|
||||||
|
if (this.timerId) {
|
||||||
|
clearInterval(this.timerId);
|
||||||
|
this.timerId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除事件监听
|
||||||
|
if (this.listener) {
|
||||||
|
window.removeEventListener('resize', this.listener);
|
||||||
|
this.listener = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
在插件中妥善处理错误,避免影响整个应用:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MyPlugin implements IPlugin {
|
||||||
|
readonly name = 'my-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
async install(core: Core, services: ServiceContainer): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 可能失败的操作
|
||||||
|
await this.loadConfig();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load plugin config:', error);
|
||||||
|
throw error; // 重新抛出,让框架知道安装失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.cleanup();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to cleanup plugin:', error);
|
||||||
|
// 即使清理失败也不应该阻止卸载
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadConfig() {
|
||||||
|
// 加载配置
|
||||||
|
}
|
||||||
|
|
||||||
|
private async cleanup() {
|
||||||
|
// 清理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置化
|
||||||
|
|
||||||
|
允许用户配置插件行为:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface NetworkPluginConfig {
|
||||||
|
serverUrl: string;
|
||||||
|
autoReconnect: boolean;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkPlugin implements IPlugin {
|
||||||
|
readonly name = 'network-plugin';
|
||||||
|
readonly version = '1.0.0';
|
||||||
|
|
||||||
|
constructor(private config: NetworkPluginConfig) {}
|
||||||
|
|
||||||
|
install(core: Core, services: ServiceContainer): void {
|
||||||
|
const network = new NetworkService(this.config);
|
||||||
|
services.registerInstance(NetworkService, network);
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall(): void {
|
||||||
|
// 清理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
const plugin = new NetworkPlugin({
|
||||||
|
serverUrl: 'ws://localhost:8080',
|
||||||
|
autoReconnect: true,
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
await Core.installPlugin(plugin);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 插件安装失败
|
||||||
|
|
||||||
|
**问题**: 插件安装时抛出错误
|
||||||
|
|
||||||
|
**原因**:
|
||||||
|
- 依赖未满足
|
||||||
|
- install 方法中有异常
|
||||||
|
- 服务注册冲突
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
1. 检查依赖是否已安装
|
||||||
|
2. 查看错误日志
|
||||||
|
3. 确保服务名称不冲突
|
||||||
|
|
||||||
|
### 插件卸载后仍有副作用
|
||||||
|
|
||||||
|
**问题**: 卸载插件后,插件的功能仍在运行
|
||||||
|
|
||||||
|
**原因**: uninstall 方法中未正确清理资源
|
||||||
|
|
||||||
|
**解决**: 确保在 uninstall 中清理:
|
||||||
|
- 定时器
|
||||||
|
- 事件监听器
|
||||||
|
- WebSocket连接
|
||||||
|
- 系统引用
|
||||||
|
|
||||||
|
### 何时使用插件
|
||||||
|
|
||||||
|
**适合使用插件**:
|
||||||
|
- 可选功能(调试工具、性能分析)
|
||||||
|
- 第三方集成(网络库、物理引擎)
|
||||||
|
- 跨项目复用的功能模块
|
||||||
|
|
||||||
|
**不适合使用插件**:
|
||||||
|
- 核心游戏逻辑
|
||||||
|
- 简单的工具类
|
||||||
|
- 项目特定的功能
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- [服务容器](./service-container.md) - 在插件中使用服务容器
|
||||||
|
- [系统架构](./system.md) - 在插件中添加系统
|
||||||
|
- [快速开始](./getting-started.md) - Core 初始化和基础使用
|
||||||
555
docs/guide/service-container.md
Normal file
555
docs/guide/service-container.md
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
# 服务容器
|
||||||
|
|
||||||
|
服务容器(ServiceContainer)是 ECS Framework 的依赖注入容器,负责管理框架中所有服务的注册、解析和生命周期。通过服务容器,你可以实现松耦合的架构设计,提高代码的可测试性和可维护性。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
### 什么是服务容器
|
||||||
|
|
||||||
|
服务容器是一个轻量级的依赖注入(DI)容器,它提供了:
|
||||||
|
|
||||||
|
- **服务注册**: 将服务类型注册到容器中
|
||||||
|
- **服务解析**: 从容器中获取服务实例
|
||||||
|
- **生命周期管理**: 自动管理服务实例的创建和销毁
|
||||||
|
- **依赖注入**: 自动解析服务之间的依赖关系
|
||||||
|
|
||||||
|
### 核心概念
|
||||||
|
|
||||||
|
#### 服务(Service)
|
||||||
|
|
||||||
|
服务是实现了 `IService` 接口的类,必须提供 `dispose()` 方法用于资源清理:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IService } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class MyService implements IService {
|
||||||
|
constructor() {
|
||||||
|
// 初始化逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 生命周期
|
||||||
|
|
||||||
|
服务容器支持两种生命周期:
|
||||||
|
|
||||||
|
- **Singleton(单例)**: 整个应用程序生命周期内只有一个实例,所有解析请求返回同一个实例
|
||||||
|
- **Transient(瞬时)**: 每次解析都创建新的实例
|
||||||
|
|
||||||
|
## 基础使用
|
||||||
|
|
||||||
|
### 访问服务容器
|
||||||
|
|
||||||
|
Core 类内置了服务容器,可以通过 `Core.services` 访问:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 初始化Core
|
||||||
|
Core.create({ debug: true });
|
||||||
|
|
||||||
|
// 访问服务容器
|
||||||
|
const container = Core.services;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注册服务
|
||||||
|
|
||||||
|
#### 注册单例服务
|
||||||
|
|
||||||
|
单例服务在首次解析时创建,之后所有解析请求都返回同一个实例:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class DataService implements IService {
|
||||||
|
private data: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
getData(key: string) {
|
||||||
|
return this.data.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(key: string, value: any) {
|
||||||
|
this.data.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.data.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册单例服务
|
||||||
|
Core.services.registerSingleton(DataService);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 注册瞬时服务
|
||||||
|
|
||||||
|
瞬时服务每次解析都创建新实例,适用于无状态或短生命周期的服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class CommandService implements IService {
|
||||||
|
execute(command: string) {
|
||||||
|
console.log(`Executing: ${command}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册瞬时服务
|
||||||
|
Core.services.registerTransient(CommandService);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 注册服务实例
|
||||||
|
|
||||||
|
直接注册已创建的实例,自动视为单例:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const config = new ConfigService();
|
||||||
|
config.load('./config.json');
|
||||||
|
|
||||||
|
// 注册实例
|
||||||
|
Core.services.registerInstance(ConfigService, config);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用工厂函数注册
|
||||||
|
|
||||||
|
工厂函数允许你在创建服务时执行自定义逻辑:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
Core.services.registerSingleton(LoggerService, (container) => {
|
||||||
|
const logger = new LoggerService();
|
||||||
|
logger.setLevel('debug');
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 解析服务
|
||||||
|
|
||||||
|
#### resolve 方法
|
||||||
|
|
||||||
|
解析服务实例,如果服务未注册会抛出异常:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 解析服务
|
||||||
|
const dataService = Core.services.resolve(DataService);
|
||||||
|
dataService.setData('player', { name: 'Alice', score: 100 });
|
||||||
|
|
||||||
|
// 单例服务,多次解析返回同一个实例
|
||||||
|
const same = Core.services.resolve(DataService);
|
||||||
|
console.log(same === dataService); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### tryResolve 方法
|
||||||
|
|
||||||
|
尝试解析服务,如果未注册返回 null 而不抛出异常:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const optional = Core.services.tryResolve(OptionalService);
|
||||||
|
if (optional) {
|
||||||
|
optional.doSomething();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### isRegistered 方法
|
||||||
|
|
||||||
|
检查服务是否已注册:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (Core.services.isRegistered(DataService)) {
|
||||||
|
const service = Core.services.resolve(DataService);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内置服务
|
||||||
|
|
||||||
|
Core 在初始化时自动注册了以下内置服务:
|
||||||
|
|
||||||
|
### TimerManager
|
||||||
|
|
||||||
|
定时器管理器,负责管理所有游戏定时器:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const timerManager = Core.services.resolve(TimerManager);
|
||||||
|
|
||||||
|
// 创建定时器
|
||||||
|
timerManager.schedule(1.0, false, null, (timer) => {
|
||||||
|
console.log('1秒后执行');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### PerformanceMonitor
|
||||||
|
|
||||||
|
性能监控器,监控游戏性能并提供优化建议:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const monitor = Core.services.resolve(PerformanceMonitor);
|
||||||
|
|
||||||
|
// 启用性能监控
|
||||||
|
monitor.enable();
|
||||||
|
|
||||||
|
// 获取性能数据
|
||||||
|
const fps = monitor.getFPS();
|
||||||
|
```
|
||||||
|
|
||||||
|
### SceneManager
|
||||||
|
|
||||||
|
场景管理器,管理场景的生命周期:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const sceneManager = Core.services.resolve(SceneManager);
|
||||||
|
|
||||||
|
// 获取当前场景
|
||||||
|
const currentScene = sceneManager.currentScene;
|
||||||
|
```
|
||||||
|
|
||||||
|
### PoolManager
|
||||||
|
|
||||||
|
对象池管理器,管理所有对象池:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const poolManager = Core.services.resolve(PoolManager);
|
||||||
|
|
||||||
|
// 创建对象池
|
||||||
|
const bulletPool = poolManager.createPool('bullets', () => new Bullet(), 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
### PluginManager
|
||||||
|
|
||||||
|
插件管理器,管理插件的安装和卸载:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const pluginManager = Core.services.resolve(PluginManager);
|
||||||
|
|
||||||
|
// 获取所有已安装的插件
|
||||||
|
const plugins = pluginManager.getAllPlugins();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 依赖注入
|
||||||
|
|
||||||
|
ECS Framework 提供了装饰器来简化依赖注入。
|
||||||
|
|
||||||
|
### @Injectable 装饰器
|
||||||
|
|
||||||
|
标记类为可注入的服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Injectable, IService } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class GameService implements IService {
|
||||||
|
constructor() {
|
||||||
|
console.log('GameService created');
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
console.log('GameService disposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### @Inject 装饰器
|
||||||
|
|
||||||
|
在构造函数中注入依赖:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Injectable, Inject, IService } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class PlayerService implements IService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DataService) private data: DataService,
|
||||||
|
@Inject(GameService) private game: GameService
|
||||||
|
) {
|
||||||
|
// data 和 game 会自动从容器中解析
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注册可注入服务
|
||||||
|
|
||||||
|
使用 `registerInjectable` 自动处理依赖注入:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { registerInjectable } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 注册服务(会自动解析@Inject依赖)
|
||||||
|
registerInjectable(Core.services, PlayerService);
|
||||||
|
|
||||||
|
// 解析时会自动注入依赖
|
||||||
|
const player = Core.services.resolve(PlayerService);
|
||||||
|
```
|
||||||
|
|
||||||
|
### @Updatable 装饰器
|
||||||
|
|
||||||
|
标记服务为可更新的,使其在每帧自动被调用:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Injectable, Updatable, IService, IUpdatable } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@Updatable() // 默认优先级为0
|
||||||
|
class PhysicsService implements IService, IUpdatable {
|
||||||
|
update(deltaTime?: number): void {
|
||||||
|
// 每帧更新物理模拟
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定更新优先级(数值越小越先执行)
|
||||||
|
@Injectable()
|
||||||
|
@Updatable(10)
|
||||||
|
class RenderService implements IService, IUpdatable {
|
||||||
|
update(deltaTime?: number): void {
|
||||||
|
// 每帧渲染
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 `@Updatable` 装饰器的服务会被 Core 自动调用,无需手动管理:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Core.update() 会自动调用所有@Updatable服务的update方法
|
||||||
|
function gameLoop(deltaTime: number) {
|
||||||
|
Core.update(deltaTime); // 自动更新所有可更新服务
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义服务
|
||||||
|
|
||||||
|
### 创建自定义服务
|
||||||
|
|
||||||
|
实现 `IService` 接口并注册到容器:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IService } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class AudioService implements IService {
|
||||||
|
private sounds: Map<string, HTMLAudioElement> = new Map();
|
||||||
|
|
||||||
|
play(soundId: string) {
|
||||||
|
const sound = this.sounds.get(soundId);
|
||||||
|
if (sound) {
|
||||||
|
sound.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load(soundId: string, url: string) {
|
||||||
|
const audio = new Audio(url);
|
||||||
|
this.sounds.set(soundId, audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 停止所有音效并清理
|
||||||
|
for (const sound of this.sounds.values()) {
|
||||||
|
sound.pause();
|
||||||
|
sound.src = '';
|
||||||
|
}
|
||||||
|
this.sounds.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册自定义服务
|
||||||
|
Core.services.registerSingleton(AudioService);
|
||||||
|
|
||||||
|
// 使用服务
|
||||||
|
const audio = Core.services.resolve(AudioService);
|
||||||
|
audio.load('jump', '/sounds/jump.mp3');
|
||||||
|
audio.play('jump');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 服务间依赖
|
||||||
|
|
||||||
|
服务可以依赖其他服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Injectable()
|
||||||
|
class ConfigService implements IService {
|
||||||
|
private config: any = {};
|
||||||
|
|
||||||
|
get(key: string) {
|
||||||
|
return this.config[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.config = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class NetworkService implements IService {
|
||||||
|
constructor(
|
||||||
|
@Inject(ConfigService) private config: ConfigService
|
||||||
|
) {
|
||||||
|
// 使用配置服务
|
||||||
|
const apiUrl = this.config.get('apiUrl');
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 清理网络连接
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册服务(按依赖顺序)
|
||||||
|
registerInjectable(Core.services, ConfigService);
|
||||||
|
registerInjectable(Core.services, NetworkService);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 服务替换(测试)
|
||||||
|
|
||||||
|
在测试中替换真实服务为模拟服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 测试代码
|
||||||
|
class MockDataService implements IService {
|
||||||
|
getData(key: string) {
|
||||||
|
return 'mock data';
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册模拟服务(用于测试)
|
||||||
|
Core.services.registerInstance(DataService, new MockDataService());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 循环依赖检测
|
||||||
|
|
||||||
|
服务容器会自动检测循环依赖:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// A 依赖 B,B 依赖 A
|
||||||
|
@Injectable()
|
||||||
|
class ServiceA implements IService {
|
||||||
|
constructor(@Inject(ServiceB) b: ServiceB) {}
|
||||||
|
dispose(): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class ServiceB implements IService {
|
||||||
|
constructor(@Inject(ServiceA) a: ServiceA) {}
|
||||||
|
dispose(): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析时会抛出错误: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取所有服务
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取所有已注册的服务类型
|
||||||
|
const types = Core.services.getRegisteredServices();
|
||||||
|
|
||||||
|
// 获取所有已实例化的服务实例
|
||||||
|
const instances = Core.services.getAll();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 服务清理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 注销单个服务
|
||||||
|
Core.services.unregister(MyService);
|
||||||
|
|
||||||
|
// 清空所有服务(会调用每个服务的dispose方法)
|
||||||
|
Core.services.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 服务命名
|
||||||
|
|
||||||
|
服务类名应该以 `Service` 或 `Manager` 结尾,清晰表达其职责:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class PlayerService implements IService {}
|
||||||
|
class AudioManager implements IService {}
|
||||||
|
class NetworkService implements IService {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源清理
|
||||||
|
|
||||||
|
始终在 `dispose()` 方法中清理资源:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ResourceService implements IService {
|
||||||
|
private resources: Map<string, Resource> = new Map();
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// 释放所有资源
|
||||||
|
for (const resource of this.resources.values()) {
|
||||||
|
resource.release();
|
||||||
|
}
|
||||||
|
this.resources.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 避免过度使用
|
||||||
|
|
||||||
|
不要把所有类都注册为服务,服务应该是:
|
||||||
|
|
||||||
|
- 全局单例或需要共享状态
|
||||||
|
- 需要在多处使用
|
||||||
|
- 生命周期需要管理
|
||||||
|
- 需要依赖注入
|
||||||
|
|
||||||
|
对于简单的工具类或数据类,直接创建实例即可。
|
||||||
|
|
||||||
|
### 依赖方向
|
||||||
|
|
||||||
|
保持清晰的依赖方向,避免循环依赖:
|
||||||
|
|
||||||
|
```
|
||||||
|
高层服务 -> 中层服务 -> 底层服务
|
||||||
|
GameLogic -> DataService -> ConfigService
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 服务未注册错误
|
||||||
|
|
||||||
|
**问题**: `Error: Service MyService is not registered`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```typescript
|
||||||
|
// 确保服务已注册
|
||||||
|
Core.services.registerSingleton(MyService);
|
||||||
|
|
||||||
|
// 或者使用tryResolve
|
||||||
|
const service = Core.services.tryResolve(MyService);
|
||||||
|
if (!service) {
|
||||||
|
console.log('Service not found');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 循环依赖错误
|
||||||
|
|
||||||
|
**问题**: `Circular dependency detected`
|
||||||
|
|
||||||
|
**解决**: 重新设计服务依赖关系,引入中间服务或使用事件系统解耦。
|
||||||
|
|
||||||
|
### 何时使用单例 vs 瞬时
|
||||||
|
|
||||||
|
- **单例**: 管理器类、配置、缓存、状态管理
|
||||||
|
- **瞬时**: 命令对象、请求处理器、临时任务
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- [插件系统](./plugin-system.md) - 使用服务容器注册插件服务
|
||||||
|
- [快速开始](./getting-started.md) - Core 初始化和基础使用
|
||||||
|
- [系统架构](./system.md) - 在系统中使用服务
|
||||||
@@ -354,14 +354,18 @@ class PerformanceSystem extends EntitySystem {
|
|||||||
|
|
||||||
### 添加系统到场景
|
### 添加系统到场景
|
||||||
|
|
||||||
|
框架提供了两种方式添加系统:传入实例或传入类型(自动依赖注入)。
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 在场景子类中添加系统
|
// 在场景子类中添加系统
|
||||||
class GameScene extends Scene {
|
class GameScene extends Scene {
|
||||||
protected initialize(): void {
|
protected initialize(): void {
|
||||||
// 添加系统
|
// 方式1:传入实例
|
||||||
this.addSystem(new MovementSystem());
|
this.addSystem(new MovementSystem());
|
||||||
this.addSystem(new RenderSystem());
|
this.addSystem(new RenderSystem());
|
||||||
this.addSystem(new PhysicsSystem());
|
|
||||||
|
// 方式2:传入类型(自动依赖注入)
|
||||||
|
this.addEntityProcessor(PhysicsSystem);
|
||||||
|
|
||||||
// 设置系统更新顺序
|
// 设置系统更新顺序
|
||||||
const movementSystem = this.getSystem(MovementSystem);
|
const movementSystem = this.getSystem(MovementSystem);
|
||||||
@@ -372,6 +376,48 @@ class GameScene extends Scene {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 系统依赖注入
|
||||||
|
|
||||||
|
系统实现了 `IService` 接口,支持通过依赖注入获取其他服务或系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ECSSystem, Injectable, Inject } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@ECSSystem('Physics')
|
||||||
|
class PhysicsSystem extends EntitySystem {
|
||||||
|
constructor(
|
||||||
|
@Inject(CollisionService) private collision: CollisionService
|
||||||
|
) {
|
||||||
|
super(Matcher.all(Transform, RigidBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 使用注入的服务
|
||||||
|
this.collision.detectCollisions(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现 IService 接口的 dispose 方法
|
||||||
|
public dispose(): void {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用时传入类型即可,框架会自动注入依赖
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 自动依赖注入
|
||||||
|
this.addEntityProcessor(PhysicsSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意事项:
|
||||||
|
- 使用 `@Injectable()` 装饰器标记需要依赖注入的系统
|
||||||
|
- 在构造函数参数中使用 `@Inject()` 装饰器声明依赖
|
||||||
|
- 系统必须实现 `dispose()` 方法(IService 接口要求)
|
||||||
|
- 使用 `addEntityProcessor(类型)` 而不是 `addSystem(new 类型())` 来启用依赖注入
|
||||||
|
|
||||||
### 系统更新顺序
|
### 系统更新顺序
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -15557,7 +15557,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@esengine/ecs-framework",
|
"name": "@esengine/ecs-framework",
|
||||||
"version": "2.1.52",
|
"version": "2.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"msgpack-lite": "^0.1.26"
|
"msgpack-lite": "^0.1.26"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@esengine/ecs-framework",
|
"name": "@esengine/ecs-framework",
|
||||||
"version": "2.1.52",
|
"version": "2.2.0",
|
||||||
"description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架",
|
"description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架",
|
||||||
"main": "bin/index.js",
|
"main": "bin/index.js",
|
||||||
"types": "bin/index.d.ts",
|
"types": "bin/index.d.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user