升级项目框架,移除大部分无用的物理和tween系统
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
# Collision
|
||||
碰撞检测在大多数游戏中都很常见。框架内使用了一些更先进的碰撞/重叠检查方法,如Minkowski、分离轴定理和古老的三角法
|
||||
|
||||
## 线与线相交 [lineToLine]
|
||||
- 返回是否相交
|
||||
```typescript
|
||||
const a1 = new es.Vector2(0, 0);
|
||||
const a2 = new es.Vector2(100, 100);
|
||||
const b1 = new es.Vector2(-100, 0);
|
||||
const b2 = new es.Vector2(100, 200);
|
||||
const result = es.Collisions.lineToLine(a1, a2, b1, b2);
|
||||
```
|
||||
- 返回是否相交并获得相交的点
|
||||
```typescript
|
||||
const a1 = new es.Vector2(0, 0);
|
||||
const a2 = new es.Vector2(100, 100);
|
||||
const b1 = new es.Vector2(-100, 0);
|
||||
const b2 = new es.Vector2(100, 200);
|
||||
// 相交的点坐标
|
||||
const intersection = new es.Vector2();
|
||||
const result = es.Collisions.lineToLineIntersection(a1, a2, b1, b2, intersection);
|
||||
```
|
||||
|
||||
## 圆和圆相交 [circleToCircle]
|
||||
```typescript
|
||||
const center1 = new es.Vector2(0, 0);
|
||||
const radius1 = 50;
|
||||
const center2 = new es.Vector2(30, 30);
|
||||
const radius2 = 50;
|
||||
const result = es.Collisions.circleToCircle(center1, radius1, center2, radius2);
|
||||
```
|
||||
|
||||
## 圆和线相交 [circleToLine]
|
||||
```typescript
|
||||
const center1 = new es.Vector2(0, 0);
|
||||
const radius1 = 50;
|
||||
const a1 = new es.Vector2(0, 0);
|
||||
const a2 = new es.Vector2(100, 100);
|
||||
const result = es.Collisions.circleToLine(center1, radius1, a1, a2);
|
||||
```
|
||||
|
||||
## 点是否在圆内 [circleToPoint]
|
||||
```typescript
|
||||
const center1 = new es.Vector2(0, 0);
|
||||
const radius1 = 50;
|
||||
const point = new es.Vector2(0, 0);
|
||||
const result = es.Collisions.circleToPoint(center1, radius1, point);
|
||||
```
|
||||
|
||||
## 圆是否和矩形相交 [rectToCircle]
|
||||
```typescript
|
||||
const rect = new es.Rectangle(0, 0, 100, 100);
|
||||
const center = new es.Vector2(30, 30);
|
||||
const radius = 50;
|
||||
const result = es.Collisions.rectToCircle(rect, center, radius);
|
||||
```
|
||||
|
||||
## 矩形与线是否相交 [rectToLine]
|
||||
```typescript
|
||||
const rect = new es.Rectangle(0, 0, 100, 100);
|
||||
const a1 = new es.Vector2(0, 0);
|
||||
const a2 = new es.Vector2(100, 100);
|
||||
const result = es.Collisions.rectToLine(rect, a1, a2);
|
||||
```
|
||||
|
||||
## 点是否在矩形内 [rectToPoint]
|
||||
```typescript
|
||||
const point = new es.Vector2(100, 100);
|
||||
const result = es.Collisions.rectToPoint(0, 0, 100, 100, point);
|
||||
```
|
||||
496
docs/core-concepts.md
Normal file
496
docs/core-concepts.md
Normal file
@@ -0,0 +1,496 @@
|
||||
# 核心概念
|
||||
|
||||
ECS Framework 基于 Entity-Component-System 架构模式,这是一种高度模块化和可扩展的游戏开发架构。本文档将详细介绍框架的核心概念。
|
||||
|
||||
## ECS 架构概述
|
||||
|
||||
ECS 架构将传统的面向对象设计分解为三个核心部分:
|
||||
|
||||
- **Entity(实体)** - 游戏世界中的对象,包含基本属性如位置、旋转、缩放
|
||||
- **Component(组件)** - 包含数据和行为的功能模块
|
||||
- **System(系统)** - 处理实体集合的逻辑处理单元
|
||||
|
||||
## Core(核心)
|
||||
|
||||
Core 是框架的核心管理类,负责游戏的生命周期管理。
|
||||
|
||||
### 创建和配置
|
||||
|
||||
```typescript
|
||||
import { Core } from './Core';
|
||||
|
||||
// 创建核心实例(调试模式)
|
||||
const core = Core.create(true);
|
||||
|
||||
// 创建核心实例(发布模式)
|
||||
const core = Core.create(false);
|
||||
```
|
||||
|
||||
### 事件系统
|
||||
|
||||
```typescript
|
||||
import { CoreEvents } from './ECS/CoreEvents';
|
||||
|
||||
// 监听核心事件
|
||||
Core.emitter.addObserver(CoreEvents.frameUpdated, this.onUpdate, this);
|
||||
|
||||
// 发送帧更新事件
|
||||
Core.emitter.emit(CoreEvents.frameUpdated);
|
||||
|
||||
// 发送自定义事件
|
||||
Core.emitter.emit("customEvent", { data: "value" });
|
||||
```
|
||||
|
||||
### 定时器系统
|
||||
|
||||
```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 './ECS/Scene';
|
||||
|
||||
// 创建场景
|
||||
const scene = new Scene();
|
||||
scene.name = "GameScene";
|
||||
|
||||
// 设置为当前场景
|
||||
Core.scene = scene;
|
||||
|
||||
// 场景生命周期
|
||||
scene.begin(); // 开始场景
|
||||
scene.update(); // 更新场景
|
||||
scene.end(); // 结束场景
|
||||
```
|
||||
|
||||
## Entity(实体)
|
||||
|
||||
实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性。
|
||||
|
||||
### 实体的基本属性
|
||||
|
||||
```typescript
|
||||
import { Vector2 } from './Math/Vector2';
|
||||
|
||||
const entity = scene.createEntity("MyEntity");
|
||||
|
||||
// 位置
|
||||
entity.position = new Vector2(100, 200);
|
||||
entity.position = entity.position.add(new Vector2(10, 0));
|
||||
|
||||
// 旋转(弧度)
|
||||
entity.rotation = Math.PI / 4;
|
||||
|
||||
// 缩放
|
||||
entity.scale = new Vector2(2, 2);
|
||||
|
||||
// 标签(用于分类)
|
||||
entity.tag = 1;
|
||||
|
||||
// 启用状态
|
||||
entity.enabled = true;
|
||||
|
||||
// 活跃状态
|
||||
entity.active = true;
|
||||
|
||||
// 更新顺序
|
||||
entity.updateOrder = 10;
|
||||
```
|
||||
|
||||
### 实体层级关系
|
||||
|
||||
```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 './ECS/Component';
|
||||
|
||||
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();
|
||||
```
|
||||
|
||||
## Scene(场景)
|
||||
|
||||
场景是实体和系统的容器,管理游戏世界的状态。
|
||||
|
||||
### 场景生命周期
|
||||
|
||||
```typescript
|
||||
class GameScene extends es.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);
|
||||
```
|
||||
|
||||
## System(系统)
|
||||
|
||||
系统处理实体集合,实现游戏的核心逻辑。
|
||||
|
||||
### EntitySystem
|
||||
|
||||
最常用的系统类型,处理实体集合:
|
||||
|
||||
```typescript
|
||||
class MovementSystem extends es.EntitySystem {
|
||||
protected process(entities: es.Entity[]) {
|
||||
for (const entity of entities) {
|
||||
const movement = entity.getComponent(MovementComponent);
|
||||
if (movement) {
|
||||
movement.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ProcessingSystem
|
||||
|
||||
定期处理的系统:
|
||||
|
||||
```typescript
|
||||
class HealthRegenerationSystem extends es.ProcessingSystem {
|
||||
protected process(entities: es.Entity[]) {
|
||||
for (const entity of entities) {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
if (health && health.currentHealth < health.maxHealth) {
|
||||
health.currentHealth += 10 * es.Time.deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### IntervalSystem
|
||||
|
||||
按时间间隔执行的系统:
|
||||
|
||||
```typescript
|
||||
class SpawnSystem extends es.IntervalSystem {
|
||||
constructor() {
|
||||
super(3.0); // 每3秒执行一次
|
||||
}
|
||||
|
||||
protected processSystem() {
|
||||
// 生成敌人
|
||||
const enemy = this.scene.createEntity("Enemy");
|
||||
enemy.addComponent(new EnemyComponent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PassiveSystem
|
||||
|
||||
被动系统,不自动处理实体:
|
||||
|
||||
```typescript
|
||||
class CollisionSystem extends es.PassiveSystem {
|
||||
public checkCollisions() {
|
||||
// 手动调用的碰撞检测逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Time(时间)
|
||||
|
||||
时间管理工具类,提供游戏时间相关功能:
|
||||
|
||||
```typescript
|
||||
// 获取时间信息
|
||||
console.log("帧时间:", es.Time.deltaTime);
|
||||
console.log("总时间:", es.Time.totalTime);
|
||||
console.log("帧数:", es.Time.frameCount);
|
||||
console.log("时间缩放:", es.Time.timeScale);
|
||||
|
||||
// 设置时间缩放(慢动作效果)
|
||||
es.Time.timeScale = 0.5;
|
||||
|
||||
// 检查时间间隔
|
||||
if (es.Time.checkEvery(1.0, lastCheckTime)) {
|
||||
// 每秒执行一次
|
||||
}
|
||||
```
|
||||
|
||||
## Vector2(二维向量)
|
||||
|
||||
二维向量类,提供数学运算:
|
||||
|
||||
```typescript
|
||||
// 创建向量
|
||||
const vec1 = new es.Vector2(10, 20);
|
||||
const vec2 = es.Vector2.zero;
|
||||
const vec3 = es.Vector2.one;
|
||||
|
||||
// 向量运算
|
||||
const sum = vec1.add(vec2);
|
||||
const diff = vec1.subtract(vec2);
|
||||
const scaled = vec1.multiply(2);
|
||||
const normalized = vec1.normalize();
|
||||
|
||||
// 向量属性
|
||||
console.log("长度:", vec1.length);
|
||||
console.log("长度平方:", vec1.lengthSquared);
|
||||
|
||||
// 静态方法
|
||||
const distance = es.Vector2.distance(vec1, vec2);
|
||||
const lerped = es.Vector2.lerp(vec1, vec2, 0.5);
|
||||
const fromAngle = es.Vector2.fromAngle(Math.PI / 4);
|
||||
```
|
||||
|
||||
## 性能监控
|
||||
|
||||
框架内置性能监控工具:
|
||||
|
||||
```typescript
|
||||
// 获取性能监控实例
|
||||
const monitor = es.PerformanceMonitor.instance;
|
||||
|
||||
// 查看性能数据
|
||||
console.log("平均FPS:", monitor.averageFPS);
|
||||
console.log("最小FPS:", monitor.minFPS);
|
||||
console.log("最大FPS:", monitor.maxFPS);
|
||||
console.log("内存使用:", monitor.memoryUsage);
|
||||
|
||||
// 重置性能数据
|
||||
monitor.reset();
|
||||
```
|
||||
|
||||
## 对象池
|
||||
|
||||
内存管理优化工具:
|
||||
|
||||
```typescript
|
||||
// 创建对象池
|
||||
class BulletPool extends es.Pool<Bullet> {
|
||||
protected createObject(): Bullet {
|
||||
return new Bullet();
|
||||
}
|
||||
}
|
||||
|
||||
const bulletPool = new BulletPool();
|
||||
|
||||
// 使用对象池
|
||||
const bullet = bulletPool.obtain();
|
||||
// 使用bullet...
|
||||
bulletPool.free(bullet);
|
||||
|
||||
// 清空对象池
|
||||
bulletPool.clear();
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 实体设计
|
||||
|
||||
- 实体只包含基本属性,功能通过组件实现
|
||||
- 合理使用实体层级关系
|
||||
- 及时销毁不需要的实体
|
||||
|
||||
### 2. 组件设计
|
||||
|
||||
- 组件保持单一职责
|
||||
- 使用生命周期方法进行初始化和清理
|
||||
- 避免组件间直接依赖
|
||||
|
||||
### 3. 系统设计
|
||||
|
||||
- 系统专注于特定逻辑处理
|
||||
- 合理设置系统更新顺序
|
||||
- 使用被动系统处理特殊逻辑
|
||||
|
||||
### 4. 性能优化
|
||||
|
||||
- 使用对象池减少内存分配
|
||||
- 监控性能数据
|
||||
- 合理使用时间缩放
|
||||
|
||||
## 总结
|
||||
|
||||
ECS Framework 提供了完整的实体组件系统架构:
|
||||
|
||||
- **Core** 管理游戏生命周期和全局功能
|
||||
- **Entity** 作为游戏对象的基础容器
|
||||
- **Component** 实现具体的功能模块
|
||||
- **System** 处理游戏逻辑
|
||||
- **Scene** 管理游戏世界状态
|
||||
|
||||
通过合理使用这些核心概念,可以构建出结构清晰、易于维护的游戏代码。
|
||||
@@ -1,189 +0,0 @@
|
||||
# Coroutine
|
||||
## 协程介绍
|
||||
框架的协程系统是基于js的一个简单而强大的迭代器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的代码
|
||||
|
||||
### 倒计时器
|
||||
这是一个简单的脚本组件,只做了倒计时,并且在到达0的时候log一个信息。
|
||||
```typescript
|
||||
export class AComponent extends es.Component implements es.IUpdatable {
|
||||
public timer = 3;
|
||||
update() {
|
||||
this.timer -= es.Time.deltaTime;
|
||||
if(this.timer <= 0) {
|
||||
console.Log("Timer has finished!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
还不错,代码简短实用,但问题是,如果我们需要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?刚开始的时候,我们的代码也许会是这样的:
|
||||
|
||||
```typescript
|
||||
export class AComponent extends es.Component implements es.IUpdatable
|
||||
{
|
||||
public firstTimer = 3;
|
||||
public secondTimer = 2;
|
||||
public thirdTimer = 1;
|
||||
update() {
|
||||
this.firstTimer -= es.Time.deltaTime;
|
||||
if(this.firstTimer <= 0)
|
||||
console.Log("First timer has finished!");
|
||||
this.secondTimer -= es.Time.deltaTime;
|
||||
if(this.secondTimer <= 0)
|
||||
console.Log("Second timer has finished!");
|
||||
this.thirdTimer -= es.Time.deltaTime;
|
||||
if(this.thirdTimer <= 0)
|
||||
console.Log("Third timer has finished!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
尽管不是太糟糕,但是我个人不是很喜欢自己的代码中充斥着这些计时器变量,它们看上去很乱,而且当我需要重新开始计时的时候还得记得去重置它们(这活我经常忘记做)。
|
||||
|
||||
|
||||
|
||||
如果我只用一个for循环来做这些,看上去是否会好很多?
|
||||
|
||||
```typescript
|
||||
for(let timer = 3; timer >= 0; timer -= es.Time.deltaTime) {
|
||||
//Just do nothing...
|
||||
}
|
||||
console.Log("This happens after 5 seconds!");
|
||||
```
|
||||
|
||||
现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个跌倒变量。
|
||||
|
||||
|
||||
|
||||
好的,你可能现在明白我的意思:协程可以做的正是这一点!
|
||||
|
||||
## 码入你的协程!
|
||||
|
||||
现在,这里提供了上面例子运用协程的版本!我建议你从这里开始跟着我来写一个简单的脚本组件,这样你可以在你自己的程序中看到它是如何工作的。
|
||||
|
||||
```typescript
|
||||
export class AComponent extends es.Component implements es.IUpdatable
|
||||
{
|
||||
onAddedToEntity() {
|
||||
es.Core.startCoroutine(this.countdown());
|
||||
}
|
||||
|
||||
*countdown() {
|
||||
for(let timer = 3; timer >= 0; timer -= es.Time.deltaTime)
|
||||
yield null;
|
||||
console.Log("This message appears after 3 seconds!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
这看上去有点不一样,没关系,接下来我会解释这里到底发生了什么。
|
||||
|
||||
```typescript
|
||||
es.Core.startCoroutine(this.countdown());
|
||||
```
|
||||
|
||||
这一行用来开始我们的countdown程序,注意,我并没有给它传入参数,但是这个方法调用了它自己(这是通过传递countdown的yield返回值来实现的)。
|
||||
|
||||
### Yield
|
||||
|
||||
为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,框架必须通过某种方式来存储这个方法的状态,这是通过迭代器中使用yield语句得到的返回值,当你`yield`一个方法时,你相当于说了,**现在停止这个方法,然后在下一帧中从这里重新开始!**。
|
||||
|
||||
> 注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。当然,同样的你可以继续yield其他协程,我会在下一个教程中讲到这些。
|
||||
|
||||
## 一些例子
|
||||
|
||||
协程在刚开始接触的时候是非常难以理解的,无论是新手还是经验丰富的程序员我都见过他们对于协程语句一筹莫展的时候。因此我认为通过例子来理解它是最好的方法,这里有一些简单的协程例子:
|
||||
|
||||
### 多次输出Hello
|
||||
|
||||
记住,yield是 **停止执行方法,并且在下一帧从这里重新开始**,这意味着你可以这样做:
|
||||
|
||||
```typescript
|
||||
//这将打招呼 5 次,每帧一次,持续 5 帧
|
||||
*sayHelloFiveTimes() {
|
||||
yield null;
|
||||
console.Log("Hello");
|
||||
yield null;
|
||||
console.Log("Hello");
|
||||
yield null;
|
||||
console.Log("Hello");
|
||||
yield null;
|
||||
console.Log("Hello");
|
||||
yield null;
|
||||
console.Log("Hello");
|
||||
}
|
||||
//这将做与上述功能完全相同的事情!
|
||||
*sayHello5Times() {
|
||||
for(let i = 0; i < 5; i++) {
|
||||
console.Log("Hello");
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 每一帧输出“Hello”,无限循环。。。
|
||||
|
||||
通过在一个while循环中使用yield,你可以得到一个无限循环的协程,这几乎就跟一个update()循环等同
|
||||
|
||||
```typescript
|
||||
//一旦启动,这将一直运行直到手动停止
|
||||
*sayHelloEveryFrame(){
|
||||
while(true) {
|
||||
console.Log("Hello");
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 计时
|
||||
不过跟update()不一样的是,你可以在协程中做一些更有趣的事
|
||||
|
||||
```typescript
|
||||
*countSeconds(){
|
||||
let seconds = 0;
|
||||
while(true)
|
||||
{
|
||||
// 1秒后执行下一帧
|
||||
yield 1;
|
||||
seconds++;
|
||||
console.Log("自协程启动以来已经过去了" + seconds + "秒钟.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个方法突出了协程一个非常酷的地方:方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即使是在不同的帧中。还记得这个教程开始时那些烦人的计时器变量吗?通过协程,我们再也不需要担心它们了,只需要把变量直接放到方法里面!
|
||||
|
||||
### 开始和终止协程
|
||||
|
||||
之前,我们已经学过了通过 es.Core.startCoroutine()方法来开始一个协程,就像这样:
|
||||
|
||||
```typescript
|
||||
const coroutine = es.Core.startCoroutine(this.countdown());
|
||||
```
|
||||
|
||||
我们可以像这样停止协程
|
||||
|
||||
```typescript
|
||||
coroutine.stop();
|
||||
```
|
||||
|
||||
或者你可以再迭代器内返回`yield "break"`方式中止协程
|
||||
|
||||
```typescript
|
||||
*countSeconds(){
|
||||
let seconds = 0;
|
||||
while(true)
|
||||
{
|
||||
for(let timer = 0; timer < 1; timer += es.Time.deltaTime)
|
||||
yield null;
|
||||
seconds++;
|
||||
console.Log("自协程启动以来已经过去了" + seconds + "秒钟.");
|
||||
|
||||
// 如果大于10秒,终止协程
|
||||
if (second > 10)
|
||||
yield "break";
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,121 +0,0 @@
|
||||
# 创建实体
|
||||
|
||||
实体必须依赖于场景,不能单独存在。创建实体方法由场景提供。
|
||||
|
||||
## 方式一
|
||||
```typescript
|
||||
// 通过全局快捷获取场景创建实体
|
||||
const playerEntity = es.Core.scene.createEntity("player");
|
||||
```
|
||||
|
||||
## 方式二
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
// 通过场景内创建
|
||||
const playerEntity = this.createEntity("player");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Transform
|
||||
|
||||
框架中提供的实体不同于其他框架实体,它更偏向于游戏使用,实体内含有`Transform`属性。可用于快速访问位置,旋转,缩放等。如果需要应用于游戏引擎,请再组件重写`onTransformChanged`监听这些属性的变化。
|
||||
|
||||
> 实体内包含对transform里位置、旋转、缩放的快捷tween方法。`tweenPositionTo`/`tweenLocalPositionTo`/`tweenScaleTo`/`tweenLocalScaleTo`/`tweenRotationDegreesTo`/`tweenLocalRotationDegreesTo`
|
||||
|
||||
### tag / setTag
|
||||
|
||||
实体还提供`tag`属性及`setTag`方法来快速设置实体的标记,可再场景中使用`findEntitiesWithTag`快速查询拥有该标记的实体或使用`findEntityWithTag`来查找第一个拥有该标记的实体,你可以把它当作组来使用。
|
||||
|
||||
### detachFromScene / attachToScene
|
||||
当你不想实体与场景被销毁时一同被销毁。可先 `detachFromScene`,等待合适的时机再调用 `attachToScene` 放入新的场景。
|
||||
|
||||
|
||||
# 创建组件
|
||||
|
||||
组件一般配合实体使用。组件需要继承 `es.Component` 来标识为组件,如果想让组件拥有每帧更新能力则额外继承`es.IUpdatable` 接口。在实现的`update`方法当中进行更新逻辑。
|
||||
|
||||
```typescript
|
||||
// es.IUpdatable接口为可选接口,如果不需要更新能力则不必继承
|
||||
export class AComponent extends es.Component implements es.IUpdatable {
|
||||
update() {
|
||||
// 更新逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 加入组件
|
||||
|
||||
组件必须挂载于实体上,不能单独存在,如果需要单独于场景的组件则参考 [es.SceneComponent](scene_component.md) 组件。
|
||||
|
||||
- 方式一:将现有的AComponent加入实体
|
||||
```typescript
|
||||
const aCom = playerEntity.addComponent(new AComponent());
|
||||
```
|
||||
|
||||
- 方式二:在实体上直接创建组件
|
||||
```typescript
|
||||
const aCom = playerEntity.createComponent(AComponent);
|
||||
```
|
||||
|
||||
## 获取组件
|
||||
|
||||
- 方式一: 根据类型获取找到满足条件的第一个组件
|
||||
```typescript
|
||||
// 不能保证已经加入场景
|
||||
const aCom = playerEntity.getComponent(AComponent);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// 保证已经加入场景
|
||||
const aCom = playerEntity.getComponentInScene(AComponent);
|
||||
```
|
||||
|
||||
- 方式二: 尝试找到一个组件,返回是否找到组件标志,第二参数需要一个引用组件用于存储已找到的组件
|
||||
```typescript
|
||||
const outCom = new Ref<AComponent>();
|
||||
const find = playerEntity.tryGetComponent(AComponent, outCom);
|
||||
if (find) {
|
||||
const aCom = outCom.value;
|
||||
}
|
||||
```
|
||||
|
||||
- 方式三:获取该类型的组件,如果未找到则创建一个并返回
|
||||
```typescript
|
||||
const aCom = playerEntity.getOrCreateComponent(AComponent);
|
||||
```
|
||||
|
||||
- 方式四:根据第二参数中的列表找到该类型的所有组件并返回
|
||||
```typescript
|
||||
const findArray: Component[] = [
|
||||
new AComponent(),
|
||||
new BComponent(),
|
||||
new CComponent()
|
||||
];
|
||||
// findArray可不传,则在实体上寻找满足第一个条件的所有组件
|
||||
const coms = playerEntity.getComponents(AComponent, findArray);
|
||||
```
|
||||
|
||||
- 组件是否存在
|
||||
|
||||
```typescript
|
||||
const find = playerEntity.hasComponent(AComponent);
|
||||
```
|
||||
|
||||
## 移除组件
|
||||
|
||||
- 方式一: 移除已实例组件
|
||||
```typescript
|
||||
playerEntity.removeComponent(aCom);
|
||||
```
|
||||
|
||||
- 方式二:移除满足类型的第一个组件
|
||||
```typescript
|
||||
playerEntity.removeComponentForType(AComponent);
|
||||
```
|
||||
|
||||
- 方式三: 移除所有组件
|
||||
```typescript
|
||||
playerEntity.removeAllComponents();
|
||||
```
|
||||
@@ -1,39 +0,0 @@
|
||||
# Emitter
|
||||
Core提供了一个在某些关键时刻触发事件的发射器。 通过Core.emitter.addObserver和Core.emitter.removeObserver进行访问。 CoreEvents枚举定义了所有可用事件。
|
||||
|
||||
发射器类也可以在自己的类中使用。 您可以通过number,enum或任何结构键输入事件。
|
||||
|
||||
## 自定义事件发生器
|
||||
|
||||
- string为key的事件发生器
|
||||
```typescript
|
||||
export enum CustomEvent {
|
||||
enum1,
|
||||
enum2
|
||||
}
|
||||
|
||||
export class MainScene extends es.Scene {
|
||||
// string为key的事件发生器
|
||||
private str_emitter = new es.Emitter<string>();
|
||||
// number为key的事件发生器
|
||||
private num_emitter = new es.Emitter<number>();
|
||||
// enum为key的事件发生器
|
||||
private custom_emitter = new es.Emitter<CustomEvent>();
|
||||
|
||||
onStart() {
|
||||
// 监听触发器
|
||||
this.str_emitter.addObserver("test", this.onStrEmit, this);
|
||||
|
||||
// 触发监听器
|
||||
this.str_emitter.emit("test");
|
||||
|
||||
// 移除事件触发器
|
||||
this.str_emitter.removeObserver("test", this.onStrEmit);
|
||||
}
|
||||
|
||||
// args为emit传入的参数。不传则为空
|
||||
onStrEmit(...args: any[]) {
|
||||
console.log("test");
|
||||
}
|
||||
}
|
||||
```
|
||||
625
docs/getting-started.md
Normal file
625
docs/getting-started.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# 快速入门
|
||||
|
||||
本指南将帮助您快速上手 ECS Framework,这是一个轻量级的实体组件系统框架,专为小游戏设计。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
ecs-framework/
|
||||
├── source/
|
||||
│ ├── src/ # 源代码
|
||||
│ │ ├── ECS/ # ECS核心系统
|
||||
│ │ ├── Math/ # 数学运算
|
||||
│ │ ├── Types/ # 类型定义
|
||||
│ │ └── Utils/ # 工具类
|
||||
│ ├── scripts/ # 构建脚本
|
||||
│ └── tsconfig.json # TypeScript配置
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
## 安装和构建
|
||||
|
||||
### 从源码构建
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/esengine/ecs-framework.git
|
||||
|
||||
# 进入源码目录
|
||||
cd ecs-framework/source
|
||||
|
||||
# 编译TypeScript
|
||||
npx tsc
|
||||
```
|
||||
|
||||
### 直接使用
|
||||
|
||||
您可以直接将源码复制到项目中使用,或者引用编译后的JavaScript文件。
|
||||
|
||||
## 基础设置
|
||||
|
||||
### 1. 导入框架
|
||||
|
||||
```typescript
|
||||
// 导入核心类
|
||||
import { Core } from './Core';
|
||||
import { Entity } from './ECS/Entity';
|
||||
import { Component } from './ECS/Component';
|
||||
import { Scene } from './ECS/Scene';
|
||||
import { QuerySystem } from './ECS/Core/QuerySystem';
|
||||
import { Emitter } from './Utils/Emitter';
|
||||
import { TimerManager } from './Utils/Timers/TimerManager';
|
||||
```
|
||||
|
||||
### 2. 创建基础管理器
|
||||
|
||||
```typescript
|
||||
class GameManager {
|
||||
private core: Core;
|
||||
private scene: Scene;
|
||||
private querySystem: QuerySystem;
|
||||
private emitter: Emitter;
|
||||
private timerManager: TimerManager;
|
||||
|
||||
constructor() {
|
||||
// 创建核心实例
|
||||
this.core = Core.create(true);
|
||||
|
||||
// 创建场景
|
||||
this.scene = new Scene();
|
||||
this.scene.name = "GameScene";
|
||||
|
||||
// 获取场景的查询系统
|
||||
this.querySystem = this.scene.querySystem;
|
||||
|
||||
// 获取核心的事件系统和定时器
|
||||
this.emitter = Core.emitter;
|
||||
this.timerManager = this.core._timerManager;
|
||||
|
||||
// 设置当前场景
|
||||
Core.scene = this.scene;
|
||||
}
|
||||
|
||||
public update(deltaTime: number): void {
|
||||
// 更新定时器
|
||||
this.timerManager.update(deltaTime);
|
||||
|
||||
// 更新场景
|
||||
this.scene.update();
|
||||
|
||||
// 处理系统逻辑
|
||||
this.updateSystems(deltaTime);
|
||||
}
|
||||
|
||||
private updateSystems(deltaTime: number): void {
|
||||
// 在这里添加您的系统更新逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 游戏循环
|
||||
|
||||
```typescript
|
||||
const gameManager = new GameManager();
|
||||
let lastTime = performance.now();
|
||||
|
||||
function gameLoop() {
|
||||
const currentTime = performance.now();
|
||||
const deltaTime = (currentTime - lastTime) / 1000; // 转换为秒
|
||||
lastTime = currentTime;
|
||||
|
||||
gameManager.update(deltaTime);
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
// 启动游戏循环
|
||||
gameLoop();
|
||||
```
|
||||
|
||||
## 创建实体和组件
|
||||
|
||||
### 1. 定义组件
|
||||
|
||||
```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;
|
||||
|
||||
constructor(x: number = 0, y: number = 0) {
|
||||
super();
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// 生命值组件
|
||||
class HealthComponent extends Component {
|
||||
public maxHealth: number = 100;
|
||||
public currentHealth: number = 100;
|
||||
|
||||
constructor(maxHealth: number = 100) {
|
||||
super();
|
||||
this.maxHealth = maxHealth;
|
||||
this.currentHealth = maxHealth;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 创建实体
|
||||
|
||||
```typescript
|
||||
class GameManager {
|
||||
// ... 之前的代码 ...
|
||||
|
||||
public createPlayer(): Entity {
|
||||
const player = this.scene.createEntity("Player");
|
||||
|
||||
// 添加组件
|
||||
player.addComponent(new PositionComponent(400, 300));
|
||||
player.addComponent(new VelocityComponent(0, 0));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
|
||||
// 设置标签和更新顺序
|
||||
player.tag = 1; // 玩家标签
|
||||
player.updateOrder = 0;
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
public createEnemy(x: number, y: number): Entity {
|
||||
const enemy = this.scene.createEntity("Enemy");
|
||||
|
||||
enemy.addComponent(new PositionComponent(x, y));
|
||||
enemy.addComponent(new VelocityComponent(50, 0));
|
||||
enemy.addComponent(new HealthComponent(50));
|
||||
|
||||
enemy.tag = 2; // 敌人标签
|
||||
enemy.updateOrder = 1;
|
||||
|
||||
return enemy;
|
||||
}
|
||||
}
|
||||
|
||||
## 使用查询系统
|
||||
|
||||
查询系统是框架的核心功能,用于高效查找具有特定组件的实体:
|
||||
|
||||
```typescript
|
||||
class GameManager {
|
||||
// ... 之前的代码 ...
|
||||
|
||||
private updateSystems(deltaTime: number): void {
|
||||
this.updateMovementSystem(deltaTime);
|
||||
this.updateHealthSystem(deltaTime);
|
||||
this.updateCollisionSystem();
|
||||
}
|
||||
|
||||
private updateMovementSystem(deltaTime: number): void {
|
||||
// 查询所有具有位置和速度组件的实体
|
||||
const movableEntities = this.querySystem.queryTwoComponents(
|
||||
PositionComponent,
|
||||
VelocityComponent
|
||||
);
|
||||
|
||||
movableEntities.forEach(({ entity, component1: position, component2: velocity }) => {
|
||||
// 更新位置
|
||||
position.x += velocity.x * deltaTime;
|
||||
position.y += velocity.y * deltaTime;
|
||||
|
||||
// 边界检查
|
||||
if (position.x < 0 || position.x > 800) {
|
||||
velocity.x = -velocity.x;
|
||||
}
|
||||
if (position.y < 0 || position.y > 600) {
|
||||
velocity.y = -velocity.y;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateHealthSystem(deltaTime: number): void {
|
||||
// 查询所有具有生命值组件的实体
|
||||
const healthEntities = this.querySystem.queryComponentTyped(HealthComponent);
|
||||
|
||||
const deadEntities: Entity[] = [];
|
||||
|
||||
healthEntities.forEach(({ entity, component: health }) => {
|
||||
// 检查死亡
|
||||
if (health.isDead()) {
|
||||
deadEntities.push(entity);
|
||||
}
|
||||
});
|
||||
|
||||
// 移除死亡实体
|
||||
deadEntities.forEach(entity => {
|
||||
entity.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
private updateCollisionSystem(): void {
|
||||
// 获取玩家
|
||||
const players = this.scene.findEntitiesByTag(1); // 玩家标签
|
||||
const enemies = this.scene.findEntitiesByTag(2); // 敌人标签
|
||||
|
||||
players.forEach(player => {
|
||||
const playerPos = player.getComponent(PositionComponent);
|
||||
const playerHealth = player.getComponent(HealthComponent);
|
||||
|
||||
if (!playerPos || !playerHealth) return;
|
||||
|
||||
enemies.forEach(enemy => {
|
||||
const enemyPos = enemy.getComponent(PositionComponent);
|
||||
|
||||
if (!enemyPos) return;
|
||||
|
||||
// 简单的距离检测
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(playerPos.x - enemyPos.x, 2) +
|
||||
Math.pow(playerPos.y - enemyPos.y, 2)
|
||||
);
|
||||
|
||||
if (distance < 50) { // 碰撞距离
|
||||
playerHealth.takeDamage(10);
|
||||
console.log(`玩家受到伤害!当前生命值: ${playerHealth.currentHealth}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用事件系统
|
||||
|
||||
框架内置了事件系统,用于组件间通信:
|
||||
|
||||
```typescript
|
||||
// 定义事件类型
|
||||
enum GameEvents {
|
||||
PLAYER_DIED = 'playerDied',
|
||||
ENEMY_SPAWNED = 'enemySpawned',
|
||||
SCORE_CHANGED = 'scoreChanged'
|
||||
}
|
||||
|
||||
class GameManager {
|
||||
// ... 之前的代码 ...
|
||||
|
||||
constructor() {
|
||||
// ... 之前的代码 ...
|
||||
|
||||
// 监听事件
|
||||
this.emitter.on(GameEvents.PLAYER_DIED, this.onPlayerDied.bind(this));
|
||||
this.emitter.on(GameEvents.ENEMY_SPAWNED, this.onEnemySpawned.bind(this));
|
||||
}
|
||||
|
||||
private onPlayerDied(player: Entity): void {
|
||||
console.log('游戏结束!');
|
||||
// 重置游戏或显示游戏结束界面
|
||||
}
|
||||
|
||||
private onEnemySpawned(enemy: Entity): void {
|
||||
console.log('新敌人出现!');
|
||||
}
|
||||
|
||||
private updateHealthSystem(deltaTime: number): void {
|
||||
const healthEntities = this.querySystem.queryComponentTyped(HealthComponent);
|
||||
|
||||
healthEntities.forEach(({ entity, component: health }) => {
|
||||
if (health.isDead()) {
|
||||
// 发送死亡事件
|
||||
if (entity.tag === 1) { // 玩家
|
||||
this.emitter.emit(GameEvents.PLAYER_DIED, entity);
|
||||
}
|
||||
|
||||
entity.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用定时器
|
||||
|
||||
框架提供了强大的定时器系统:
|
||||
|
||||
```typescript
|
||||
class GameManager {
|
||||
// ... 之前的代码 ...
|
||||
|
||||
public startGame(): void {
|
||||
// 创建玩家
|
||||
this.createPlayer();
|
||||
|
||||
// 每2秒生成一个敌人
|
||||
Core.schedule(2.0, true, this, (timer) => {
|
||||
const x = Math.random() * 800;
|
||||
const y = Math.random() * 600;
|
||||
const enemy = this.createEnemy(x, y);
|
||||
this.emitter.emit(GameEvents.ENEMY_SPAWNED, enemy);
|
||||
});
|
||||
|
||||
// 5秒后增加敌人生成速度
|
||||
Core.schedule(5.0, false, this, (timer) => {
|
||||
console.log('游戏难度提升!');
|
||||
// 可以在这里修改敌人生成间隔
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
## 完整示例
|
||||
|
||||
以下是一个完整的小游戏示例,展示了框架的主要功能:
|
||||
|
||||
```typescript
|
||||
// 导入框架
|
||||
import { Core } from './Core';
|
||||
import { Entity } from './ECS/Entity';
|
||||
import { Component } from './ECS/Component';
|
||||
import { Scene } from './ECS/Scene';
|
||||
import { QuerySystem } from './ECS/Core/QuerySystem';
|
||||
import { Emitter } from './Utils/Emitter';
|
||||
|
||||
// 定义组件
|
||||
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 HealthComponent extends Component {
|
||||
constructor(public maxHealth: number = 100) {
|
||||
super();
|
||||
this.currentHealth = maxHealth;
|
||||
}
|
||||
|
||||
public currentHealth: number;
|
||||
|
||||
public takeDamage(damage: number): void {
|
||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
||||
}
|
||||
|
||||
public isDead(): boolean {
|
||||
return this.currentHealth <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 游戏事件
|
||||
enum GameEvents {
|
||||
PLAYER_DIED = 'playerDied',
|
||||
ENEMY_SPAWNED = 'enemySpawned'
|
||||
}
|
||||
|
||||
// 完整的游戏管理器
|
||||
class SimpleGame {
|
||||
private core: Core;
|
||||
private scene: Scene;
|
||||
private querySystem: QuerySystem;
|
||||
private emitter: Emitter;
|
||||
private isRunning: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.core = Core.create(true);
|
||||
this.scene = new Scene();
|
||||
this.scene.name = "SimpleGame";
|
||||
this.querySystem = this.scene.querySystem;
|
||||
this.emitter = Core.emitter;
|
||||
|
||||
// 设置场景
|
||||
Core.scene = this.scene;
|
||||
|
||||
// 监听事件
|
||||
this.emitter.on(GameEvents.PLAYER_DIED, () => {
|
||||
console.log('游戏结束!');
|
||||
this.isRunning = false;
|
||||
});
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
console.log('游戏开始!');
|
||||
this.isRunning = true;
|
||||
|
||||
// 创建玩家
|
||||
this.createPlayer();
|
||||
|
||||
// 定期生成敌人
|
||||
Core.schedule(2.0, true, this, (timer) => {
|
||||
if (this.isRunning) {
|
||||
this.createEnemy();
|
||||
}
|
||||
});
|
||||
|
||||
// 启动游戏循环
|
||||
this.gameLoop();
|
||||
}
|
||||
|
||||
private createPlayer(): Entity {
|
||||
const player = this.scene.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(400, 300));
|
||||
player.addComponent(new VelocityComponent(100, 0));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
player.tag = 1; // 玩家标签
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
private createEnemy(): Entity {
|
||||
const enemy = this.scene.createEntity("Enemy");
|
||||
const x = Math.random() * 800;
|
||||
const y = Math.random() * 600;
|
||||
|
||||
enemy.addComponent(new PositionComponent(x, y));
|
||||
enemy.addComponent(new VelocityComponent(-50, 0));
|
||||
enemy.addComponent(new HealthComponent(50));
|
||||
enemy.tag = 2; // 敌人标签
|
||||
|
||||
this.emitter.emit(GameEvents.ENEMY_SPAWNED, enemy);
|
||||
|
||||
return enemy;
|
||||
}
|
||||
|
||||
private update(deltaTime: number): void {
|
||||
// 更新场景
|
||||
this.scene.update();
|
||||
|
||||
// 更新游戏系统
|
||||
this.updateMovement(deltaTime);
|
||||
this.updateCollision();
|
||||
this.updateHealth();
|
||||
}
|
||||
|
||||
private updateMovement(deltaTime: number): void {
|
||||
const movableEntities = this.querySystem.queryTwoComponents(
|
||||
PositionComponent,
|
||||
VelocityComponent
|
||||
);
|
||||
|
||||
movableEntities.forEach(({ entity, component1: pos, component2: vel }) => {
|
||||
pos.x += vel.x * deltaTime;
|
||||
pos.y += vel.y * deltaTime;
|
||||
|
||||
// 边界检查
|
||||
if (pos.x < 0 || pos.x > 800) vel.x = -vel.x;
|
||||
if (pos.y < 0 || pos.y > 600) vel.y = -vel.y;
|
||||
});
|
||||
}
|
||||
|
||||
private updateCollision(): void {
|
||||
const players = this.scene.findEntitiesByTag(1);
|
||||
const enemies = this.scene.findEntitiesByTag(2);
|
||||
|
||||
players.forEach(player => {
|
||||
const playerPos = player.getComponent(PositionComponent);
|
||||
const playerHealth = player.getComponent(HealthComponent);
|
||||
|
||||
if (!playerPos || !playerHealth) return;
|
||||
|
||||
enemies.forEach(enemy => {
|
||||
const enemyPos = enemy.getComponent(PositionComponent);
|
||||
if (!enemyPos) return;
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(playerPos.x - enemyPos.x, 2) +
|
||||
Math.pow(playerPos.y - enemyPos.y, 2)
|
||||
);
|
||||
|
||||
if (distance < 50) {
|
||||
playerHealth.takeDamage(10);
|
||||
console.log(`碰撞!玩家生命值: ${playerHealth.currentHealth}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private updateHealth(): void {
|
||||
const healthEntities = this.querySystem.queryComponentTyped(HealthComponent);
|
||||
const deadEntities: Entity[] = [];
|
||||
|
||||
healthEntities.forEach(({ entity, component: health }) => {
|
||||
if (health.isDead()) {
|
||||
deadEntities.push(entity);
|
||||
|
||||
if (entity.tag === 1) { // 玩家死亡
|
||||
this.emitter.emit(GameEvents.PLAYER_DIED, entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 移除死亡实体
|
||||
deadEntities.forEach(entity => {
|
||||
entity.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
private gameLoop(): void {
|
||||
let lastTime = performance.now();
|
||||
|
||||
const loop = () => {
|
||||
if (!this.isRunning) return;
|
||||
|
||||
const currentTime = performance.now();
|
||||
const deltaTime = (currentTime - lastTime) / 1000;
|
||||
lastTime = currentTime;
|
||||
|
||||
this.update(deltaTime);
|
||||
|
||||
requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
loop();
|
||||
}
|
||||
}
|
||||
|
||||
// 启动游戏
|
||||
const game = new SimpleGame();
|
||||
game.start();
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
现在您已经掌握了 ECS Framework 的基础用法,可以继续学习:
|
||||
|
||||
- [核心概念](core-concepts.md) - 深入了解 ECS 架构和设计原理
|
||||
- [查询系统使用指南](query-system-usage.md) - 学习高性能查询系统的详细用法
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何在不同游戏引擎中集成?
|
||||
|
||||
A: ECS Framework 是引擎无关的,您只需要:
|
||||
1. 将框架源码复制到项目中
|
||||
2. 在游戏引擎的主循环中调用 `scene.update()`
|
||||
3. 根据需要集成渲染、输入等引擎特定功能
|
||||
|
||||
### Q: 如何处理输入?
|
||||
|
||||
A: 框架本身不提供输入处理,建议:
|
||||
1. 创建一个输入组件来存储输入状态
|
||||
2. 在游戏循环中更新输入状态
|
||||
3. 在相关组件中读取输入状态并处理
|
||||
|
||||
### Q: 如何调试?
|
||||
|
||||
A: 框架提供了多种调试功能:
|
||||
- 使用 `entity.getDebugInfo()` 查看实体信息
|
||||
- 使用 `querySystem.getPerformanceReport()` 查看查询性能
|
||||
- 使用 `querySystem.getStats()` 查看详细统计信息
|
||||
|
||||
### Q: 性能如何优化?
|
||||
|
||||
A: 框架已经内置了多种性能优化:
|
||||
- 使用位掩码进行快速组件匹配
|
||||
- 多级索引系统加速查询
|
||||
- 智能缓存减少重复计算
|
||||
- 批量操作减少开销
|
||||
|
||||
建议定期调用 `querySystem.optimizeIndexes()` 来自动优化配置。
|
||||
@@ -1,80 +0,0 @@
|
||||
# 如何开始
|
||||
|
||||
## 初始化框架
|
||||
|
||||
```typescript
|
||||
// 创建调试模式下的`Core`实例
|
||||
const core = es.Core.create();
|
||||
|
||||
// 创建非调试模式下的`Core`实例
|
||||
const core = es.Core.create(false);
|
||||
```
|
||||
|
||||
## 分发帧事件
|
||||
|
||||
```typescript
|
||||
// dt 是一个可选参数,如果传入了 es.Time.deltaTime或者不传入参数,则代表使用框架的内置的时间差来更新游戏状态;
|
||||
// 如果传入了游戏引擎自带的 deltaTime,则代表使用该值来更新游戏状态。
|
||||
// 在 es.Core.update 方法中,会根据 dt 的值来计算时间戳信息,并更新全局管理器和当前场景的状态
|
||||
es.Core.emitter.emit(es.CoreEvents.frameUpdated, dt);
|
||||
```
|
||||
|
||||
> 尽可能使用引擎的dt,以免再游戏暂停继续时由于dt导致的跳帧问题
|
||||
|
||||
> **您还需要一个默认的场景以使得游戏可以进行使用ecs框架以及物理类或tween系统**
|
||||
|
||||
## 创建场景类
|
||||
|
||||
场景类需要继承框架中的 `es.Scene`
|
||||
|
||||
```typescript
|
||||
/** 示例场景 */
|
||||
export class MainScene extends Scene {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化场景,添加实体和组件
|
||||
*
|
||||
* 这个方法会在场景被创建时被调用。我们在这个方法中创建了一个实体,
|
||||
* 并向它添加了一个SpriteRender组件和一个TransformMove组件。
|
||||
*/
|
||||
public initialize() {
|
||||
// 创建一个实体
|
||||
let entity = this.createEntity("Player");
|
||||
|
||||
// 添加一个SpriteRender组件,用于显示实体的图像
|
||||
let spriteRender = entity.addComponent(new SpriteRender());
|
||||
spriteRender.sprite = new es.Sprite(new es.Texture("player.png"));
|
||||
|
||||
// 添加一个TransformMove组件,用于移动实体
|
||||
let transformMove = entity.addComponent(new TransformMove());
|
||||
transformMove.speed = 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景开始运行时执行的操作
|
||||
*
|
||||
* 这个方法会在场景开始运行时被调用。我们在这个方法中输出一条消息表示场景已经开始运行。
|
||||
*/
|
||||
public onStart() {
|
||||
console.log("MainScene has started!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景被销毁时执行的操作
|
||||
*
|
||||
* 这个方法会在场景被销毁时被调用。我们在这个方法中输出一条消息表示场景已经被卸载。
|
||||
*/
|
||||
public unload() {
|
||||
console.log("MainScene has been unloaded!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
要想激活该场景需要通过核心类 `Core` 来设置当前 `MainScene` 为使用的场景
|
||||
|
||||
```typescript
|
||||
es.Core.scene = new MainScene();
|
||||
```
|
||||
@@ -1,73 +0,0 @@
|
||||
## 关于 Physics/Collision
|
||||
框架中的物理不是一个真实的物理模拟。它只提供了游戏物理。您可以执行一些操作,如检测碰撞器、重叠检查、碰撞检查、扫描测试等。不是一个完整的刚体模拟。
|
||||
|
||||
### Colliders 物理系统的根本
|
||||
没有Collider,在物理系统中什么也不会发生。 碰撞器存在于实体类中,有几种类型:BoxCollider,CircleCollider和PolygonCollider。 您可以像这样添加Collider:`entity.addComponent(new BoxCollider())`. 将碰撞器添加到Entity时,它们会自动添加到SpatialHash中。
|
||||
|
||||
### SpatialHash:你永远不会用到它,但它仍然对你很重要
|
||||
SpatialHash类是一个隐藏类,该类为您的游戏全局管理 `collider`。静态物理类是SpatialHash的公共包装器。 SpatialHash没有设置大小限制,用于快速进行碰撞/线投射/重叠检查。例如,如果你有一个英雄在世界各地移动,而不必检查每个对撞机(可能是数百个)是否发生碰撞,则只需向SpatialHash询问英雄附近的所有collider即可。这大大缩小了您的碰撞检查范围。
|
||||
|
||||
SpatialHash有一个可配置的方面,它可以极大地影响其性能:单元大小。 SpatialHash将空间分成一个网格,选择适当的网格大小可以将可能发生的碰撞查询减到最少。默认情况下,网格大小为100像素。您可以通过在创建场景之前设置`Physics.SpatialHashCellSize`来更改此设置。选择比您的平均玩家/敌人人数稍大的尺寸通常效果最佳。
|
||||
|
||||
### Physics 类
|
||||
物理类是物理的核心类。 您可以设置一些属性,例如前面提到的spatialHashCellSize,raycastsHitTriggers和raycastsStartInColliders。
|
||||
- linecast:从开始到结束投射一条线,并返回与layerMask相匹配的碰撞器的第一次命中
|
||||
- overlapRectangle:检查是否有任何collider在矩形区域内
|
||||
- overlapCircle:检查是否有任何collider在圆形区域内
|
||||
- boxcastBroadphase:返回边界与collider.bounds相交的所有碰撞器。 请注意,这是一个broadphase检查,因此它只检查边界,不执行单个碰撞器到碰撞器的检查!
|
||||
|
||||
会注意到上面提到的layerMask。 layerMask允许您确定与哪些碰撞器碰撞。 每个collider都可以设置其物理层,以便在查询物理系统时可以选择仅检索与传入的layerMask匹配的对撞机。 所有物理方法都接受默认为所有图层的图层蒙版参数。 使用此选项可以过滤冲突检查,并通过不执行不必要的冲突检查来使性能保持最佳状态。
|
||||
|
||||
### 使用物理系统
|
||||
射线检测对于检查敌人的视线、探测实体的空间环境、快速移动的子弹等各种事情都非常有用。下面是一个从头到尾投射线条的示例,如果击中某个物体,它只会记录数据:
|
||||
```ts
|
||||
const hit = es.Physics.linecast( start, end );
|
||||
if( hit.collider != null )
|
||||
console.log( `ray hit ${hit}, entity: {hit.collider.entity}`);
|
||||
```
|
||||
|
||||
我们使用了一些更先进的碰撞/重叠检查方法,如Minkowski和、分离轴定理和古老的三角法。这些都被包装在Collider类上的简单易用的方法中。让我们看一些例子。
|
||||
|
||||
第一个例子是处理碰撞的最简单方法。deltaMovement是您希望移动实体的量,通常是velocity*Time.deltaTime. collidesWithAny方法将检查所有碰撞并调整deltaMovement以解决任何碰撞。
|
||||
|
||||
```ts
|
||||
// 碰撞结果将包含一些非常有用的信息,例如被撞的collider,表面命中的法线和最小平移矢量(MTV)。 MTV可用于将碰撞实体直接移动到命中的碰撞器附近。
|
||||
let collisionResult = null;
|
||||
|
||||
// 进行检查以查看entity.getComponent(Collider)(实体上的第一个碰撞器)是否与场景中的任何其他碰撞器发生碰撞。请注意,如果您有多个碰撞器,则可以获取并遍历它们,而不是仅检查第一个碰撞器。
|
||||
if( entity.getComponent(es.Collider).collidesWithAny( deltaMovement, collisionResult ) )
|
||||
{
|
||||
// 记录CollisionResult。 您可能需要使用它来添加一些粒子效果或与您的游戏相关的任何其他内容。
|
||||
console.log( `collision result: ${collisionResult}` );
|
||||
}
|
||||
|
||||
// 将实体移到新位置。 已经调整了deltaMovement为我们解决冲突。
|
||||
entity.position = entity.position.add(deltaMovement);
|
||||
```
|
||||
|
||||
如果您需要对碰撞发生时的情况进行更多控制,则也可以手动检查是否与其他collider发生碰撞。 请注意,执行此操作时,deltaMovement不会为您调整。 解决冲突时,您需要考虑最小平移矢量。
|
||||
|
||||
```ts
|
||||
let collisionResult = null;
|
||||
|
||||
// 进行检查以查看entity.getComponent<Collider>是否与一些其他Collider发生碰撞
|
||||
if( entity.getComponent(es.Collider).collidesWith( someOtherCollider, deltaMovement, collisionResult ) )
|
||||
{
|
||||
// 将实体移动到与命中Collider相邻的位置,然后记录CollisionResult
|
||||
entity.position = entity.position.add(deltaMovement.sub(collisionResult.minimumTranslationVector));
|
||||
console.log( `collision result: ${collisionResult}` );
|
||||
}
|
||||
```
|
||||
我们可以使用前面提到的Physics.boxcastBroadphase方法或更具体地讲,将自身排除在查询之外的版本,使上述示例更进一步。 该方法将为我们提供场景中所有在我们附近的collider,然后我们可以使用这些对撞机进行实际的碰撞检查。
|
||||
|
||||
```ts
|
||||
// 在我们自身以外的位置获取可能与之重叠的任何东西
|
||||
let neighborColliders = es.Physics.boxcastBroadphaseExcludingSelf( entity.getComponent(es.Collider) );
|
||||
|
||||
// 遍历并检查每个对撞机是否重叠
|
||||
for( let collider of neighborColliders )
|
||||
{
|
||||
if( entity.getComponent(es.Collider).overlaps( collider ) )
|
||||
console.log( `我们正在重叠一个collider : ${collider}` );
|
||||
}
|
||||
```
|
||||
325
docs/query-system-usage.md
Normal file
325
docs/query-system-usage.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# QuerySystem 使用指南
|
||||
|
||||
QuerySystem 是 ECS Framework 中的高性能实体查询系统,支持多级索引、智能缓存和类型安全的查询操作。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 1. 获取查询系统
|
||||
|
||||
```typescript
|
||||
import { Scene } from './ECS/Scene';
|
||||
import { Entity } from './ECS/Entity';
|
||||
|
||||
// 创建场景,查询系统会自动创建
|
||||
const scene = new Scene();
|
||||
const querySystem = scene.querySystem;
|
||||
|
||||
// 或者从Core获取当前场景的查询系统
|
||||
import { Core } from './Core';
|
||||
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.queryAllTyped(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 healthEntities = querySystem.queryComponentTyped(HealthComponent);
|
||||
healthEntities.forEach(({ entity, component }) => {
|
||||
console.log(`实体 ${entity.name} 的生命值: ${component.value}`);
|
||||
});
|
||||
|
||||
// 查询两个组件类型
|
||||
const movableEntities = querySystem.queryTwoComponents(PositionComponent, VelocityComponent);
|
||||
movableEntities.forEach(({ entity, component1: position, component2: velocity }) => {
|
||||
// 更新位置
|
||||
position.x += velocity.x;
|
||||
position.y += velocity.y;
|
||||
});
|
||||
```
|
||||
|
||||
### 4. 使用查询构建器
|
||||
|
||||
```typescript
|
||||
// 创建复杂查询
|
||||
const query = querySystem.createQuery()
|
||||
.withAll(PositionComponent, RenderComponent)
|
||||
.without(HiddenComponent)
|
||||
.withTag(1) // 特定标签
|
||||
.orderByName()
|
||||
.limit(10);
|
||||
|
||||
const result = query.execute();
|
||||
|
||||
// 链式操作
|
||||
const visibleEnemies = querySystem.createQuery()
|
||||
.withAll(EnemyComponent, PositionComponent)
|
||||
.without(DeadComponent, HiddenComponent)
|
||||
.filter(entity => entity.name.startsWith('Boss'))
|
||||
.orderBy((a, b) => a.id - b.id);
|
||||
|
||||
// 迭代结果
|
||||
visibleEnemies.forEach((entity, index) => {
|
||||
console.log(`敌人 ${index}: ${entity.name}`);
|
||||
});
|
||||
```
|
||||
|
||||
### 5. 高级查询功能
|
||||
|
||||
```typescript
|
||||
// 复合查询
|
||||
const complexResult = querySystem.queryComplex(
|
||||
{
|
||||
type: QueryConditionType.ALL,
|
||||
componentTypes: [PositionComponent, VelocityComponent],
|
||||
mask: /* 位掩码 */
|
||||
},
|
||||
{
|
||||
type: QueryConditionType.NONE,
|
||||
componentTypes: [DeadComponent],
|
||||
mask: /* 位掩码 */
|
||||
}
|
||||
);
|
||||
|
||||
// 批量查询
|
||||
const batchResults = querySystem.batchQuery([
|
||||
{ type: QueryConditionType.ALL, componentTypes: [HealthComponent], mask: /* 位掩码 */ },
|
||||
{ type: QueryConditionType.ALL, componentTypes: [ManaComponent], mask: /* 位掩码 */ }
|
||||
]);
|
||||
|
||||
// 并行查询
|
||||
const parallelResults = await querySystem.parallelQuery([
|
||||
{ type: QueryConditionType.ALL, componentTypes: [PositionComponent], mask: /* 位掩码 */ },
|
||||
{ type: QueryConditionType.ALL, componentTypes: [VelocityComponent], mask: /* 位掩码 */ }
|
||||
]);
|
||||
```
|
||||
|
||||
## 场景级别的实体查询
|
||||
|
||||
除了使用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();
|
||||
|
||||
// 预热常用查询
|
||||
const commonQueries = [
|
||||
{ type: QueryConditionType.ALL, componentTypes: [PositionComponent], mask: /* 位掩码 */ },
|
||||
{ type: QueryConditionType.ALL, componentTypes: [VelocityComponent], mask: /* 位掩码 */ }
|
||||
];
|
||||
querySystem.warmUpCache(commonQueries);
|
||||
```
|
||||
|
||||
### 2. 索引优化
|
||||
|
||||
```typescript
|
||||
// 自动优化索引配置
|
||||
querySystem.optimizeIndexes();
|
||||
|
||||
// 获取性能统计
|
||||
const stats = querySystem.getStats();
|
||||
console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`);
|
||||
console.log(`实体数量: ${stats.entityCount}`);
|
||||
|
||||
// 获取详细性能报告
|
||||
const report = querySystem.getPerformanceReport();
|
||||
console.log(report);
|
||||
```
|
||||
|
||||
### 3. 查询监听和快照
|
||||
|
||||
```typescript
|
||||
// 监听查询结果变更
|
||||
const unwatch = querySystem.watchQuery(
|
||||
{ type: QueryConditionType.ALL, componentTypes: [EnemyComponent], mask: /* 位掩码 */ },
|
||||
(entities, changeType) => {
|
||||
console.log(`敌人实体${changeType}: ${entities.length}个`);
|
||||
}
|
||||
);
|
||||
|
||||
// 取消监听
|
||||
unwatch();
|
||||
|
||||
// 创建查询快照
|
||||
const snapshot1 = querySystem.createSnapshot(
|
||||
{ type: QueryConditionType.ALL, componentTypes: [PlayerComponent], mask: /* 位掩码 */ }
|
||||
);
|
||||
|
||||
// 稍后创建另一个快照
|
||||
const snapshot2 = querySystem.createSnapshot(
|
||||
{ type: QueryConditionType.ALL, componentTypes: [PlayerComponent], mask: /* 位掩码 */ }
|
||||
);
|
||||
|
||||
// 比较快照
|
||||
const diff = querySystem.compareSnapshots(snapshot1, snapshot2);
|
||||
console.log(`新增: ${diff.added.length}, 移除: ${diff.removed.length}`);
|
||||
```
|
||||
|
||||
## 实际使用示例
|
||||
|
||||
### 移动系统示例
|
||||
|
||||
```typescript
|
||||
import { EntitySystem } from './ECS/Systems/EntitySystem';
|
||||
|
||||
class MovementSystem extends EntitySystem {
|
||||
public update(): void {
|
||||
// 查询所有可移动的实体
|
||||
const movableEntities = this.scene.querySystem.queryTwoComponents(
|
||||
PositionComponent,
|
||||
VelocityComponent
|
||||
);
|
||||
|
||||
movableEntities.forEach(({ entity, component1: position, component2: velocity }) => {
|
||||
// 更新位置
|
||||
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 {
|
||||
public update(): void {
|
||||
// 获取所有具有碰撞器的实体
|
||||
const collidableEntities = this.scene.querySystem.queryTwoComponents(
|
||||
PositionComponent,
|
||||
ColliderComponent
|
||||
);
|
||||
|
||||
// 检测碰撞
|
||||
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.entity, entityB.entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private checkCollision(entityA: any, entityB: any): boolean {
|
||||
// 简单的距离检测
|
||||
const posA = entityA.component1;
|
||||
const posB = entityB.component1;
|
||||
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 {
|
||||
public update(): void {
|
||||
// 查询所有具有生命值的实体
|
||||
const healthEntities = this.scene.querySystem.queryComponentTyped(HealthComponent);
|
||||
const deadEntities: Entity[] = [];
|
||||
|
||||
healthEntities.forEach(({ entity, component: health }) => {
|
||||
// 检查死亡
|
||||
if (health.currentHealth <= 0) {
|
||||
deadEntities.push(entity);
|
||||
}
|
||||
});
|
||||
|
||||
// 移除死亡实体
|
||||
deadEntities.forEach(entity => {
|
||||
console.log(`实体 ${entity.name} 已死亡`);
|
||||
entity.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 查询优化
|
||||
|
||||
- 尽量使用类型安全的查询方法
|
||||
- 避免在每帧都创建新的查询
|
||||
- 合理使用缓存机制
|
||||
|
||||
### 2. 性能监控
|
||||
|
||||
- 定期检查查询性能报告
|
||||
- 监控缓存命中率
|
||||
- 优化频繁使用的查询
|
||||
|
||||
### 3. 内存管理
|
||||
|
||||
- 及时清理不需要的查询监听器
|
||||
- 合理设置缓存大小
|
||||
- 避免创建过多的查询快照
|
||||
|
||||
### 4. 代码组织
|
||||
|
||||
- 将复杂查询封装到专门的方法中
|
||||
- 使用查询构建器创建可读性更好的查询
|
||||
- 在系统中合理组织查询逻辑
|
||||
@@ -1,2 +0,0 @@
|
||||
## 渲染框架
|
||||
为了方便快速与引擎对接使用,框架内置了一套渲染框架,它仅仅只有接口,你需要实现框架所需要的内容才可以进行渲染。
|
||||
@@ -1,71 +0,0 @@
|
||||
# scene_component
|
||||
这是一个场景组件的基类,如果您需要一个不在实体上的组件则继承它 `es.SceneComponent`。场景组件默认包含`update`/`onEnabled`/`onDisabled`/`onRemovedFromScene`,你可以对他们进行重载。
|
||||
|
||||
```typescript
|
||||
export class ASceneComponent extends es.SceneComponent {
|
||||
/**
|
||||
* 在启用此SceneComponent时调用
|
||||
*/
|
||||
onEnabled() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 当禁用此SceneComponent时调用
|
||||
*/
|
||||
onDisabled() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 当该SceneComponent从场景中移除时调用
|
||||
*/
|
||||
onRemovedFromScene() {
|
||||
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 场景组件需要添加至场景上, 通过场景中的 `addSceneComponent` 方法加入。
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
const aSceneCom = this.addSceneComponent(new ASceneComponent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 如果想要获取该场景组件则通过`getSceneComponent`方法获取
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
const aSceneCom = this.getSceneComponent(ASceneComponent);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 如果获取时发现没有可以自动创建则通过 `getOrCreateSceneComponent` 方法
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
const aSceneCom = this.getOrCreateSceneComponent(ASceneComponent);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 删除场景组件
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
this.removeSceneComponent(aSceneCom);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,67 +0,0 @@
|
||||
# system
|
||||
系统是ecs的核心。你的游戏逻辑应该在这里进行处理,所有的实体及组件也会在这里进行集中处理。用于处理实体的系统叫做 `es.EntityProcessingSystem`。 你需要继承他并实现`processEntity(entity: Entity)`方法。
|
||||
|
||||
```typescript
|
||||
export class ASystem extends es.EntityProcessingSystem {
|
||||
processEntity(entity: Entity){
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
系统也依赖于场景,如果想要系统被激活则需要使用场景中`addEntityProcessor`方法。系统被实例化需要传入一个`es.Matcher` 参数。
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
this.addEntityProcessor(new ASystem(es.Matcher.empty().all(AComponent)));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Matcher
|
||||
matcher是系统的匹配器,用于匹配满足条件的实体传入系统进行处理。如果想要一个空的匹配器则直接 `es.Matcher.empty()`
|
||||
|
||||
- all
|
||||
同时拥有多个组件
|
||||
|
||||
```typescript
|
||||
es.Matcher.empty().all(AComponent, BComponent);
|
||||
```
|
||||
|
||||
- one
|
||||
拥有任意一个组件
|
||||
|
||||
```typescript
|
||||
es.Matcher.empty().one(AComponent, BComponent);
|
||||
```
|
||||
|
||||
- exclude
|
||||
拥有某些组件,并且不包含某些组件
|
||||
```typescript
|
||||
// 不包含CComponent或者DComponent
|
||||
es.Matcher.empty().all(AComponent, BComponent).exclude(CComponent, DComponent);
|
||||
|
||||
// 不同时包含CComponent和DComponent
|
||||
es.Matcher.empty().all(AComponent, BComponent).exclude(CComponent).exclude(DComponent);
|
||||
```
|
||||
|
||||
## 获取系统
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
const aSystem = this.getEntityProcessor(ASystem);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 移除系统
|
||||
|
||||
```typescript
|
||||
export class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
this.removeEntityProcessor(aSystem);
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/time.md
23
docs/time.md
@@ -1,23 +0,0 @@
|
||||
# Time
|
||||
游戏中会经常使用到关于时间类。框架内提供了关于时间的多个属性
|
||||
|
||||
## 游戏运行的总时间
|
||||
`es.Time.totalTime`
|
||||
|
||||
## deltaTime的未缩放版本。不受时间尺度的影响
|
||||
`es.Time.unscaledDeltaTime`
|
||||
|
||||
## 前一帧到当前帧的时间增量(按时间刻度进行缩放)
|
||||
`es.Time.deltaTime`
|
||||
|
||||
## 时间刻度缩放
|
||||
`es.Time.timeScale`
|
||||
|
||||
## DeltaTime可以为的最大值
|
||||
`es.Time.maxDeltaTime` 默认为Number.MAX_VALUE
|
||||
|
||||
## 已传递的帧总数
|
||||
`es.Time.frameCount`
|
||||
|
||||
## 自场景加载以来的总时间
|
||||
`es.Time.timeSinceSceneLoad`
|
||||
Reference in New Issue
Block a user