升级项目框架,移除大部分无用的物理和tween系统
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
/source/node_modules
|
||||
/source/bin
|
||||
/demo/bin-debug
|
||||
/demo/bin-release
|
||||
/.idea
|
||||
|
||||
@@ -1,117 +1,295 @@
|
||||
ecs-framework 的目标是成为功能强大的框架。它为您构建游戏提供了坚实的基础。它包括的许多功能包括:
|
||||
# ECS Framework
|
||||
|
||||
- 完整的场景/实体/组件系统
|
||||
- SpatialHash是一种空间散列数据结构,用于加速2D物理引擎的碰撞检测,它能够将物体分割为多个小区域并快速查询每个区域内包含的物体,从而大幅度提高碰撞检测的效率。
|
||||
- AABB,圆和多边形碰撞/触发检测
|
||||
- 高效的协程,可在多个帧或动画定时中分解大型任务(Core.startCoroutine)
|
||||
- 通过Astar和广度优先搜索提供寻路支持,以查找图块地图或您自己的自定义格式 ( 参见 https://github.com/esengine/ecs-astar )
|
||||
- tween系统。任何number / Vector / 矩形/字段或属性都可以tween。
|
||||
- 针对核心事件的优化的事件发射器(发射器类),您也可以将其添加到自己的任何类中
|
||||
- 延迟和重复任务的调度程序(核心调度方法)
|
||||
[](https://badge.fury.io/js/%40esengine%2Fecs-framework)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
# 快速开始
|
||||
- [运行框架](docs/getting_start.md)
|
||||
- [创建实体与组件](docs/create_entity_component.md)
|
||||
- [创建系统](docs/system.md)
|
||||
- [全局时间Time](docs/time.md)
|
||||
- [协程Coroutine](docs/coroutine.md)
|
||||
- [Physics](docs/physics.md)
|
||||
- [Emitter](docs/emitter.md)
|
||||
- [Collision](docs/collision.md)
|
||||
一个轻量级的 TypeScript ECS(Entity-Component-System)框架,专为小游戏开发设计,适用于 Laya、Cocos 等游戏引擎。
|
||||
|
||||
## 交流群
|
||||
点击链接加入群聊【ecs游戏框架交流】:https://jq.qq.com/?_wv=1027&k=29w1Nud6
|
||||
## ✨ 特性
|
||||
|
||||
- 🚀 **轻量级 ECS 架构** - 基于实体组件系统,提供清晰的代码结构
|
||||
- 📡 **事件系统** - 内置 Emitter 事件发射器,支持类型安全的事件管理
|
||||
- ⏰ **定时器系统** - 完整的定时器管理,支持延迟和重复任务
|
||||
- 🔍 **查询系统** - 基于位掩码的高性能实体查询
|
||||
- 🛠️ **性能监控** - 内置性能监控工具,帮助优化游戏性能
|
||||
- 🎯 **对象池** - 内存管理优化,减少垃圾回收压力
|
||||
- 📊 **数学库** - 完整的 2D 数学运算支持
|
||||
|
||||
## Scene/Entity/Component
|
||||
Scene表示游戏场景,是所有实体和组件的容器;Entity表示游戏场景中的实体,是组件的容器;Component表示游戏实体中的组件,包含实体的具体行为逻辑。
|
||||
## 📦 安装
|
||||
|
||||
### Scene
|
||||
这是一个ECS(Entity-Component-System)框架的场景类,用于管理游戏中的实体和处理器。它具有以下特点:
|
||||
```bash
|
||||
npm install @esengine/ecs-framework
|
||||
```
|
||||
|
||||
- 维护了一个实体列表和一个实体处理器列表,以便在游戏中轻松添加、更新和删除实体。
|
||||
- 具有实体、组件和处理器的基本结构,可帮助您组织代码并实现分离的关注点。
|
||||
- 可以添加和删除场景组件,这是一个特殊类型的组件,可用于实现场景范围的逻辑。
|
||||
- 通过使用实体处理器,可以轻松地处理实体的更新和渲染。
|
||||
- 可以搜索场景中的实体、组件和处理器,并根据需要添加或删除它们。
|
||||
- 可以为实体分配唯一的标识符,以便在处理实体时轻松地跟踪它们。
|
||||
## 🚀 快速开始
|
||||
|
||||
### Entity
|
||||
Entity 是ECS中的一个基础概念,它代表了游戏中的实体。每个 Entity 都有唯一的 ID 和名称,可以在一个 Scene 中被创建、添加、删除和管理。
|
||||
### 1. 初始化框架
|
||||
|
||||
在一个 Entity 中,可以添加多个 Component 来实现不同的功能。例如,Transform Component 用来表示实体的位置、旋转和缩放等变换属性,其他自定义的 Component 则可以实现各种具体的游戏逻辑。
|
||||
```typescript
|
||||
import { Core, CoreEvents } from '@esengine/ecs-framework';
|
||||
|
||||
在 Entity 中,可以通过以下方法来管理 Component:
|
||||
// 创建 Core 实例
|
||||
const core = Core.create(true); // true 表示开启调试模式
|
||||
|
||||
- createComponent(componentType: new (...args) => T): T 用来创建并返回一个指定类型的 Component 实例。
|
||||
- addComponent<T extends Component>(component: T): T 用来添加一个已有的 Component 实例。
|
||||
- getComponent<T extends Component>(type: new (...args) => T): T 用来获取指定类型的 Component 实例。
|
||||
- getComponentInScene<T extends Component>(type: new (...args) => T): T 用来获取指定类型的 Component 实例,但会在整个场景中搜索。
|
||||
- tryGetComponent<T extends Component>(type: new (...args) => T, outComponent: Ref<T>): boolean 用来尝试获取指定类型的 Component 实例,并将结果存储在传入的 outComponent 参数中。
|
||||
- hasComponent<T extends Component>(type: new (...args) => T): boolean 用来检查 Entity 是否有指定类型的 Component 实例。
|
||||
- getOrCreateComponent<T extends Component>(type: new (...args) => T): T 如果 Entity 没有指定类型的 Component 实例,将会创建一个并返回。
|
||||
此外,还可以通过 removeComponent() 方法来移除一个指定的 Component 实例,或者 removeAllComponents() 方法来移除 Entity 中的所有 Component 实例。
|
||||
// 在游戏循环中更新框架
|
||||
function gameLoop() {
|
||||
// 发送帧更新事件
|
||||
Core.emitter.emit(CoreEvents.frameUpdated);
|
||||
}
|
||||
```
|
||||
|
||||
最后,在 Entity 中还可以通过 Tween 类来实现各种动画效果,例如 tweenPositionTo()、tweenScaleTo() 和 tweenRotationDegreesTo() 等方法。
|
||||
### 2. 创建场景
|
||||
|
||||
以下是Entity类中一些关键和重要的属性:
|
||||
```typescript
|
||||
import { Scene, Vector2, EntitySystem } from '@esengine/ecs-framework';
|
||||
|
||||
- scene:实体所属的场景对象。
|
||||
- name:实体的名称。
|
||||
- id:实体的唯一标识符。
|
||||
- transform:实体的变换组件。
|
||||
- components:实体的组件列表。
|
||||
- updateInterval:实体更新间隔,用于控制实体的更新频率。
|
||||
- componentBits:用于标记实体拥有哪些组件。
|
||||
这些属性是Entity类中非常重要的,它们可以用来操作和控制实体的行为和状态。其中,transform和components属性是实体最为关键的组成部分,前者用于操作实体的位置、旋转和缩放等变换信息,后者用于添加、删除、查询和更新实体的组件信息。通过这些属性,我们可以非常方便地构建出自己的游戏对象,并实现一些基本的功能,如移动、碰撞、动画、音效等。
|
||||
class GameScene extends Scene {
|
||||
public initialize() {
|
||||
// 创建玩家实体
|
||||
const player = this.createEntity("Player");
|
||||
|
||||
### Component
|
||||
这是一个用于构建实体组件系统的基本组件类,所有其他组件都应该从这个抽象类派生。每个组件都可以与一个实体相关联,可以通过实体来访问其它组件。
|
||||
// 设置位置
|
||||
player.position = new Vector2(100, 100);
|
||||
|
||||
重要属性:
|
||||
// 添加自定义组件
|
||||
const movement = player.addComponent(new MovementComponent());
|
||||
|
||||
- id: 组件的唯一标识符。
|
||||
- entity: 附加此组件的实体。
|
||||
重要方法:
|
||||
// 添加系统
|
||||
this.addEntityProcessor(new MovementSystem());
|
||||
}
|
||||
|
||||
- addComponent<T extends Component>(component: T): T:将指定的组件添加到此组件所在的实体中。
|
||||
- getComponent<T extends Component>(type: new (...args: any[]) => T): T:获取指定类型的组件。
|
||||
- getComponents(typeName: any, componentList?: any[]): any[]:获取指定类型的组件数组。
|
||||
- hasComponent(type: new (...args: any[]) => Component): boolean:判断实体是否包含指定类型的组件。
|
||||
- removeComponent(component?: Component): void:从此组件所在的实体中删除指定的组件,如果未指定组件,则删除此组件本身。
|
||||
此外,组件还具有一些生命周期方法,例如 initialize()、onAddedToEntity()、onRemovedFromEntity()、onEntityTransformChanged()、onEnabled()、onDisabled() 等,这些方法可以根据需要在派生类中进行重写,以便在实体上添加或移除组件时执行相应的操作。
|
||||
public onStart() {
|
||||
console.log("游戏场景已启动");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前场景
|
||||
Core.scene = new GameScene();
|
||||
```
|
||||
|
||||
## Debug
|
||||
这是一个静态类 Debug,它包含了一些方法用于打印调试信息。其中包含的方法如下:
|
||||
### 3. 创建组件
|
||||
|
||||
- warnIf(condition: boolean, format: string, ...args: any[]): 如果 condition 为 true,则打印警告信息。
|
||||
- warn(format: string, ...args: any[]): 打印警告信息。
|
||||
- error(format: string, ...args: any[]): 打印错误信息。
|
||||
- log(type: LogType, format: string, ...args: any[]): 打印指定类型的信息,可选的类型有 error、warn、log、info 和 trace。
|
||||
该类的优点是它提供了一种统一的调试信息输出方式,可以帮助开发者更方便地输出调试信息,以便在调试时更快地定位问题。缺点是它的功能比较单一,只能输出调试信息,不能对调试信息进行更加复杂的处理。
|
||||
```typescript
|
||||
import { Component, Vector2, Time } from '@esengine/ecs-framework';
|
||||
|
||||
## Flags
|
||||
这是一个静态工具类 Flags,提供了一些位标志操作的方法:
|
||||
class MovementComponent extends Component {
|
||||
public speed: number = 100;
|
||||
public direction: Vector2 = Vector2.zero;
|
||||
|
||||
- isFlagSet:检查位标志是否已在数值中设置(该标志未移位)
|
||||
- isUnshiftedFlagSet:检查位标志是否在数值中设置(该标志已移位)
|
||||
- setFlagExclusive:设置数值标志位,移除所有已经设置的标志
|
||||
- setFlag:设置标志位
|
||||
- unsetFlag:取消标志位
|
||||
- invertFlags:反转数值集合位
|
||||
- binaryStringRepresentation:打印 number 的二进制表示,方便调试 number 标志。
|
||||
public update() {
|
||||
if (this.direction.length > 0) {
|
||||
const movement = this.direction.multiply(this.speed * Time.deltaTime);
|
||||
this.entity.position = this.entity.position.add(movement);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 如何参与项目
|
||||
#### Node.js版本
|
||||
v10.20.1
|
||||
#### 操作步骤
|
||||
1. 进入source目录
|
||||
2. 安装package包: `npm install`
|
||||
3. 打包成js: `gulp build`
|
||||
> 如遇到gulp未找到则先执行 `npm install gulp -g`
|
||||
### 4. 创建系统
|
||||
|
||||
## 扩展库
|
||||
```typescript
|
||||
import { EntitySystem, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
#### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar)
|
||||
#### [基于ecs-framework开发的AI(BehaviourTree、UtilityAI)系统](https://github.com/esengine/BehaviourTree-ai)
|
||||
class MovementSystem extends EntitySystem {
|
||||
protected process(entities: Entity[]) {
|
||||
for (const entity of entities) {
|
||||
const movement = entity.getComponent(MovementComponent);
|
||||
if (movement) {
|
||||
movement.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 核心概念
|
||||
|
||||
### Entity(实体)
|
||||
实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性,可以添加组件来扩展功能。
|
||||
|
||||
```typescript
|
||||
import { Vector2 } from '@esengine/ecs-framework';
|
||||
|
||||
const entity = scene.createEntity("MyEntity");
|
||||
entity.position = new Vector2(100, 200);
|
||||
entity.rotation = Math.PI / 4;
|
||||
entity.scale = new Vector2(2, 2);
|
||||
```
|
||||
|
||||
### Component(组件)
|
||||
组件包含数据和行为,定义了实体的特性。
|
||||
|
||||
```typescript
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public maxHealth: number = 100;
|
||||
public currentHealth: number = 100;
|
||||
|
||||
public takeDamage(damage: number) {
|
||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
||||
if (this.currentHealth <= 0) {
|
||||
this.entity.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### System(系统)
|
||||
系统处理实体集合,实现游戏逻辑。
|
||||
|
||||
```typescript
|
||||
import { EntitySystem, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
class HealthSystem extends EntitySystem {
|
||||
protected process(entities: Entity[]) {
|
||||
for (const entity of entities) {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
if (health && health.currentHealth <= 0) {
|
||||
entity.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎮 高级功能
|
||||
|
||||
### 事件系统
|
||||
|
||||
```typescript
|
||||
import { Core, CoreEvents } from '@esengine/ecs-framework';
|
||||
|
||||
// 监听事件
|
||||
Core.emitter.addObserver(CoreEvents.frameUpdated, this.onFrameUpdate, this);
|
||||
|
||||
// 发射自定义事件
|
||||
Core.emitter.emit("playerDied", { player: entity, score: 1000 });
|
||||
|
||||
// 移除监听
|
||||
Core.emitter.removeObserver(CoreEvents.frameUpdated, this.onFrameUpdate);
|
||||
```
|
||||
|
||||
### 定时器系统
|
||||
|
||||
```typescript
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
|
||||
// 延迟执行
|
||||
Core.schedule(2.0, false, this, (timer) => {
|
||||
console.log("2秒后执行");
|
||||
});
|
||||
|
||||
// 重复执行
|
||||
Core.schedule(1.0, true, this, (timer) => {
|
||||
console.log("每秒执行一次");
|
||||
});
|
||||
```
|
||||
|
||||
### 实体查询
|
||||
|
||||
```typescript
|
||||
// 按名称查找
|
||||
const player = scene.findEntity("Player");
|
||||
|
||||
// 按标签查找
|
||||
const enemies = scene.findEntitiesByTag(1);
|
||||
|
||||
// 按ID查找
|
||||
const entity = scene.findEntityById(123);
|
||||
```
|
||||
|
||||
### 性能监控
|
||||
|
||||
```typescript
|
||||
import { PerformanceMonitor } from '@esengine/ecs-framework';
|
||||
|
||||
// 获取性能数据
|
||||
const monitor = PerformanceMonitor.instance;
|
||||
console.log("平均FPS:", monitor.averageFPS);
|
||||
console.log("内存使用:", monitor.memoryUsage);
|
||||
```
|
||||
|
||||
## 🛠️ 开发工具
|
||||
|
||||
### 对象池
|
||||
|
||||
```typescript
|
||||
// 创建对象池
|
||||
class BulletPool extends es.Pool<Bullet> {
|
||||
protected createObject(): Bullet {
|
||||
return new Bullet();
|
||||
}
|
||||
}
|
||||
|
||||
const bulletPool = new BulletPool();
|
||||
|
||||
// 获取对象
|
||||
const bullet = bulletPool.obtain();
|
||||
|
||||
// 释放对象
|
||||
bulletPool.free(bullet);
|
||||
```
|
||||
|
||||
### 实体调试
|
||||
|
||||
```typescript
|
||||
// 获取实体调试信息
|
||||
const debugInfo = entity.getDebugInfo();
|
||||
console.log("实体信息:", debugInfo);
|
||||
|
||||
// 获取场景统计
|
||||
const stats = scene.getStats();
|
||||
console.log("场景统计:", stats);
|
||||
```
|
||||
|
||||
## 📖 文档
|
||||
|
||||
- [快速入门](docs/getting-started.md) - 从零开始学习框架使用
|
||||
- [核心概念](docs/core-concepts.md) - 深入了解 ECS 架构和设计原理
|
||||
- [查询系统使用指南](docs/query-system-usage.md) - 学习高性能查询系统的详细用法
|
||||
|
||||
## 🔗 扩展库
|
||||
|
||||
- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、广度优先、Dijkstra、GOAP 算法
|
||||
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI 系统
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
### 开发环境设置
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/esengine/ecs-framework.git
|
||||
|
||||
# 进入源码目录
|
||||
cd ecs-framework/source
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 构建项目
|
||||
npm run build
|
||||
|
||||
# 运行测试
|
||||
npm test
|
||||
```
|
||||
|
||||
### 构建要求
|
||||
|
||||
- Node.js >= 14.0.0
|
||||
- TypeScript >= 4.0.0
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 [MIT](LICENSE) 许可证。
|
||||
|
||||
## 💬 交流群
|
||||
|
||||
加入 QQ 群讨论:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
|
||||
|
||||
---
|
||||
|
||||
**ECS Framework** - 让游戏开发更简单、更高效!
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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}` );
|
||||
}
|
||||
```
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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`
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
},
|
||||
"modules": "auto"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# 编译输出目录
|
||||
bin/
|
||||
|
||||
# 依赖
|
||||
node_modules/
|
||||
|
||||
# 日志文件
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# 环境文件
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# 编辑器配置
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 测试覆盖率
|
||||
coverage/
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
@@ -0,0 +1,57 @@
|
||||
# 源码文件(只发布编译后的文件)
|
||||
src/
|
||||
tsconfig.json
|
||||
gulpfile.js
|
||||
build.config.js
|
||||
.babelrc
|
||||
|
||||
# 开发工具配置
|
||||
.vscode/
|
||||
.idea/
|
||||
.wing/
|
||||
|
||||
# 依赖和缓存
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# 测试文件
|
||||
*.test.js
|
||||
*.test.ts
|
||||
*.spec.js
|
||||
*.spec.ts
|
||||
test/
|
||||
tests/
|
||||
__tests__/
|
||||
coverage/
|
||||
|
||||
# 构建工具
|
||||
.nyc_output
|
||||
.cache
|
||||
|
||||
# 环境文件
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# 编辑器文件
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 其他
|
||||
.git/
|
||||
.gitignore
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Vendored
-6954
File diff suppressed because it is too large
Load Diff
-17052
File diff suppressed because it is too large
Load Diff
Vendored
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* ECS框架构建配置
|
||||
* 针对Laya、Cocos等游戏引擎优化
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
// 输入配置
|
||||
input: {
|
||||
entry: './src/index.ts',
|
||||
tsconfig: './tsconfig.json'
|
||||
},
|
||||
|
||||
// 输出配置
|
||||
output: {
|
||||
dir: './bin',
|
||||
formats: ['es', 'cjs', 'umd'], // ES模块、CommonJS、UMD
|
||||
filename: {
|
||||
es: 'ecs-framework.esm.js',
|
||||
cjs: 'ecs-framework.cjs.js',
|
||||
umd: 'ecs-framework.umd.js'
|
||||
},
|
||||
minify: true,
|
||||
sourcemap: true
|
||||
},
|
||||
|
||||
// 游戏引擎特定配置
|
||||
engines: {
|
||||
laya: {
|
||||
// Laya引擎特定优化
|
||||
target: 'es5',
|
||||
polyfills: ['Promise', 'Object.assign'],
|
||||
globals: ['Laya']
|
||||
},
|
||||
|
||||
cocos: {
|
||||
// Cocos引擎特定优化
|
||||
target: 'es6',
|
||||
polyfills: [],
|
||||
globals: ['cc']
|
||||
}
|
||||
},
|
||||
|
||||
// 小游戏平台优化
|
||||
platforms: {
|
||||
wechat: {
|
||||
// 微信小游戏优化
|
||||
maxSize: '4MB',
|
||||
treeshaking: true,
|
||||
compression: 'gzip'
|
||||
},
|
||||
alipay: {
|
||||
// 支付宝小游戏优化
|
||||
maxSize: '4MB',
|
||||
treeshaking: true,
|
||||
compression: 'gzip'
|
||||
},
|
||||
bytedance: {
|
||||
// 字节跳动小游戏优化
|
||||
maxSize: '4MB',
|
||||
treeshaking: true,
|
||||
compression: 'gzip'
|
||||
}
|
||||
},
|
||||
|
||||
// 性能优化配置
|
||||
optimization: {
|
||||
// 启用Tree Shaking
|
||||
treeshaking: true,
|
||||
// 代码分割
|
||||
codeSplitting: false, // 小游戏通常不需要代码分割
|
||||
// 压缩配置
|
||||
minify: {
|
||||
removeComments: true,
|
||||
removeConsole: false, // 保留console用于调试
|
||||
removeDebugger: true
|
||||
},
|
||||
// 内联小文件
|
||||
inlineThreshold: 1024
|
||||
},
|
||||
|
||||
// 开发配置
|
||||
development: {
|
||||
sourcemap: true,
|
||||
hotReload: false, // 小游戏不支持热重载
|
||||
debugMode: true
|
||||
},
|
||||
|
||||
// 生产配置
|
||||
production: {
|
||||
sourcemap: false,
|
||||
minify: true,
|
||||
optimization: true,
|
||||
bundleAnalyzer: true
|
||||
}
|
||||
};
|
||||
+134
-1
@@ -5,7 +5,15 @@ const terser = require('gulp-terser');
|
||||
const inject = require('gulp-inject-string');
|
||||
const ts = require('gulp-typescript');
|
||||
const merge = require('merge2');
|
||||
const tsProject = ts.createProject('tsconfig.json');
|
||||
const typescript = require('gulp-typescript');
|
||||
const concat = require('gulp-concat');
|
||||
const replace = require('gulp-string-replace');
|
||||
|
||||
// TypeScript项目配置
|
||||
const tsProject = ts.createProject('tsconfig.json', {
|
||||
module: 'es2020',
|
||||
target: 'es2018'
|
||||
});
|
||||
|
||||
function buildJs() {
|
||||
return tsProject.src()
|
||||
@@ -24,6 +32,131 @@ function buildDts() {
|
||||
.pipe(gulp.dest('./bin'));
|
||||
}
|
||||
|
||||
// 构建ES模块版本
|
||||
gulp.task('build-esm', function() {
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(tsProject())
|
||||
.pipe(concat('ecs-framework.esm.js'))
|
||||
.pipe(inject.prepend('// ECS Framework - ES Module Version\n'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 构建UMD版本(兼容各种游戏引擎)
|
||||
gulp.task('build-umd', function() {
|
||||
const umdProject = ts.createProject('tsconfig.json', {
|
||||
module: 'umd',
|
||||
target: 'es5'
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(umdProject())
|
||||
.pipe(concat('ecs-framework.umd.js'))
|
||||
.pipe(inject.prepend(`// ECS Framework - UMD Version
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = global || self, factory(global.ECS = {}));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
`))
|
||||
.pipe(inject.append('\n})));'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 构建Laya引擎专用版本
|
||||
gulp.task('build-laya', function() {
|
||||
const layaProject = ts.createProject('tsconfig.json', {
|
||||
module: 'none',
|
||||
target: 'es5'
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(layaProject())
|
||||
.pipe(concat('ecs-framework.laya.js'))
|
||||
.pipe(replace(/export\s+/g, ''))
|
||||
.pipe(inject.prepend(`// ECS Framework - Laya Engine Version
|
||||
var ECS = ECS || {};
|
||||
(function(ECS) {
|
||||
`))
|
||||
.pipe(inject.append('\n})(ECS);'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 构建Cocos引擎专用版本
|
||||
gulp.task('build-cocos', function() {
|
||||
const cocosProject = ts.createProject('tsconfig.json', {
|
||||
module: 'es2020',
|
||||
target: 'es2018'
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(cocosProject())
|
||||
.pipe(concat('ecs-framework.cocos.js'))
|
||||
.pipe(inject.prepend('// ECS Framework - Cocos Engine Version\n'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 压缩所有构建文件
|
||||
gulp.task('minify', function() {
|
||||
return gulp.src(['bin/*.js', '!bin/*.min.js'])
|
||||
.pipe(terser({
|
||||
compress: {
|
||||
drop_console: false, // 保留console用于调试
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.log'] // 可选择性移除console.log
|
||||
},
|
||||
mangle: {
|
||||
keep_fnames: true // 保留函数名用于调试
|
||||
},
|
||||
format: {
|
||||
comments: false
|
||||
}
|
||||
}))
|
||||
.pipe(concat(function(file) {
|
||||
return file.basename.replace('.js', '.min.js');
|
||||
}))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 生成类型定义文件
|
||||
gulp.task('build-types', function() {
|
||||
const dtsProject = ts.createProject('tsconfig.json', {
|
||||
declaration: true,
|
||||
emitDeclarationOnly: true
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(dtsProject())
|
||||
.pipe(concat('ecs-framework.d.ts'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 清理构建目录
|
||||
gulp.task('clean', function() {
|
||||
const del = require('del');
|
||||
return del(['bin/**/*']);
|
||||
});
|
||||
|
||||
// 开发构建(快速,包含源码映射)
|
||||
gulp.task('build-dev', gulp.series('clean', 'build-esm', 'build-types'));
|
||||
|
||||
// 生产构建(完整,包含所有版本和压缩)
|
||||
gulp.task('build', gulp.series(
|
||||
'clean',
|
||||
gulp.parallel('build-esm', 'build-umd', 'build-laya', 'build-cocos'),
|
||||
'build-types',
|
||||
'minify'
|
||||
));
|
||||
|
||||
// 监听文件变化
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/**/*.ts', gulp.series('build-dev'));
|
||||
});
|
||||
|
||||
// 默认任务
|
||||
gulp.task('default', gulp.series('build'));
|
||||
|
||||
exports.buildJs = buildJs;
|
||||
exports.buildDts = buildDts;
|
||||
exports.build = series(buildJs, buildDts);
|
||||
Generated
+389
-4
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@esengine/egret-framework",
|
||||
"version": "1.0.1",
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.0.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@esengine/egret-framework",
|
||||
"version": "1.0.1",
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.0.4",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.4",
|
||||
@@ -21,6 +21,7 @@
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"merge2": "^1.4.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"tsify": "^5.0.4",
|
||||
"typedoc": "^0.28.5",
|
||||
"typescript": "^5.8.3",
|
||||
@@ -1563,6 +1564,109 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
@@ -1627,6 +1731,17 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.6.0.tgz",
|
||||
@@ -2815,6 +2930,37 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn/node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-browserify": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz",
|
||||
@@ -3057,6 +3203,13 @@
|
||||
"node": ">= 10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.165",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz",
|
||||
@@ -3427,6 +3580,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/from": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
|
||||
@@ -4815,6 +4985,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -5148,6 +5334,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
@@ -5352,6 +5548,13 @@
|
||||
"shell-quote": "^1.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
@@ -5459,6 +5662,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
@@ -5499,6 +5712,30 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pause-stream": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||
@@ -5990,6 +6227,69 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^10.3.7"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ripemd160": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
@@ -6112,6 +6412,29 @@
|
||||
"fast-safe-stringify": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
@@ -6201,6 +6524,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
@@ -6425,6 +6761,22 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
@@ -6438,6 +6790,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
|
||||
@@ -7286,6 +7652,25 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
+36
-10
@@ -1,14 +1,39 @@
|
||||
{
|
||||
"name": "@esengine/egret-framework",
|
||||
"version": "1.0.1",
|
||||
"description": "用于egret 包含众多高性能方法以供使用",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.0.4",
|
||||
"description": "用于Laya、Cocos等游戏引擎的高性能ECS框架",
|
||||
"main": "bin/index.js",
|
||||
"types": "bin/index.d.ts",
|
||||
"files": [
|
||||
"bin/**/*",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"keywords": [
|
||||
"ecs",
|
||||
"entity-component-system",
|
||||
"game-engine",
|
||||
"typescript",
|
||||
"laya",
|
||||
"cocos",
|
||||
"egret"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "mocha --recursive --reporter tap --growl",
|
||||
"eslint": "eslint src --ext .ts"
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc --watch",
|
||||
"clean": "rimraf bin",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint:fix": "eslint src --ext .ts --fix",
|
||||
"prepublishOnly": "npm run rebuild",
|
||||
"publish:patch": "npm run rebuild && npm version patch && npm publish",
|
||||
"publish:minor": "npm run rebuild && npm version minor && npm publish",
|
||||
"publish:major": "npm run rebuild && npm version major && npm publish",
|
||||
"pack:check": "npm run rebuild && npm pack --dry-run",
|
||||
"check": "node scripts/check-publish.js"
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
@@ -25,6 +50,7 @@
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"merge2": "^1.4.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"tsify": "^5.0.4",
|
||||
"typedoc": "^0.28.5",
|
||||
"typescript": "^5.8.3",
|
||||
@@ -32,7 +58,7 @@
|
||||
"watchify": "^4.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com/359807859@qq.com"
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('🔍 发布前检查...\n');
|
||||
|
||||
// 检查必要文件
|
||||
const requiredFiles = [
|
||||
'package.json',
|
||||
'README.md',
|
||||
'LICENSE',
|
||||
'bin/index.js',
|
||||
'bin/index.d.ts'
|
||||
];
|
||||
|
||||
let allFilesExist = true;
|
||||
|
||||
requiredFiles.forEach(file => {
|
||||
if (fs.existsSync(file)) {
|
||||
console.log(`✅ ${file} 存在`);
|
||||
} else {
|
||||
console.log(`❌ ${file} 不存在`);
|
||||
allFilesExist = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 检查package.json配置
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
|
||||
console.log('\n📦 Package.json 检查:');
|
||||
console.log(`✅ 包名: ${packageJson.name}`);
|
||||
console.log(`✅ 版本: ${packageJson.version}`);
|
||||
console.log(`✅ 主入口: ${packageJson.main}`);
|
||||
console.log(`✅ 类型定义: ${packageJson.types}`);
|
||||
|
||||
// 检查bin目录
|
||||
if (fs.existsSync('bin')) {
|
||||
const binFiles = fs.readdirSync('bin', { recursive: true });
|
||||
const jsFiles = binFiles.filter(f => f.endsWith('.js')).length;
|
||||
const dtsFiles = binFiles.filter(f => f.endsWith('.d.ts')).length;
|
||||
|
||||
console.log(`\n🏗️ 编译文件检查:`);
|
||||
console.log(`✅ JavaScript 文件: ${jsFiles} 个`);
|
||||
console.log(`✅ 类型定义文件: ${dtsFiles} 个`);
|
||||
} else {
|
||||
console.log('\n❌ bin 目录不存在,请先运行 npm run build');
|
||||
allFilesExist = false;
|
||||
}
|
||||
|
||||
// 检查git状态
|
||||
const { execSync } = require('child_process');
|
||||
try {
|
||||
const gitStatus = execSync('git status --porcelain', { encoding: 'utf8' });
|
||||
if (gitStatus.trim()) {
|
||||
console.log('\n⚠️ Git 状态检查:');
|
||||
console.log('有未提交的更改,建议先提交代码');
|
||||
} else {
|
||||
console.log('\n✅ Git 状态: 工作目录干净');
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('\n⚠️ 无法检查git状态');
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
|
||||
if (allFilesExist) {
|
||||
console.log('🎉 所有检查通过!可以发布了');
|
||||
console.log('\n发布命令:');
|
||||
console.log(' npm run publish:patch # 补丁版本');
|
||||
console.log(' npm run publish:minor # 次要版本');
|
||||
console.log(' npm run publish:major # 主要版本');
|
||||
} else {
|
||||
console.log('❌ 检查失败,请修复问题后再发布');
|
||||
process.exit(1);
|
||||
}
|
||||
+316
-241
@@ -1,262 +1,337 @@
|
||||
module es {
|
||||
import { Emitter } from './Utils/Emitter';
|
||||
import { CoreEvents } from './ECS/CoreEvents';
|
||||
import { GlobalManager } from './Utils/GlobalManager';
|
||||
import { TimerManager } from './Utils/Timers/TimerManager';
|
||||
import { ITimer } from './Utils/Timers/ITimer';
|
||||
import { Time } from './Utils/Time';
|
||||
import { PerformanceMonitor } from './Utils/PerformanceMonitor';
|
||||
import { PoolManager } from './Utils/Pool';
|
||||
import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI';
|
||||
import { Scene } from './ECS/Scene';
|
||||
|
||||
/**
|
||||
* 游戏引擎核心类
|
||||
*
|
||||
* 负责管理游戏的生命周期、场景切换、全局管理器和定时器系统。
|
||||
* 提供统一的游戏循环和事件分发机制。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 创建核心实例
|
||||
* const core = Core.create(true);
|
||||
*
|
||||
* // 设置场景
|
||||
* Core.scene = new MyScene();
|
||||
*
|
||||
* // 调度定时器
|
||||
* Core.schedule(1.0, false, null, (timer) => {
|
||||
* console.log("1秒后执行");
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class Core {
|
||||
/**
|
||||
* 全局核心类
|
||||
* 核心事件发射器
|
||||
*
|
||||
* 用于发布和订阅核心级别的事件,如帧更新、场景切换等。
|
||||
*/
|
||||
export class Core {
|
||||
/**
|
||||
* 核心发射器。只发出核心级别的事件
|
||||
*/
|
||||
public static emitter: Emitter<CoreEvents>;
|
||||
public static paused = false;
|
||||
/**
|
||||
* 是否启用调试渲染
|
||||
*/
|
||||
public static debugRenderEndabled = false;
|
||||
/**
|
||||
* 简化对内部类的全局内容实例的访问
|
||||
*/
|
||||
private static _instance: Core;
|
||||
/**
|
||||
* 用于确定是否应该使用EntitySystems
|
||||
*/
|
||||
public static entitySystemsEnabled: boolean;
|
||||
/**
|
||||
* 是否正在debug模式
|
||||
* 仅允许在create时进行更改
|
||||
*/
|
||||
public readonly debug: boolean;
|
||||
public _nextScene: Scene;
|
||||
public _sceneTransition: SceneTransition;
|
||||
public static emitter: Emitter<CoreEvents>;
|
||||
|
||||
/**
|
||||
* 用于凝聚GraphicsDeviceReset事件
|
||||
*/
|
||||
public _graphicsDeviceChangeTimer: ITimer;
|
||||
/**
|
||||
* 全局访问系统
|
||||
*/
|
||||
public _globalManagers: GlobalManager[] = [];
|
||||
public _coroutineManager: CoroutineManager = new CoroutineManager();
|
||||
public _timerManager: TimerManager = new TimerManager();
|
||||
/**
|
||||
* 游戏暂停状态
|
||||
*
|
||||
* 当设置为true时,游戏循环将暂停执行。
|
||||
*/
|
||||
public static paused = false;
|
||||
|
||||
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
|
||||
Core._instance = this;
|
||||
Core.emitter = new Emitter<CoreEvents>();
|
||||
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
|
||||
/**
|
||||
* 全局核心实例
|
||||
*/
|
||||
private static _instance: Core;
|
||||
|
||||
Core.registerGlobalManager(this._coroutineManager);
|
||||
Core.registerGlobalManager(new TweenManager());
|
||||
Core.registerGlobalManager(this._timerManager);
|
||||
Core.entitySystemsEnabled = enableEntitySystems;
|
||||
/**
|
||||
* 实体系统启用状态
|
||||
*
|
||||
* 控制是否启用ECS实体系统功能。
|
||||
*/
|
||||
public static entitySystemsEnabled: boolean;
|
||||
|
||||
this.debug = debug;
|
||||
this.initialize();
|
||||
}
|
||||
/**
|
||||
* 调试模式标志
|
||||
*
|
||||
* 在调试模式下会启用额外的性能监控和错误检查。
|
||||
*/
|
||||
public readonly debug: boolean;
|
||||
|
||||
/**
|
||||
* 提供对单例/游戏实例的访问
|
||||
* @constructor
|
||||
*/
|
||||
public static get Instance() {
|
||||
return this._instance;
|
||||
}
|
||||
/**
|
||||
* 待切换的场景
|
||||
*
|
||||
* 存储下一帧要切换到的场景实例。
|
||||
*/
|
||||
public _nextScene: Scene | null = null;
|
||||
|
||||
public _frameCounterElapsedTime: number = 0;
|
||||
public _frameCounter: number = 0;
|
||||
public _totalMemory: number = 0;
|
||||
public _titleMemory: (totalMemory: number, frameCounter: number) => void;
|
||||
public _scene: Scene;
|
||||
/**
|
||||
* 全局管理器集合
|
||||
*
|
||||
* 存储所有注册的全局管理器实例。
|
||||
*/
|
||||
public _globalManagers: GlobalManager[] = [];
|
||||
|
||||
/**
|
||||
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
|
||||
*/
|
||||
public static get scene() {
|
||||
if (!this._instance)
|
||||
return null;
|
||||
return this._instance._scene;
|
||||
}
|
||||
/**
|
||||
* 定时器管理器
|
||||
*
|
||||
* 负责管理所有的游戏定时器。
|
||||
*/
|
||||
public _timerManager: TimerManager;
|
||||
|
||||
/**
|
||||
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
|
||||
* @param value
|
||||
*/
|
||||
public static set scene(value: Scene) {
|
||||
Insist.isNotNull(value, "场景不能为空");
|
||||
/**
|
||||
* 性能监控器
|
||||
*
|
||||
* 监控游戏性能并提供优化建议。
|
||||
*/
|
||||
public _performanceMonitor: PerformanceMonitor;
|
||||
|
||||
if (this._instance._scene == null) {
|
||||
this._instance._scene = value;
|
||||
this._instance.onSceneChanged();
|
||||
this._instance._scene.begin();
|
||||
} else {
|
||||
this._instance._nextScene = value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 对象池管理器
|
||||
*
|
||||
* 管理所有对象池的生命周期。
|
||||
*/
|
||||
public _poolManager: PoolManager;
|
||||
|
||||
/**
|
||||
* `Core`类的静态方法,用于创建`Core`的实例。
|
||||
* @param debug {boolean} 是否为调试模式,默认为`true`
|
||||
* @returns {Core} `Core`的实例
|
||||
*/
|
||||
public static create(debug: boolean = true): Core {
|
||||
// 如果实例还未被创建,则创建一个新的实例并保存在`_instance`静态属性中
|
||||
if (this._instance == null) {
|
||||
this._instance = new es.Core(debug);
|
||||
}
|
||||
// 返回`_instance`静态属性中保存的实例
|
||||
return this._instance;
|
||||
}
|
||||
/**
|
||||
* ECS流式API
|
||||
*
|
||||
* 提供便捷的ECS操作接口。
|
||||
*/
|
||||
public _ecsAPI?: ECSFluentAPI;
|
||||
|
||||
/**
|
||||
* 添加一个全局管理器对象,它的更新方法将调用场景前的每一帧。
|
||||
* @param manager
|
||||
*/
|
||||
public static registerGlobalManager(manager: es.GlobalManager) {
|
||||
this._instance._globalManagers.push(manager);
|
||||
manager.enabled = true;
|
||||
}
|
||||
/**
|
||||
* 当前活动场景
|
||||
*/
|
||||
public _scene?: Scene;
|
||||
|
||||
/**
|
||||
* 删除全局管理器对象
|
||||
* @param manager
|
||||
*/
|
||||
public static unregisterGlobalManager(manager: GlobalManager) {
|
||||
new es.List(this._instance._globalManagers).remove(manager);
|
||||
manager.enabled = false;
|
||||
}
|
||||
/**
|
||||
* 创建核心实例
|
||||
*
|
||||
* @param debug - 是否启用调试模式,默认为true
|
||||
* @param enableEntitySystems - 是否启用实体系统,默认为true
|
||||
*/
|
||||
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
|
||||
Core._instance = this;
|
||||
Core.emitter = new Emitter<CoreEvents>();
|
||||
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
|
||||
|
||||
/**
|
||||
* 获取指定类型的全局管理器实例
|
||||
* @param type 管理器类型的构造函数
|
||||
* @returns 指定类型的全局管理器实例,如果找不到则返回 null
|
||||
*/
|
||||
public static getGlobalManager<T extends GlobalManager>(type: new (...args) => T): T {
|
||||
for (let i = 0, s = Core._instance._globalManagers.length; i < s; ++i) {
|
||||
let manager = Core._instance._globalManagers[i];
|
||||
if (manager instanceof type)
|
||||
return manager;
|
||||
}
|
||||
// 初始化管理器
|
||||
this._timerManager = new TimerManager();
|
||||
Core.registerGlobalManager(this._timerManager);
|
||||
|
||||
// 初始化性能监控器
|
||||
this._performanceMonitor = PerformanceMonitor.instance;
|
||||
|
||||
// 初始化对象池管理器
|
||||
this._poolManager = PoolManager.getInstance();
|
||||
|
||||
Core.entitySystemsEnabled = enableEntitySystems;
|
||||
this.debug = debug;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核心实例
|
||||
*
|
||||
* @returns 全局核心实例
|
||||
*/
|
||||
public static get Instance() {
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活动的场景
|
||||
*
|
||||
* @returns 当前场景实例,如果没有则返回null
|
||||
*/
|
||||
public static get scene(): Scene | null {
|
||||
if (!this._instance)
|
||||
return null;
|
||||
return this._instance._scene || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前活动的场景
|
||||
*
|
||||
* 如果当前没有场景,会立即切换;否则会在下一帧切换。
|
||||
*
|
||||
* @param value - 要设置的场景实例
|
||||
* @throws {Error} 当场景为空时抛出错误
|
||||
*/
|
||||
public static set scene(value: Scene | null) {
|
||||
if (!value) return;
|
||||
|
||||
if (!value) {
|
||||
throw new Error("场景不能为空");
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时运行SceneTransition,允许一个场景平滑过渡到另一个场景,并具有自定义效果
|
||||
* @param sceneTransition
|
||||
*/
|
||||
public static startSceneTransition<T extends SceneTransition>(sceneTransition: T) {
|
||||
Insist.isNull(this._instance._sceneTransition, "在前一个场景转换完成之前,无法启动新的场景转换");
|
||||
this._instance._sceneTransition = sceneTransition;
|
||||
return sceneTransition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动一个coroutine。Coroutine可以将number延时几秒或延时到其他startCoroutine.Yielding
|
||||
* null将使coroutine在下一帧被执行。
|
||||
* @param enumerator
|
||||
*/
|
||||
public static startCoroutine(enumerator): ICoroutine {
|
||||
return this._instance._coroutineManager.startCoroutine(enumerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度一个一次性或重复的计时器,该计时器将调用已传递的动作
|
||||
* @param timeInSeconds
|
||||
* @param repeats
|
||||
* @param context
|
||||
* @param onTime
|
||||
*/
|
||||
public static schedule(timeInSeconds: number, repeats: boolean = false, context: any = null, onTime: (timer: ITimer) => void) {
|
||||
return this._instance._timerManager.schedule(timeInSeconds, repeats, context, onTime);
|
||||
}
|
||||
|
||||
public startDebugDraw() {
|
||||
// 如果debug标志未开启,则直接返回
|
||||
if (!this.debug) return;
|
||||
|
||||
// 计算帧率和内存使用情况
|
||||
this._frameCounter++; // 帧计数器递增
|
||||
this._frameCounterElapsedTime += Time.deltaTime; // 帧计数器累加时间
|
||||
if (this._frameCounterElapsedTime >= 1) { // 如果时间已经超过1秒,则计算帧率和内存使用情况
|
||||
let memoryInfo = window.performance["memory"]; // 获取内存使用情况
|
||||
if (memoryInfo != null) { // 如果内存使用情况存在
|
||||
// 计算内存使用情况并保留2位小数
|
||||
this._totalMemory = Number((memoryInfo.totalJSHeapSize / 1048576).toFixed(2));
|
||||
}
|
||||
if (this._titleMemory) { // 如果回调函数存在,则执行回调函数,更新标题栏显示
|
||||
this._titleMemory(this._totalMemory, this._frameCounter);
|
||||
}
|
||||
this._frameCounter = 0; // 重置帧计数器
|
||||
this._frameCounterElapsedTime -= 1; // 减去1秒时间
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一个场景结束后,下一个场景开始之前调用
|
||||
*/
|
||||
public onSceneChanged() {
|
||||
Core.emitter.emit(CoreEvents.sceneChanged);
|
||||
Time.sceneChanged();
|
||||
}
|
||||
|
||||
protected initialize() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* `Core` 类的受保护的 `update` 方法,用于更新游戏状态。
|
||||
* @param currentTime 当前时间戳,单位为毫秒,默认值为-1。
|
||||
*/
|
||||
protected update(currentTime: number = -1): void {
|
||||
// 如果引擎处于暂停状态,则直接返回,不做任何操作
|
||||
if (Core.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新时间戳信息
|
||||
Time.update(currentTime, currentTime !== -1);
|
||||
|
||||
// 更新全局管理器和当前场景
|
||||
if (this._scene != null) {
|
||||
// 依次更新所有启用的全局管理器
|
||||
for (const globalManager of this._globalManagers) {
|
||||
if (globalManager.enabled) {
|
||||
globalManager.update();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前没有场景切换正在进行,或者正在进行的场景切换不需要加载新场景
|
||||
if (this._sceneTransition == null || !this._sceneTransition._loadsNewScene) {
|
||||
this._scene.update();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理场景切换
|
||||
if (this._nextScene != null) {
|
||||
// 结束当前场景
|
||||
this._scene.end();
|
||||
|
||||
// 加载并初始化新场景
|
||||
this._scene = this._nextScene;
|
||||
this._nextScene = null;
|
||||
this.onSceneChanged();
|
||||
this._scene.begin();
|
||||
}
|
||||
|
||||
// 绘制调试信息
|
||||
this.startDebugDraw();
|
||||
this.draw();
|
||||
}
|
||||
|
||||
protected draw() {
|
||||
if (this._sceneTransition != null)
|
||||
this._sceneTransition.preRender();
|
||||
|
||||
if (this._sceneTransition != null && !this._sceneTransition.hasPreviousSceneRender) {
|
||||
if (this._scene != null) {
|
||||
Core.startCoroutine(this._sceneTransition.onBeginTransition());
|
||||
}
|
||||
|
||||
this._sceneTransition.render();
|
||||
}
|
||||
if (this._instance._scene == null) {
|
||||
this._instance._scene = value;
|
||||
this._instance.onSceneChanged();
|
||||
this._instance._scene.begin();
|
||||
} else {
|
||||
this._instance._nextScene = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Core实例
|
||||
*
|
||||
* 如果实例已存在,则返回现有实例。
|
||||
*
|
||||
* @param debug - 是否为调试模式,默认为true
|
||||
* @returns Core实例
|
||||
*/
|
||||
public static create(debug: boolean = true): Core {
|
||||
if (this._instance == null) {
|
||||
this._instance = new Core(debug);
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册全局管理器
|
||||
*
|
||||
* 将管理器添加到全局管理器列表中,并启用它。
|
||||
*
|
||||
* @param manager - 要注册的全局管理器
|
||||
*/
|
||||
public static registerGlobalManager(manager: GlobalManager) {
|
||||
this._instance._globalManagers.push(manager);
|
||||
manager.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销全局管理器
|
||||
*
|
||||
* 从全局管理器列表中移除管理器,并禁用它。
|
||||
*
|
||||
* @param manager - 要注销的全局管理器
|
||||
*/
|
||||
public static unregisterGlobalManager(manager: GlobalManager) {
|
||||
this._instance._globalManagers.splice(this._instance._globalManagers.indexOf(manager), 1);
|
||||
manager.enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的全局管理器
|
||||
*
|
||||
* @param type - 管理器类型构造函数
|
||||
* @returns 管理器实例,如果未找到则返回null
|
||||
*/
|
||||
public static getGlobalManager<T extends GlobalManager>(type: new (...args: any[]) => T): T | null {
|
||||
for (const manager of this._instance._globalManagers) {
|
||||
if (manager instanceof type)
|
||||
return manager as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度定时器
|
||||
*
|
||||
* 创建一个定时器,在指定时间后执行回调函数。
|
||||
*
|
||||
* @param timeInSeconds - 延迟时间(秒)
|
||||
* @param repeats - 是否重复执行,默认为false
|
||||
* @param context - 回调函数的上下文,默认为null
|
||||
* @param onTime - 定时器触发时的回调函数
|
||||
* @returns 创建的定时器实例
|
||||
*/
|
||||
public static schedule(timeInSeconds: number, repeats: boolean = false, context: any = null, onTime: (timer: ITimer) => void) {
|
||||
return this._instance._timerManager.schedule(timeInSeconds, repeats, context, onTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ECS流式API
|
||||
*
|
||||
* @returns ECS API实例,如果未初始化则返回null
|
||||
*/
|
||||
public static get ecsAPI(): ECSFluentAPI | null {
|
||||
return this._instance?._ecsAPI || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景切换回调
|
||||
*
|
||||
* 在场景切换时调用,用于重置时间系统等。
|
||||
*/
|
||||
public onSceneChanged() {
|
||||
Time.sceneChanged();
|
||||
|
||||
// 初始化ECS API(如果场景支持)
|
||||
if (this._scene && typeof (this._scene as any).querySystem !== 'undefined') {
|
||||
const scene = this._scene as any;
|
||||
this._ecsAPI = createECSAPI(scene, scene.querySystem, scene.eventSystem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化核心系统
|
||||
*
|
||||
* 执行核心系统的初始化逻辑。
|
||||
*/
|
||||
protected initialize() {
|
||||
// 核心系统初始化
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏主循环更新
|
||||
*
|
||||
* 每帧调用,负责更新时间系统、全局管理器和当前场景。
|
||||
*
|
||||
* @param currentTime - 当前时间戳,默认为-1(使用系统时间)
|
||||
*/
|
||||
protected update(currentTime: number = -1): void {
|
||||
if (Core.paused) return;
|
||||
|
||||
// 开始性能监控
|
||||
const frameStartTime = this._performanceMonitor.startMonitoring('Core.update');
|
||||
|
||||
Time.update(currentTime);
|
||||
|
||||
// 更新FPS监控(如果性能监控器支持)
|
||||
if (typeof (this._performanceMonitor as any).updateFPS === 'function') {
|
||||
(this._performanceMonitor as any).updateFPS(Time.deltaTime);
|
||||
}
|
||||
|
||||
// 更新全局管理器
|
||||
const managersStartTime = this._performanceMonitor.startMonitoring('GlobalManagers.update');
|
||||
for (const globalManager of this._globalManagers) {
|
||||
if (globalManager.enabled)
|
||||
globalManager.update();
|
||||
}
|
||||
this._performanceMonitor.endMonitoring('GlobalManagers.update', managersStartTime, this._globalManagers.length);
|
||||
|
||||
// 更新对象池管理器
|
||||
this._poolManager.update();
|
||||
|
||||
// 处理场景切换
|
||||
if (this._nextScene != null) {
|
||||
if (this._scene != null)
|
||||
this._scene.end();
|
||||
|
||||
this._scene = this._nextScene;
|
||||
this._nextScene = null;
|
||||
this.onSceneChanged();
|
||||
this._scene.begin();
|
||||
}
|
||||
|
||||
// 更新当前场景
|
||||
if (this._scene != null && this._scene.update) {
|
||||
const sceneStartTime = this._performanceMonitor.startMonitoring('Scene.update');
|
||||
this._scene.update();
|
||||
const entityCount = (this._scene as any).entities?.count || 0;
|
||||
this._performanceMonitor.endMonitoring('Scene.update', sceneStartTime, entityCount);
|
||||
}
|
||||
|
||||
// 结束性能监控
|
||||
this._performanceMonitor.endMonitoring('Core.update', frameStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module es {
|
||||
export class DebugConsole {
|
||||
public static Instance: DebugConsole;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
module es {
|
||||
export enum LogType {
|
||||
error,
|
||||
warn,
|
||||
log,
|
||||
info,
|
||||
trace
|
||||
}
|
||||
|
||||
export class Debug {
|
||||
/**
|
||||
* 如果条件为true,则在控制台中以警告方式打印消息。
|
||||
* @param condition 是否应该打印消息的条件
|
||||
* @param format 要打印的消息格式
|
||||
* @param args 与消息格式相对应的参数列表
|
||||
*/
|
||||
public static warnIf(condition: boolean, format: string, ...args: any[]) {
|
||||
if (condition)
|
||||
this.log(LogType.warn, format, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在控制台中以警告方式打印消息。
|
||||
* @param format 要打印的消息格式
|
||||
* @param args 与消息格式相对应的参数列表
|
||||
*/
|
||||
public static warn(format: string, ...args: any[]) {
|
||||
this.log(LogType.warn, format, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在控制台中以错误方式打印消息。
|
||||
* @param format 要打印的消息格式
|
||||
* @param args 与消息格式相对应的参数列表
|
||||
*/
|
||||
public static error(format: string, ...args: any[]) {
|
||||
this.log(LogType.error, format, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在控制台中以标准日志方式打印消息。
|
||||
* @param type 要打印的日志类型
|
||||
* @param format 要打印的消息格式
|
||||
* @param args 与消息格式相对应的参数列表
|
||||
*/
|
||||
public static log(type: LogType, format: string, ...args: any[]) {
|
||||
switch (type) {
|
||||
case LogType.error:
|
||||
console.error(`${type}: ${StringUtils.format(format, args)}`);
|
||||
break;
|
||||
case LogType.warn:
|
||||
console.warn(`${type}: ${StringUtils.format(format, args)}`);
|
||||
break;
|
||||
case LogType.log:
|
||||
console.log(`${type}: ${StringUtils.format(format, args)}`);
|
||||
break;
|
||||
case LogType.info:
|
||||
console.info(`${type}: ${StringUtils.format(format, args)}`);
|
||||
break;
|
||||
case LogType.trace:
|
||||
console.trace(`${type}: ${StringUtils.format(format, args)}`);
|
||||
break;
|
||||
default:
|
||||
throw new Error('argument out of range');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 我们在这里存储了各种系统的默认颜色,如对撞机调试渲染、Debug.drawText等。
|
||||
* 命名方式尽可能采用CLASS-THING,以明确它的使用位置
|
||||
*/
|
||||
export class DebugDefault {
|
||||
public static debugText: number = 0xffffff;
|
||||
|
||||
public static colliderBounds: number = 0xffffff * 0.3;
|
||||
public static colliderEdge: number = 0x8B0000;
|
||||
public static colliderPosition: number = 0xFFFF00;
|
||||
public static colliderCenter: number = 0xFF0000;
|
||||
|
||||
public static renderableBounds: number = 0xFFFF00;
|
||||
public static renderableCenter: number = 0x9932CC;
|
||||
|
||||
public static verletParticle: number = 0xDC345E;
|
||||
public static verletConstraintEdge: number = 0x433E36;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
module es {
|
||||
export class Insist {
|
||||
public static fail(message: string = null, ...args: any[]) {
|
||||
if (!console.assert)
|
||||
return;
|
||||
|
||||
if (message == null) {
|
||||
console.assert(false);
|
||||
} else {
|
||||
console.assert(false, StringUtils.format(message, args));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static isTrue(condition: boolean, message: string = null, ...args: any[]) {
|
||||
if (!condition) {
|
||||
if (message == null) {
|
||||
this.fail();
|
||||
} else {
|
||||
this.fail(message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static isFalse(condition: boolean, message: string = null, ...args: any[]) {
|
||||
if (message == null) {
|
||||
this.isTrue(!condition);
|
||||
} else {
|
||||
this.isTrue(!condition, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static isNull(obj, message: string = null, ...args: any[]) {
|
||||
if (message == null) {
|
||||
this.isTrue(obj == null);
|
||||
} else {
|
||||
this.isTrue(obj == null, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static isNotNull(obj, message: string = null, ...args: any[]) {
|
||||
if (message == null) {
|
||||
this.isTrue(obj != null);
|
||||
} else {
|
||||
this.isTrue(obj != null, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static areEqual(first, second, message: string, ...args: any[]) {
|
||||
if (first != second)
|
||||
this.fail(message, args);
|
||||
}
|
||||
|
||||
public static areNotEqual(first, second, message: string, ...args: any[]) {
|
||||
if (first == second)
|
||||
this.fail(message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
-154
@@ -1,169 +1,156 @@
|
||||
module es {
|
||||
/**
|
||||
* 游戏组件基类
|
||||
*
|
||||
* ECS架构中的组件(Component),用于实现具体的游戏功能。
|
||||
* 组件包含数据和行为,可以被添加到实体上以扩展实体的功能。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class HealthComponent extends Component {
|
||||
* public health: number = 100;
|
||||
*
|
||||
* public takeDamage(damage: number): void {
|
||||
* this.health -= damage;
|
||||
* if (this.health <= 0) {
|
||||
* this.entity.destroy();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class Component {
|
||||
/**
|
||||
* 执行顺序
|
||||
* - onAddedToEntity
|
||||
* - OnEnabled
|
||||
* 组件ID生成器
|
||||
*
|
||||
* 删除执行顺序
|
||||
* - onRemovedFromEntity
|
||||
* 用于为每个组件分配唯一的ID。
|
||||
*/
|
||||
export abstract class Component {
|
||||
public static _idGenerator: number = 0;
|
||||
/**
|
||||
* 此组件的唯一标识
|
||||
*/
|
||||
public readonly id: number;
|
||||
/**
|
||||
* 此组件附加的实体
|
||||
*/
|
||||
public entity: Entity;
|
||||
public static _idGenerator: number = 0;
|
||||
|
||||
constructor() {
|
||||
this.id = Component._idGenerator++;
|
||||
}
|
||||
/**
|
||||
* 组件唯一标识符
|
||||
*
|
||||
* 在整个游戏生命周期中唯一的数字ID。
|
||||
*/
|
||||
public readonly id: number;
|
||||
|
||||
/**
|
||||
* 快速访问 this.entity.transform
|
||||
*/
|
||||
public get transform(): Transform {
|
||||
return this.entity.transform;
|
||||
}
|
||||
/**
|
||||
* 组件所属的实体
|
||||
*
|
||||
* 指向拥有此组件的实体实例。
|
||||
*/
|
||||
public entity!: Entity;
|
||||
|
||||
private _enabled: boolean = true;
|
||||
/**
|
||||
* 组件启用状态
|
||||
*
|
||||
* 控制组件是否参与更新循环。
|
||||
*/
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
|
||||
*/
|
||||
public get enabled() {
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
}
|
||||
/**
|
||||
* 更新顺序
|
||||
*
|
||||
* 决定组件在更新循环中的执行顺序。
|
||||
*/
|
||||
private _updateOrder: number = 0;
|
||||
|
||||
/**
|
||||
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
/**
|
||||
* 创建组件实例
|
||||
*
|
||||
* 自动分配唯一ID给组件。
|
||||
*/
|
||||
constructor() {
|
||||
this.id = Component._idGenerator++;
|
||||
}
|
||||
|
||||
private _updateOrder = 0;
|
||||
/**
|
||||
* 获取组件启用状态
|
||||
*
|
||||
* 组件的实际启用状态取决于自身状态和所属实体的状态。
|
||||
*
|
||||
* @returns 如果组件和所属实体都启用则返回true
|
||||
*/
|
||||
public get enabled(): boolean {
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
}
|
||||
|
||||
/** 更新此实体上组件的顺序 */
|
||||
public get updateOrder() {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/** 更新此实体上组件的顺序 */
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当此组件已分配其实体,但尚未添加到实体的活动组件列表时调用。有用的东西,如物理组件,需要访问转换来修改碰撞体的属性。
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在提交所有挂起的组件更改后,将该组件添加到场景时调用。此时,设置了实体字段和实体。场景也设定好了。
|
||||
*/
|
||||
public onAddedToEntity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当此组件从其实体中移除时调用。在这里做所有的清理工作。
|
||||
*/
|
||||
public onRemovedFromEntity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体的位置改变时调用。这允许组件知道它们由于父实体的移动而移动了。
|
||||
* @param comp
|
||||
*/
|
||||
public onEntityTransformChanged(comp: ComponentTransform) {
|
||||
}
|
||||
|
||||
/**
|
||||
*当父实体或此组件启用时调用
|
||||
*/
|
||||
public onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用父实体或此组件时调用
|
||||
*/
|
||||
public onDisabled() {
|
||||
}
|
||||
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setUpdateOrder(updateOrder: number) {
|
||||
if (this._updateOrder != updateOrder) {
|
||||
this._updateOrder = updateOrder;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加组件
|
||||
* @param component 要添加的组件实例
|
||||
* @returns 返回添加的组件实例
|
||||
*/
|
||||
public addComponent<T extends Component>(component: T): T {
|
||||
return this.entity.addComponent<T>(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件
|
||||
* @param type 组件类型
|
||||
* @returns 返回获取到的组件实例
|
||||
*/
|
||||
public getComponent<T extends Component>(type: new (...args: any[]) => T): T {
|
||||
return this.entity.getComponent<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一组指定类型的组件
|
||||
* @param typeName 组件类型名
|
||||
* @param componentList 可选参数,存储组件实例的数组
|
||||
* @returns 返回指定类型的组件实例数组
|
||||
*/
|
||||
public getComponents(typeName: any, componentList?: any[]): any[] {
|
||||
return this.entity.getComponents(typeName, componentList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断实体是否包含指定类型的组件
|
||||
* @param type 组件类型
|
||||
* @returns 如果实体包含指定类型的组件,返回 true,否则返回 false。
|
||||
*/
|
||||
public hasComponent(type: new (...args: any[]) => Component): boolean {
|
||||
return this.entity.hasComponent(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除组件
|
||||
* @param component 可选参数,要删除的组件实例。如果未指定该参数,则删除当前实例上的组件。
|
||||
*/
|
||||
public removeComponent(component?: Component): void {
|
||||
if (component) {
|
||||
this.entity.removeComponent(component);
|
||||
/**
|
||||
* 设置组件启用状态
|
||||
*
|
||||
* 当状态改变时会触发相应的生命周期回调。
|
||||
*
|
||||
* @param value - 新的启用状态
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
if (this._enabled !== value) {
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.entity.removeComponent(this);
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更新顺序
|
||||
*
|
||||
* @returns 组件的更新顺序值
|
||||
*/
|
||||
public get updateOrder(): number {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更新顺序
|
||||
*
|
||||
* @param value - 新的更新顺序值
|
||||
*/
|
||||
public set updateOrder(value: number) {
|
||||
this._updateOrder = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件添加到实体时的回调
|
||||
*
|
||||
* 当组件被添加到实体时调用,可以在此方法中进行初始化操作。
|
||||
*/
|
||||
public onAddedToEntity(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件从实体移除时的回调
|
||||
*
|
||||
* 当组件从实体中移除时调用,可以在此方法中进行清理操作。
|
||||
*/
|
||||
public onRemovedFromEntity(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件启用时的回调
|
||||
*
|
||||
* 当组件被启用时调用。
|
||||
*/
|
||||
public onEnabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件禁用时的回调
|
||||
*
|
||||
* 当组件被禁用时调用。
|
||||
*/
|
||||
public onDisabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组件
|
||||
*
|
||||
* 每帧调用,用于更新组件的逻辑。
|
||||
* 子类应该重写此方法来实现具体的更新逻辑。
|
||||
*/
|
||||
public update(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 避免循环引用,在文件末尾导入Entity
|
||||
import type { Entity } from './Entity';
|
||||
@@ -1,30 +0,0 @@
|
||||
module es {
|
||||
export class ComponentType {
|
||||
public static INDEX = 0;
|
||||
|
||||
private index_ = 0;
|
||||
private type_: Class;
|
||||
|
||||
constructor(type: Class, index?: number) {
|
||||
if (index !== undefined) {
|
||||
this.index_ = ComponentType.INDEX++;
|
||||
} else {
|
||||
this.index_ = index;
|
||||
}
|
||||
this.type_ = type;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return getClassName(this.type_);
|
||||
}
|
||||
|
||||
public getIndex(): number {
|
||||
return this.index_;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return "ComponentType[" + getClassName(ComponentType) + "] (" + this.index_ + ")";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,27 @@
|
||||
module es {
|
||||
/**
|
||||
* 接口,当添加到一个Component时,只要Component和实体被启用,它就会在每个框架中调用更新方法。
|
||||
*/
|
||||
export interface IUpdatable {
|
||||
enabled: boolean;
|
||||
updateOrder: number;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于比较组件更新排序
|
||||
*/
|
||||
export class IUpdatableComparer implements IComparer<IUpdatable> {
|
||||
public compare(a: IUpdatable, b: IUpdatable) {
|
||||
return a.updateOrder - b.updateOrder;
|
||||
}
|
||||
}
|
||||
|
||||
export var isIUpdatable = (props: any): props is IUpdatable => typeof (props as IUpdatable)['update'] !== 'undefined';
|
||||
/**
|
||||
* 可更新接口
|
||||
* 当添加到组件时,只要组件和实体被启用,就会在每帧调用update方法
|
||||
*/
|
||||
export interface IUpdatable {
|
||||
enabled: boolean;
|
||||
updateOrder: number;
|
||||
update(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于比较组件更新排序的比较器
|
||||
*/
|
||||
export class IUpdatableComparer {
|
||||
public compare(a: IUpdatable, b: IUpdatable): number {
|
||||
return a.updateOrder - b.updateOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否实现了IUpdatable接口
|
||||
* @param props 要检查的对象
|
||||
* @returns 如果实现了IUpdatable接口返回true,否则返回false
|
||||
*/
|
||||
export function isIUpdatable(props: any): props is IUpdatable {
|
||||
return typeof (props as IUpdatable)['update'] !== 'undefined';
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 请注意,这不是一个完整的、多迭代的物理系统!它可以用于简单的、街机风格的物理。
|
||||
* 这可以用于简单的,街机风格的物理学
|
||||
*/
|
||||
export class ArcadeRigidbody extends Component implements IUpdatable {
|
||||
/** 这个刚体的质量。质量为0,则是一个不可移动的物体 */
|
||||
public get mass() {
|
||||
return this._mass;
|
||||
}
|
||||
|
||||
public set mass(value: number) {
|
||||
this.setMass(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 0-1范围,其中0为无反弹,1为完全反射。
|
||||
*/
|
||||
public get elasticity() {
|
||||
return this._elasticity;
|
||||
}
|
||||
|
||||
public set elasticiy(value: number) {
|
||||
this.setElasticity(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 0 - 1范围。0表示没有摩擦力,1表示物体会停止在原地
|
||||
*/
|
||||
public get friction() {
|
||||
return this._friction;
|
||||
}
|
||||
|
||||
public set friction(value: number) {
|
||||
this.setFriction(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 0-9的范围。当发生碰撞时,沿碰撞面做直线运动时,如果其平方幅度小于glue摩擦力,则将碰撞设置为上限
|
||||
*/
|
||||
public get glue() {
|
||||
return this._glue;
|
||||
}
|
||||
|
||||
public set glue(value: number) {
|
||||
this.setGlue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果为真,则每一帧都会考虑到Physics.gravity
|
||||
*/
|
||||
public shouldUseGravity: boolean = true;
|
||||
|
||||
/**
|
||||
* 该刚体的速度
|
||||
*/
|
||||
public velocity: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 质量为0的刚体被认为是不可移动的。改变速度和碰撞对它们没有影响
|
||||
*/
|
||||
public get isImmovable() {
|
||||
return this._mass < 0.0001;
|
||||
}
|
||||
|
||||
public _mass = 10;
|
||||
public _elasticity = 0.5;
|
||||
public _friction = 0.5;
|
||||
public _glue = 0.01;
|
||||
public _inverseMass: number = 0;
|
||||
public _collider: Collider;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._inverseMass = 1 / this._mass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个刚体的质量。质量为0,则是一个不可移动的物体
|
||||
* @param mass
|
||||
*/
|
||||
public setMass(mass: number): ArcadeRigidbody {
|
||||
this._mass = MathHelper.clamp(mass, 0, Number.MAX_VALUE);
|
||||
|
||||
if (this._mass > 0.0001)
|
||||
this._inverseMass = 1 / this._mass;
|
||||
else
|
||||
this._inverseMass = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0-1范围,其中0为无反弹,1为完全反射。
|
||||
* @param value
|
||||
*/
|
||||
public setElasticity(value: number): ArcadeRigidbody {
|
||||
this._elasticity = MathHelper.clamp01(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0 - 1范围。0表示没有摩擦力,1表示物体会停止在原地
|
||||
* @param value
|
||||
*/
|
||||
public setFriction(value: number): ArcadeRigidbody {
|
||||
this._friction = MathHelper.clamp01(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0-9的范围。当发生碰撞时,沿碰撞面做直线运动时,如果其平方幅度小于glue摩擦力,则将碰撞设置为上限
|
||||
* @param value
|
||||
*/
|
||||
public setGlue(value: number): ArcadeRigidbody {
|
||||
this._glue = MathHelper.clamp(value, 0, 10);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setVelocity(velocity: Vector2): ArcadeRigidbody {
|
||||
this.velocity = velocity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用刚体的质量给刚体加上一个瞬间的力脉冲。力是一个加速度,单位是每秒像素每秒。将力乘以100000,使数值使用更合理
|
||||
* @param force
|
||||
*/
|
||||
public addImpulse(force: Vector2) {
|
||||
if (!this.isImmovable) {
|
||||
this.velocity.addEqual(force.scale(100000 * (this._inverseMass * (Time.deltaTime * Time.deltaTime))));
|
||||
}
|
||||
}
|
||||
|
||||
public onAddedToEntity() {
|
||||
this._collider = null;
|
||||
for (let i = 0; i < this.entity.components.buffer.length; i++) {
|
||||
let component = this.entity.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
this._collider = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Debug.warnIf(this._collider == null, "ArcadeRigidbody 没有 Collider。ArcadeRigidbody需要一个Collider!");
|
||||
}
|
||||
|
||||
public update() {
|
||||
if (this.isImmovable || this._collider == null) {
|
||||
this.velocity = Vector2.zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shouldUseGravity)
|
||||
this.velocity.addEqual(Physics.gravity.scale(Time.deltaTime));
|
||||
this.entity.position = this.entity.position.add(this.velocity.scale(Time.deltaTime));
|
||||
|
||||
// 捞取我们在新的位置上可能会碰撞到的任何东西
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelf(this._collider, this._collider.bounds, this._collider.collidesWithLayers.value);
|
||||
if (neighbors.length > 0) {
|
||||
for (let i = 0; i < neighbors.length; i ++) {
|
||||
const neighbor = neighbors[i];
|
||||
if (!neighbor)
|
||||
continue;
|
||||
|
||||
// 如果邻近的对撞机是同一个实体,则忽略它
|
||||
if (neighbor.entity.equals(this.entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const collisionResult = new Out<CollisionResult>();
|
||||
if (this._collider.collidesWithNonMotion(neighbor, collisionResult)) {
|
||||
// 如果附近有一个ArcadeRigidbody,我们就会处理完整的碰撞响应。如果没有,我们会根据附近是不可移动的来计算事情
|
||||
let neighborRigidbody = neighbor.entity.getComponent(ArcadeRigidbody);
|
||||
if (neighborRigidbody != null) {
|
||||
this.processOverlap(neighborRigidbody, collisionResult.value.minimumTranslationVector);
|
||||
this.processCollision(neighborRigidbody, collisionResult.value.minimumTranslationVector);
|
||||
} else {
|
||||
// 没有ArcadeRigidbody,所以我们假设它是不动的,只移动我们自己的
|
||||
this.entity.position = this.entity.position.sub(collisionResult.value.minimumTranslationVector);
|
||||
const relativeVelocity = this.calculateResponseVelocity(this.velocity, collisionResult.value.minimumTranslationVector);
|
||||
this.velocity.addEqual(relativeVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 将两个重叠的刚体分开。也处理其中一个不可移动的情况
|
||||
* @param other
|
||||
* @param minimumTranslationVector
|
||||
*/
|
||||
public processOverlap(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
|
||||
if (this.isImmovable) {
|
||||
other.entity.position = other.entity.position.add(minimumTranslationVector);
|
||||
} else if (other.isImmovable) {
|
||||
this.entity.position = this.entity.position.sub(minimumTranslationVector);
|
||||
} else {
|
||||
this.entity.position = this.entity.position.sub(minimumTranslationVector.scale(0.5));
|
||||
other.entity.position = other.entity.position.add(minimumTranslationVector.scale(0.5));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理两个非重叠的刚体的碰撞。新的速度将根据情况分配给每个刚体
|
||||
* @param other
|
||||
* @param minimumTranslationVector
|
||||
*/
|
||||
public processCollision(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
|
||||
// 我们计算两个相撞物体的响应。
|
||||
// 计算的基础是沿碰撞表面法线反射的物体的相对速度。
|
||||
// 然后,响应的一部分会根据质量加到每个物体上
|
||||
let relativeVelocity = this.velocity.sub(other.velocity);
|
||||
|
||||
relativeVelocity = this.calculateResponseVelocity(relativeVelocity, minimumTranslationVector);
|
||||
|
||||
// 现在,我们使用质量来线性缩放两个刚体上的响应
|
||||
const totalinverseMass = this._inverseMass + other._inverseMass;
|
||||
const ourResponseFraction = this._inverseMass / totalinverseMass;
|
||||
const otherResponseFraction = other._inverseMass / totalinverseMass;
|
||||
|
||||
|
||||
this.velocity = this.velocity.add(relativeVelocity.scale(ourResponseFraction));
|
||||
other.velocity = other.velocity.sub(relativeVelocity.scale(otherResponseFraction));
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定两个物体和MTV之间的相对速度,本方法修改相对速度,使其成为碰撞响应
|
||||
* @param relativeVelocity
|
||||
* @param minimumTranslationVector
|
||||
* @param responseVelocity
|
||||
*/
|
||||
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2) {
|
||||
// 首先,我们得到反方向的归一化MTV:表面法线
|
||||
let inverseMTV = minimumTranslationVector.scale(-1);
|
||||
let normal = inverseMTV.normalize();
|
||||
// 速度是沿碰撞法线和碰撞平面分解的。
|
||||
// 弹性将影响沿法线的响应(法线速度分量),摩擦力将影响速度的切向分量(切向速度分量)
|
||||
let n = relativeVelocity.dot(normal);
|
||||
|
||||
let normalVelocityComponent = normal.scale(n);
|
||||
let tangentialVelocityComponent = relativeVelocity.sub(normalVelocityComponent);
|
||||
|
||||
if (n > 0)
|
||||
normalVelocityComponent = Vector2.zero;
|
||||
|
||||
// 如果切向分量的平方幅度小于glue,那么我们就把摩擦力提升到最大
|
||||
let coefficientOfFriction = this._friction;
|
||||
if (tangentialVelocityComponent.lengthSquared() < this._glue)
|
||||
coefficientOfFriction = 1.01;
|
||||
|
||||
// 弹性影响速度的法向分量,摩擦力影响速度的切向分量
|
||||
return normalVelocityComponent
|
||||
.scale(1 + this._elasticity)
|
||||
.sub(tangentialVelocityComponent.scale(coefficientOfFriction))
|
||||
.scale(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,587 +0,0 @@
|
||||
module es {
|
||||
class CharacterRaycastOrigins {
|
||||
public topLeft: Vector2;
|
||||
public bottomRight: Vector2;
|
||||
public bottomLeft: Vector2;
|
||||
|
||||
public constructor() {
|
||||
this.topLeft = Vector2.zero;
|
||||
this.bottomRight = Vector2.zero;
|
||||
this.bottomLeft = Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
class CharacterCollisionState2D {
|
||||
public right: boolean = false;
|
||||
public left: boolean = false;
|
||||
public above: boolean = false;
|
||||
public below: boolean = false;
|
||||
public becameGroundedThisFrame: boolean = false;
|
||||
public wasGroundedLastFrame: boolean = false;
|
||||
public movingDownSlope: boolean = false;
|
||||
public slopeAngle: number = 0;
|
||||
|
||||
public hasCollision(): boolean {
|
||||
return this.below || this.right || this.left || this.above;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.right = this.left = false;
|
||||
this.above = this.below = false;
|
||||
this.becameGroundedThisFrame = this.movingDownSlope = false;
|
||||
this.slopeAngle = 0;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `[CharacterCollisionState2D] r: ${this.right}, l: ${this.left}, a: ${this.above}, b: ${this.below}, movingDownSlope: ${this.movingDownSlope}, angle: ${this.slopeAngle}, wasGroundedLastFrame: ${this.wasGroundedLastFrame}, becameGroundedThisFrame: ${this.becameGroundedThisFrame}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class CharacterController implements ITriggerListener {
|
||||
public onControllerCollidedEvent: ObservableT<RaycastHit>;
|
||||
public onTriggerEnterEvent: ObservableT<Collider>;
|
||||
public onTriggerExitEvent: ObservableT<Collider>;
|
||||
|
||||
|
||||
/**
|
||||
* 如果为 true,则在垂直移动单帧时将忽略平台的一种方式
|
||||
*/
|
||||
public ignoreOneWayPlatformsTime: number;
|
||||
public supportSlopedOneWayPlatforms: boolean;
|
||||
|
||||
public ignoredColliders: Set<Collider> = new Set();
|
||||
|
||||
|
||||
/**
|
||||
* 定义距离碰撞射线的边缘有多远。
|
||||
* 如果使用 0 范围进行投射,则通常会导致不需要的光线击中(例如,直接从表面水平投射的足部碰撞器可能会导致击中)
|
||||
*/
|
||||
public get skinWidth() {
|
||||
return this._skinWidth;
|
||||
}
|
||||
|
||||
public set skinWidth(value: number) {
|
||||
this._skinWidth = value;
|
||||
this.recalculateDistanceBetweenRays();
|
||||
}
|
||||
|
||||
/**
|
||||
* CC2D 可以爬升的最大坡度角
|
||||
*/
|
||||
public slopeLimit: number = 30;
|
||||
|
||||
/**
|
||||
* 构成跳跃的帧之间垂直运动变化的阈值
|
||||
*/
|
||||
public jumpingThreshold: number = -7;
|
||||
|
||||
/**
|
||||
* 基于斜率乘以速度的曲线(负 = 下坡和正 = 上坡)
|
||||
*/
|
||||
public slopeSpeedMultiplier: AnimCurve;
|
||||
|
||||
public totalHorizontalRays: number = 5;
|
||||
public totalVerticalRays: number = 3;
|
||||
|
||||
public collisionState: CharacterCollisionState2D = new CharacterCollisionState2D();
|
||||
public velocity: Vector2 = new Vector2(0, 0);
|
||||
|
||||
public get isGrounded(): boolean {
|
||||
return this.collisionState.below;
|
||||
}
|
||||
|
||||
public get raycastHitsThisFrame(): RaycastHit[] {
|
||||
return this._raycastHitsThisFrame;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
player: Entity,
|
||||
skinWidth?: number,
|
||||
platformMask: number = -1,
|
||||
onewayPlatformMask: number = -1,
|
||||
triggerMask: number = -1
|
||||
) {
|
||||
this.onTriggerEnterEvent = new ObservableT();
|
||||
this.onTriggerExitEvent = new ObservableT();
|
||||
this.onControllerCollidedEvent = new ObservableT();
|
||||
|
||||
this.platformMask = platformMask;
|
||||
this.oneWayPlatformMask = onewayPlatformMask;
|
||||
this.triggerMask = triggerMask;
|
||||
|
||||
// 将我们的单向平台添加到我们的普通平台掩码中,以便我们可以从上方降落
|
||||
this.platformMask |= this.oneWayPlatformMask;
|
||||
|
||||
this._player = player;
|
||||
let collider = null;
|
||||
for (let i = 0; i < this._player.components.buffer.length; i++) {
|
||||
let component = this._player.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
collider = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
collider.isTrigger = false;
|
||||
if (collider instanceof BoxCollider) {
|
||||
this._collider = collider as BoxCollider;
|
||||
} else {
|
||||
throw new Error('player collider must be box');
|
||||
}
|
||||
|
||||
// 在这里,我们触发了具有主体的 setter 的属性
|
||||
this.skinWidth = skinWidth || collider.width * 0.05;
|
||||
|
||||
this._slopeLimitTangent = Math.tan(75 * MathHelper.Deg2Rad);
|
||||
this._triggerHelper = new ColliderTriggerHelper(this._player);
|
||||
|
||||
// 我们想设置我们的 CC2D 忽略所有碰撞层,除了我们的 triggerMask
|
||||
for (let i = 0; i < 32; i++) {
|
||||
// 查看我们的 triggerMask 是否包含此层,如果不包含则忽略它
|
||||
if ((this.triggerMask & (1 << i)) === 0) {
|
||||
Flags.unsetFlag(this._collider.collidesWithLayers, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onTriggerEnter(other: Collider, local: Collider): void {
|
||||
this.onTriggerEnterEvent.notify(other);
|
||||
}
|
||||
|
||||
public onTriggerExit(other: Collider, local: Collider): void {
|
||||
this.onTriggerExitEvent.notify(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试将角色移动到位置 + deltaMovement。 任何挡路的碰撞器都会在遇到时导致运动停止
|
||||
* @param deltaMovement
|
||||
* @param deltaTime
|
||||
*/
|
||||
public move(deltaMovement: Vector2, deltaTime: number): void {
|
||||
this.collisionState.wasGroundedLastFrame = this.collisionState.below;
|
||||
|
||||
this.collisionState.reset();
|
||||
this._raycastHitsThisFrame = [];
|
||||
this._isGoingUpSlope = false;
|
||||
|
||||
this.primeRaycastOrigins();
|
||||
|
||||
if (deltaMovement.y > 0 && this.collisionState.wasGroundedLastFrame) {
|
||||
deltaMovement = this.handleVerticalSlope(deltaMovement);
|
||||
}
|
||||
|
||||
if (deltaMovement.x !== 0) {
|
||||
deltaMovement = this.moveHorizontally(deltaMovement);
|
||||
}
|
||||
|
||||
if (deltaMovement.y !== 0) {
|
||||
deltaMovement = this.moveVertically(deltaMovement);
|
||||
}
|
||||
|
||||
this._player.setPosition(
|
||||
this._player.position.x + deltaMovement.x,
|
||||
this._player.position.y + deltaMovement.y
|
||||
);
|
||||
|
||||
if (deltaTime > 0) {
|
||||
this.velocity.x = deltaMovement.x / deltaTime;
|
||||
this.velocity.y = deltaMovement.y / deltaTime;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.collisionState.wasGroundedLastFrame &&
|
||||
this.collisionState.below
|
||||
) {
|
||||
this.collisionState.becameGroundedThisFrame = true;
|
||||
}
|
||||
|
||||
if (this._isGoingUpSlope) {
|
||||
this.velocity.y = 0;
|
||||
}
|
||||
|
||||
if (!this._isWarpingToGround) {
|
||||
this._triggerHelper.update();
|
||||
}
|
||||
for (let i = 0; i < this._raycastHitsThisFrame.length; i++) {
|
||||
this.onControllerCollidedEvent.notify(this._raycastHitsThisFrame[i]);
|
||||
}
|
||||
|
||||
if (this.ignoreOneWayPlatformsTime > 0) {
|
||||
this.ignoreOneWayPlatformsTime -= deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 直接向下移动直到接地
|
||||
* @param maxDistance
|
||||
*/
|
||||
public warpToGrounded(maxDistance: number = 1000): void {
|
||||
this.ignoreOneWayPlatformsTime = 0;
|
||||
this._isWarpingToGround = true;
|
||||
let delta = 0;
|
||||
do {
|
||||
delta += 1;
|
||||
this.move(new Vector2(0, 1), 0.02);
|
||||
if (delta > maxDistance) {
|
||||
break;
|
||||
}
|
||||
} while (!this.isGrounded);
|
||||
this._isWarpingToGround = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这应该在您必须在运行时修改 BoxCollider2D 的任何时候调用。
|
||||
* 它将重新计算用于碰撞检测的光线之间的距离。
|
||||
* 它也用于 skinWidth setter,以防在运行时更改。
|
||||
*/
|
||||
public recalculateDistanceBetweenRays(): void {
|
||||
const colliderUsableHeight =
|
||||
this._collider.height * Math.abs(this._player.scale.y) -
|
||||
2 * this._skinWidth;
|
||||
this._verticalDistanceBetweenRays =
|
||||
colliderUsableHeight / (this.totalHorizontalRays - 1);
|
||||
|
||||
const colliderUsableWidth =
|
||||
this._collider.width * Math.abs(this._player.scale.x) -
|
||||
2 * this._skinWidth;
|
||||
this._horizontalDistanceBetweenRays =
|
||||
colliderUsableWidth / (this.totalVerticalRays - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 raycastOrigins 重置为由 skinWidth 插入的框碰撞器的当前范围。
|
||||
* 插入它是为了避免从直接接触另一个碰撞器的位置投射光线,从而导致不稳定的法线数据。
|
||||
*/
|
||||
private primeRaycastOrigins(): void {
|
||||
const rect = this._collider.bounds;
|
||||
this._raycastOrigins.topLeft = new Vector2(
|
||||
rect.x + this._skinWidth,
|
||||
rect.y + this._skinWidth
|
||||
);
|
||||
this._raycastOrigins.bottomRight = new Vector2(
|
||||
rect.right - this._skinWidth,
|
||||
rect.bottom - this._skinWidth
|
||||
);
|
||||
this._raycastOrigins.bottomLeft = new Vector2(
|
||||
rect.x + this._skinWidth,
|
||||
rect.bottom - this._skinWidth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我们必须在这方面使用一些技巧。
|
||||
* 光线必须从我们的碰撞器(skinWidth)内部的一小段距离投射,以避免零距离光线会得到错误的法线。
|
||||
* 由于这个小偏移,我们必须增加光线距离 skinWidth 然后记住在实际移动玩家之前从 deltaMovement 中删除 skinWidth
|
||||
* @param deltaMovement
|
||||
* @returns
|
||||
*/
|
||||
private moveHorizontally(deltaMovement: Vector2): Vector2 {
|
||||
const isGoingRight = deltaMovement.x > 0;
|
||||
let rayDistance =
|
||||
Math.abs(deltaMovement.x) +
|
||||
this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
const rayDirection: Vector2 = isGoingRight ? Vector2.right : Vector2.left;
|
||||
const initialRayOriginY = this._raycastOrigins.bottomLeft.y;
|
||||
const initialRayOriginX = isGoingRight
|
||||
? this._raycastOrigins.bottomRight.x -
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
|
||||
: this._raycastOrigins.bottomLeft.x +
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
|
||||
|
||||
for (let i = 0; i < this.totalHorizontalRays; i++) {
|
||||
const ray = new Vector2(
|
||||
initialRayOriginX,
|
||||
initialRayOriginY - i * this._verticalDistanceBetweenRays
|
||||
);
|
||||
|
||||
// 如果我们接地,我们将只在第一条射线(底部)上包含 oneWayPlatforms。
|
||||
// 允许我们走上倾斜的 oneWayPlatforms
|
||||
if (
|
||||
i === 0 &&
|
||||
this.supportSlopedOneWayPlatforms &&
|
||||
this.collisionState.wasGroundedLastFrame
|
||||
) {
|
||||
this._raycastHit = Physics.linecast(
|
||||
ray,
|
||||
ray.add(rayDirection.scaleEqual(rayDistance)),
|
||||
this.platformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
} else {
|
||||
this._raycastHit = Physics.linecast(
|
||||
ray,
|
||||
ray.add(rayDirection.scaleEqual(rayDistance)),
|
||||
this.platformMask & ~this.oneWayPlatformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
}
|
||||
|
||||
if (this._raycastHit.collider) {
|
||||
if (
|
||||
i === 0 &&
|
||||
this.handleHorizontalSlope(
|
||||
deltaMovement,
|
||||
Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up)
|
||||
)
|
||||
) {
|
||||
this._raycastHitsThisFrame.push(this._raycastHit);
|
||||
break;
|
||||
}
|
||||
|
||||
deltaMovement.x = this._raycastHit.point.x - ray.x;
|
||||
rayDistance = Math.abs(deltaMovement.x);
|
||||
|
||||
if (isGoingRight) {
|
||||
deltaMovement.x -= this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.right = true;
|
||||
} else {
|
||||
deltaMovement.x += this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.left = true;
|
||||
}
|
||||
|
||||
this._raycastHitsThisFrame.push(this._raycastHit);
|
||||
|
||||
if (
|
||||
rayDistance <
|
||||
this._skinWidth * this.rayOriginSkinMutiplier +
|
||||
this.kSkinWidthFloatFudgeFactor
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
private moveVertically(deltaMovement: Vector2): Vector2 {
|
||||
const isGoingUp = deltaMovement.y < 0;
|
||||
let rayDistance =
|
||||
Math.abs(deltaMovement.y) +
|
||||
this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
const rayDirection = isGoingUp ? Vector2.up : Vector2.down;
|
||||
|
||||
let initialRayOriginX = this._raycastOrigins.topLeft.x;
|
||||
const initialRayOriginY = isGoingUp
|
||||
? this._raycastOrigins.topLeft.y +
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
|
||||
: this._raycastOrigins.bottomLeft.y -
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
|
||||
|
||||
initialRayOriginX += deltaMovement.x;
|
||||
|
||||
let mask = this.platformMask;
|
||||
if (isGoingUp || this.ignoreOneWayPlatformsTime > 0) {
|
||||
mask &= ~this.oneWayPlatformMask;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.totalVerticalRays; i++) {
|
||||
const rayStart = new Vector2(
|
||||
initialRayOriginX + i * this._horizontalDistanceBetweenRays,
|
||||
initialRayOriginY
|
||||
);
|
||||
this._raycastHit = Physics.linecast(
|
||||
rayStart,
|
||||
rayStart.add(rayDirection.scaleEqual(rayDistance)),
|
||||
mask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
if (this._raycastHit.collider) {
|
||||
deltaMovement.y = this._raycastHit.point.y - rayStart.y;
|
||||
rayDistance = Math.abs(deltaMovement.y);
|
||||
|
||||
if (isGoingUp) {
|
||||
deltaMovement.y += this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.above = true;
|
||||
} else {
|
||||
deltaMovement.y -= this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.below = true;
|
||||
}
|
||||
|
||||
this._raycastHitsThisFrame.push(this._raycastHit);
|
||||
|
||||
if (!isGoingUp && deltaMovement.y < -0.00001) {
|
||||
this._isGoingUpSlope = true;
|
||||
}
|
||||
|
||||
if (
|
||||
rayDistance <
|
||||
this._skinWidth * this.rayOriginSkinMutiplier +
|
||||
this.kSkinWidthFloatFudgeFactor
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 BoxCollider2D 下的中心点是否存在坡度。
|
||||
* 如果找到一个,则调整 deltaMovement 以便玩家保持接地,并考虑slopeSpeedModifier 以加快移动速度。
|
||||
* @param deltaMovement
|
||||
* @returns
|
||||
*/
|
||||
private handleVerticalSlope(deltaMovement: Vector2): Vector2 {
|
||||
const centerOfCollider =
|
||||
(this._raycastOrigins.bottomLeft.x +
|
||||
this._raycastOrigins.bottomRight.x) *
|
||||
0.5;
|
||||
const rayDirection = Vector2.down;
|
||||
|
||||
const slopeCheckRayDistance =
|
||||
this._slopeLimitTangent *
|
||||
(this._raycastOrigins.bottomRight.x - centerOfCollider);
|
||||
|
||||
const slopeRay = new Vector2(
|
||||
centerOfCollider,
|
||||
this._raycastOrigins.bottomLeft.y
|
||||
);
|
||||
|
||||
this._raycastHit = Physics.linecast(
|
||||
slopeRay,
|
||||
slopeRay.add(rayDirection.scaleEqual(slopeCheckRayDistance)),
|
||||
this.platformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
if (this._raycastHit.collider) {
|
||||
const angle = Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up);
|
||||
if (angle === 0) {
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
const isMovingDownSlope =
|
||||
Math.sign(this._raycastHit.normal.x) === Math.sign(deltaMovement.x);
|
||||
if (isMovingDownSlope) {
|
||||
const slopeModifier = this.slopeSpeedMultiplier
|
||||
? this.slopeSpeedMultiplier.lerp(-angle)
|
||||
: 1;
|
||||
deltaMovement.y +=
|
||||
this._raycastHit.point.y - slopeRay.y - this.skinWidth;
|
||||
deltaMovement.x *= slopeModifier;
|
||||
this.collisionState.movingDownSlope = true;
|
||||
this.collisionState.slopeAngle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果我们要上坡,则处理调整 deltaMovement
|
||||
* @param deltaMovement
|
||||
* @param angle
|
||||
* @returns
|
||||
*/
|
||||
private handleHorizontalSlope(
|
||||
deltaMovement: Vector2,
|
||||
angle: number
|
||||
): boolean {
|
||||
if (Math.round(angle) === 90) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (angle < this.slopeLimit) {
|
||||
if (deltaMovement.y > this.jumpingThreshold) {
|
||||
const slopeModifier = this.slopeSpeedMultiplier
|
||||
? this.slopeSpeedMultiplier.lerp(angle)
|
||||
: 1;
|
||||
deltaMovement.x *= slopeModifier;
|
||||
|
||||
deltaMovement.y = Math.abs(
|
||||
Math.tan(angle * MathHelper.Deg2Rad) * deltaMovement.x
|
||||
);
|
||||
const isGoingRight = deltaMovement.x > 0;
|
||||
|
||||
const ray = isGoingRight
|
||||
? this._raycastOrigins.bottomRight
|
||||
: this._raycastOrigins.bottomLeft;
|
||||
let raycastHit = null;
|
||||
if (
|
||||
this.supportSlopedOneWayPlatforms &&
|
||||
this.collisionState.wasGroundedLastFrame
|
||||
) {
|
||||
raycastHit = Physics.linecast(
|
||||
ray,
|
||||
ray.add(deltaMovement),
|
||||
this.platformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
} else {
|
||||
raycastHit = Physics.linecast(
|
||||
ray,
|
||||
ray.add(deltaMovement),
|
||||
this.platformMask & ~this.oneWayPlatformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
}
|
||||
|
||||
if (raycastHit.collider) {
|
||||
deltaMovement.x = raycastHit.point.x - ray.x;
|
||||
deltaMovement.y = raycastHit.point.y - ray.y;
|
||||
if (isGoingRight) {
|
||||
deltaMovement.x -= this._skinWidth;
|
||||
} else {
|
||||
deltaMovement.x += this._skinWidth;
|
||||
}
|
||||
}
|
||||
|
||||
this._isGoingUpSlope = true;
|
||||
this.collisionState.below = true;
|
||||
}
|
||||
} else {
|
||||
deltaMovement.x = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _player: Entity;
|
||||
private _collider: BoxCollider;
|
||||
private _skinWidth: number = 0.02;
|
||||
private _triggerHelper: ColliderTriggerHelper;
|
||||
|
||||
/**
|
||||
* 这用于计算为检查坡度而投射的向下光线。
|
||||
* 我们使用有点随意的值 75 度来计算检查斜率的射线的长度。
|
||||
*/
|
||||
private _slopeLimitTangent: number;
|
||||
|
||||
private readonly kSkinWidthFloatFudgeFactor: number = 0.001;
|
||||
|
||||
/**
|
||||
* 我们的光线投射原点角的支架(TR、TL、BR、BL)
|
||||
*/
|
||||
private _raycastOrigins: CharacterRaycastOrigins = new CharacterRaycastOrigins();
|
||||
|
||||
/**
|
||||
* 存储我们在移动过程中命中的光线投射
|
||||
*/
|
||||
private _raycastHit: RaycastHit = new RaycastHit();
|
||||
|
||||
/**
|
||||
* 存储此帧发生的任何光线投射命中。
|
||||
* 我们必须存储它们,以防我们遇到水平和垂直移动的碰撞,以便我们可以在设置所有碰撞状态后发送事件
|
||||
*/
|
||||
private _raycastHitsThisFrame: RaycastHit[];
|
||||
|
||||
// 水平/垂直移动数据
|
||||
private _verticalDistanceBetweenRays: number;
|
||||
private _horizontalDistanceBetweenRays: number;
|
||||
|
||||
/**
|
||||
* 我们使用这个标志来标记我们正在爬坡的情况,我们修改了 delta.y 以允许爬升。
|
||||
* 原因是,如果我们到达斜坡的尽头,我们可以进行调整以保持接地
|
||||
*/
|
||||
private _isGoingUpSlope: boolean = false;
|
||||
|
||||
private _isWarpingToGround: boolean = true;
|
||||
|
||||
private platformMask: number = -1;
|
||||
private triggerMask: number = -1;
|
||||
private oneWayPlatformMask: number = -1;
|
||||
|
||||
private readonly rayOriginSkinMutiplier = 4;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
///<reference path="./Collider.ts" />
|
||||
module es {
|
||||
export class BoxCollider extends Collider {
|
||||
/**
|
||||
* 创建一个BoxCollider,并使用x/y组件作为局部Offset
|
||||
* @param x
|
||||
* @param y
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
constructor(x: number = 0, y: number = 0, width: number = 1, height: number = 1) {
|
||||
super();
|
||||
|
||||
this._localOffset = new Vector2(x + width / 2, y + height / 2);
|
||||
|
||||
this.shape = new Box(width, height);
|
||||
}
|
||||
|
||||
public get width() {
|
||||
return (this.shape as Box).width;
|
||||
}
|
||||
|
||||
public set width(value: number) {
|
||||
this.setWidth(value);
|
||||
}
|
||||
|
||||
public get height() {
|
||||
return (this.shape as Box).height;
|
||||
}
|
||||
|
||||
public set height(value: number) {
|
||||
this.setHeight(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的大小
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public setSize(width: number, height: number) {
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width || height != box.height) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的宽度
|
||||
* @param width
|
||||
*/
|
||||
public setWidth(width: number): BoxCollider {
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, box.height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity != null && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的高度
|
||||
* @param height
|
||||
*/
|
||||
public setHeight(height: number) {
|
||||
let box = this.shape as Box;
|
||||
if (height != box.height) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(box.width, height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[BoxCollider: bounds: ${this.bounds}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
module es {
|
||||
export class CircleCollider extends Collider {
|
||||
/**
|
||||
* 创建一个具有半径的CircleCollider。
|
||||
* 请注意,当指定半径时,如果在实体上使用RenderableComponent,您将需要设置原点来对齐CircleCollider。
|
||||
* 例如,如果RenderableComponent有一个0,0的原点,并且创建了一个半径为1.5f * renderable.width的CircleCollider,你可以通过设置originNormalied为中心除以缩放尺寸来偏移原点
|
||||
*
|
||||
* @param radius
|
||||
*/
|
||||
constructor(radius: number = 1) {
|
||||
super();
|
||||
|
||||
this.shape = new Circle(radius);
|
||||
}
|
||||
|
||||
public get radius(): number {
|
||||
return (this.shape as Circle).radius;
|
||||
}
|
||||
|
||||
public set radius(value: number) {
|
||||
this.setRadius(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置圆的半径
|
||||
* @param radius
|
||||
*/
|
||||
public setRadius(radius: number): CircleCollider {
|
||||
let circle = this.shape as Circle;
|
||||
if (radius != circle.radius) {
|
||||
circle.radius = radius;
|
||||
circle._originalRadius = radius;
|
||||
this._isPositionDirty = true;
|
||||
|
||||
if (this.entity != null && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[CircleCollider: bounds: ${this.bounds}, radius: ${(this.shape as Circle).radius}]`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
module es {
|
||||
export class Collider extends Component {
|
||||
public static readonly lateSortOrder = 999;
|
||||
public castSortOrder: number = 0;
|
||||
/**
|
||||
* 对撞机的基本形状
|
||||
*/
|
||||
public shape: Shape;
|
||||
/**
|
||||
* 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件
|
||||
*/
|
||||
public isTrigger: boolean = false;
|
||||
/**
|
||||
* 在处理冲突时,physicsLayer可以用作过滤器。Flags类有帮助位掩码的方法
|
||||
*/
|
||||
public physicsLayer = new Ref(1 << 0);
|
||||
/**
|
||||
* 碰撞器在使用移动器移动时应该碰撞的层
|
||||
* 默认为所有层
|
||||
*/
|
||||
public collidesWithLayers: Ref<number> = new Ref(Physics.allLayers);
|
||||
/**
|
||||
* 如果为true,碰撞器将根据附加的变换缩放和旋转
|
||||
*/
|
||||
public shouldColliderScaleAndRotateWithTransform = true;
|
||||
/**
|
||||
* 这个对撞机在物理系统注册时的边界。
|
||||
* 存储这个允许我们始终能够安全地从物理系统中移除对撞机,即使它在试图移除它之前已经被移动了。
|
||||
*/
|
||||
public registeredPhysicsBounds: Rectangle = new Rectangle();
|
||||
|
||||
public _localOffsetLength: number;
|
||||
public _isPositionDirty: boolean = true;
|
||||
public _isRotationDirty: boolean = true;
|
||||
/**
|
||||
* 标记来跟踪我们的实体是否被添加到场景中
|
||||
*/
|
||||
protected _isParentEntityAddedToScene;
|
||||
/**
|
||||
* 标记来记录我们是否注册了物理系统
|
||||
*/
|
||||
protected _isColliderRegistered;
|
||||
|
||||
/**
|
||||
* 表示碰撞器的绝对位置
|
||||
*/
|
||||
public get absolutePosition(): Vector2 {
|
||||
return Vector2.add(this.entity.transform.position, this._localOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装变换。如果碰撞器没和实体一起旋转 则返回transform.rotation
|
||||
*/
|
||||
public get rotation(): number {
|
||||
if (this.shouldColliderScaleAndRotateWithTransform && this.entity != null)
|
||||
return this.entity.transform.rotation;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public get bounds(): Rectangle {
|
||||
if (this._isPositionDirty || this._isRotationDirty) {
|
||||
this.shape.recalculateBounds(this);
|
||||
this._isPositionDirty = this._isRotationDirty = false;
|
||||
}
|
||||
|
||||
return this.shape.bounds;
|
||||
}
|
||||
|
||||
protected _localOffset: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器几何图形的最终位置。
|
||||
* 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转
|
||||
*/
|
||||
public get localOffset(): Vector2 {
|
||||
return this._localOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器几何图形的最终位置。
|
||||
* 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转
|
||||
* @param value
|
||||
*/
|
||||
public set localOffset(value: Vector2) {
|
||||
this.setLocalOffset(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器的最终位置。
|
||||
* 这允许您向一个实体添加多个碰撞器并分别定位它们。
|
||||
* @param offset
|
||||
*/
|
||||
public setLocalOffset(offset: Vector2): Collider {
|
||||
if (!this._localOffset.equals(offset)) {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
this._localOffset.setTo(offset.x, offset.y);
|
||||
this._localOffsetLength = this._localOffset.magnitude();
|
||||
this._isPositionDirty = true;
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果为true,碰撞器将根据附加的变换缩放和旋转
|
||||
* @param shouldColliderScaleAndRotationWithTransform
|
||||
*/
|
||||
public setShouldColliderScaleAndRotateWithTransform(shouldColliderScaleAndRotationWithTransform: boolean): Collider {
|
||||
this.shouldColliderScaleAndRotateWithTransform = shouldColliderScaleAndRotationWithTransform;
|
||||
this._isPositionDirty = this._isRotationDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public onAddedToEntity() {
|
||||
this._isParentEntityAddedToScene = true;
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
public onRemovedFromEntity() {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
this._isParentEntityAddedToScene = false;
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: ComponentTransform) {
|
||||
switch (comp) {
|
||||
case ComponentTransform.position:
|
||||
this._isPositionDirty = true;
|
||||
break;
|
||||
case ComponentTransform.scale:
|
||||
this._isPositionDirty = true;
|
||||
break;
|
||||
case ComponentTransform.rotation:
|
||||
this._isRotationDirty = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._isColliderRegistered)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
public onEnabled() {
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
this._isPositionDirty = this._isRotationDirty = true;
|
||||
}
|
||||
|
||||
public onDisabled() {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 父实体会在不同的时间调用它(当添加到场景,启用,等等)
|
||||
*/
|
||||
public registerColliderWithPhysicsSystem() {
|
||||
// 如果在将我们添加到实体之前更改了origin等属性,则实体可以为null
|
||||
if (this._isParentEntityAddedToScene && !this._isColliderRegistered) {
|
||||
Physics.addCollider(this);
|
||||
this._isColliderRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 父实体会在不同的时候调用它(从场景中移除,禁用,等等)
|
||||
*/
|
||||
public unregisterColliderWithPhysicsSystem() {
|
||||
if (this._isParentEntityAddedToScene && this._isColliderRegistered) {
|
||||
Physics.removeCollider(this);
|
||||
}
|
||||
this._isColliderRegistered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查这个形状是否与物理系统中的其他对撞机重叠
|
||||
* @param other
|
||||
*/
|
||||
public overlaps(other: Collider): boolean {
|
||||
return this.shape.overlaps(other.shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查这个与运动应用的碰撞器(移动向量)是否与碰撞器碰撞。如果是这样,将返回true,并且结果将填充碰撞数据。
|
||||
* @param collider
|
||||
* @param motion
|
||||
* @param result
|
||||
*/
|
||||
public collidesWith(collider: Collider, motion: Vector2, result: Out<CollisionResult>): boolean {
|
||||
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
|
||||
const oldPosition = this.entity.position;
|
||||
this.entity.position = this.entity.position.add(motion);
|
||||
|
||||
const didCollide = this.shape.collidesWithShape(collider.shape, result);
|
||||
if (didCollide)
|
||||
result.value.collider = collider;
|
||||
|
||||
// 将图形位置返回到检查前的位置
|
||||
this.entity.position = oldPosition;
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查这个对撞机是否与对撞机发生碰撞。如果碰撞,则返回true,结果将被填充
|
||||
* @param collider
|
||||
* @param result
|
||||
*/
|
||||
public collidesWithNonMotion(collider: Collider, result: Out<CollisionResult>): boolean {
|
||||
if (this.shape.collidesWithShape(collider.shape, result)) {
|
||||
result.value.collider = collider;
|
||||
return true;
|
||||
}
|
||||
|
||||
result.value.collider = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查此碰撞器是否已应用运动(增量运动矢量)与任何碰撞器发生碰撞。
|
||||
* 如果是这样,则将返回true,并且将使用碰撞数据填充结果。 运动将设置为碰撞器在碰撞之前可以行进的最大距离。
|
||||
* @param motion
|
||||
* @param result
|
||||
*/
|
||||
public collidesWithAny(motion: Vector2, result: Out<CollisionResult>) {
|
||||
result.value = new CollisionResult();
|
||||
|
||||
// 在我们的新位置上获取我们可能会碰到的任何东西
|
||||
let colliderBounds = this.bounds.clone();
|
||||
colliderBounds.x += motion.x;
|
||||
colliderBounds.y += motion.y;
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelf(this, colliderBounds, this.collidesWithLayers.value);
|
||||
|
||||
// 更改形状位置,使其处于移动后的位置,以便我们检查是否有重叠
|
||||
let oldPosition = this.shape.position;
|
||||
this.shape.position = Vector2.add(this.shape.position, motion);
|
||||
|
||||
let didCollide = false;
|
||||
for (let i = 0; i < neighbors.length; i ++ ){
|
||||
const neighbor = neighbors[i];
|
||||
if (neighbor.isTrigger)
|
||||
continue;
|
||||
|
||||
if (this.collidesWithNonMotion(neighbor, result)) {
|
||||
motion = motion.sub(result.value.minimumTranslationVector);
|
||||
this.shape.position = this.shape.position.sub(result.value.minimumTranslationVector);
|
||||
didCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 将形状位置返回到检查之前的位置
|
||||
this.shape.position = oldPosition;
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查此碰撞器是否与场景中的其他碰撞器碰撞。它相交的第一个碰撞器将在碰撞结果中返回碰撞数据。
|
||||
* @param result
|
||||
*/
|
||||
public collidesWithAnyNonMotion(result: Out<CollisionResult>) {
|
||||
result.value = new CollisionResult();
|
||||
// 在我们的新位置上获取我们可能会碰到的任何东西
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this, this.collidesWithLayers.value);
|
||||
|
||||
for (let neighbor of neighbors) {
|
||||
if (neighbor.isTrigger)
|
||||
continue;
|
||||
|
||||
if (this.collidesWithNonMotion(neighbor, result))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 多边形应该以顺时针方式定义
|
||||
*/
|
||||
export class PolygonCollider extends Collider {
|
||||
/**
|
||||
* 如果这些点没有居中,它们将以localOffset的差异为居中。
|
||||
* @param points
|
||||
*/
|
||||
constructor(points: Vector2[]) {
|
||||
super();
|
||||
|
||||
// 第一点和最后一点决不能相同。我们想要一个开放的多边形
|
||||
let isPolygonClosed = points[0] == points[points.length - 1];
|
||||
|
||||
// 最后一个移除
|
||||
if (isPolygonClosed)
|
||||
points = points.slice(0, points.length - 1);
|
||||
|
||||
let center = Polygon.findPolygonCenter(points);
|
||||
this.setLocalOffset(center);
|
||||
Polygon.recenterPolygonVerts(points);
|
||||
this.shape = new Polygon(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 扇形碰撞器
|
||||
*/
|
||||
export class SectorCollider extends Collider {
|
||||
constructor(
|
||||
center: Vector2,
|
||||
radius: number,
|
||||
startAngle: number,
|
||||
endAngle: number
|
||||
) {
|
||||
super();
|
||||
this.shape = new Sector(center, radius, startAngle, endAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 当添加到组件时,每当实体上的冲突器与另一个组件重叠/退出时,将调用这些方法。
|
||||
* ITriggerListener方法将在实现接口的触发器实体上的任何组件上调用。
|
||||
* 注意,这个接口只与Mover类一起工作
|
||||
*/
|
||||
export interface ITriggerListener {
|
||||
/**
|
||||
* 当碰撞器与触发碰撞器相交时调用。这是在触发碰撞器和触发碰撞器上调用的。
|
||||
* 移动必须由Mover/ProjectileMover方法处理,以使其自动工作。
|
||||
* @param other
|
||||
* @param local
|
||||
*/
|
||||
onTriggerEnter(other: Collider, local: Collider);
|
||||
|
||||
/**
|
||||
* 当另一个碰撞器离开触发碰撞器时调用
|
||||
* @param other
|
||||
* @param local
|
||||
*/
|
||||
onTriggerExit(other: Collider, local: Collider);
|
||||
}
|
||||
|
||||
export class TriggerListenerHelper {
|
||||
public static getITriggerListener(entity: Entity, components: ITriggerListener[]){
|
||||
if (entity.components._components.length > 0) {
|
||||
for (let i = 0; i < entity.components._components.length; i ++) {
|
||||
const component = entity.components._components[i];
|
||||
if (isITriggerListener(component)) {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i in entity.components._componentsToAdd) {
|
||||
let component = entity.components._componentsToAdd[i];
|
||||
if (isITriggerListener(component)) {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
export var isITriggerListener = (props: any): props is ITriggerListener => typeof (props as ITriggerListener)['onTriggerEnter'] !== 'undefined';
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 辅助类说明了一种处理移动的方法,它考虑了包括触发器在内的所有冲突。
|
||||
* ITriggerListener接口用于管理对移动过程中违反的任何触发器的回调。
|
||||
* 一个物体只能通过移动器移动。要正确报告触发器的move方法。
|
||||
*
|
||||
* 请注意,多个移动者相互交互将多次调用ITriggerListener。
|
||||
*/
|
||||
export class Mover extends Component {
|
||||
private _triggerHelper: ColliderTriggerHelper;
|
||||
|
||||
public onAddedToEntity() {
|
||||
this._triggerHelper = new ColliderTriggerHelper(this.entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算修改运动矢量的运动,以考虑移动时可能发生的碰撞
|
||||
* @param motion
|
||||
* @param collisionResult
|
||||
*/
|
||||
public calculateMovement(motion: Vector2, collisionResult: Out<CollisionResult>): boolean {
|
||||
collisionResult.value = new CollisionResult();
|
||||
|
||||
let collider = null;
|
||||
if (this.entity.components.buffer.length > 0)
|
||||
for (let i = 0; i < this.entity.components.buffer.length; i++) {
|
||||
let component = this.entity.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
collider = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (collider == null || this._triggerHelper == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移动所有的非触发碰撞器并获得最近的碰撞
|
||||
let colliders: Collider[] = [];
|
||||
if (this.entity.components.buffer.length > 0)
|
||||
for (let i = 0; i < this.entity.components.buffer.length; i ++) {
|
||||
let component = this.entity.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
colliders.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (colliders.length > 0) {
|
||||
for (let i = 0; i < colliders.length; i++) {
|
||||
let collider = colliders[i];
|
||||
|
||||
// 不检测触发器 在我们移动后会重新访问它
|
||||
if (collider.isTrigger)
|
||||
continue;
|
||||
|
||||
// 获取我们在新位置可能发生碰撞的任何东西
|
||||
let bounds = collider.bounds;
|
||||
bounds.x += motion.x;
|
||||
bounds.y += motion.y;
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, bounds, collider.collidesWithLayers.value);
|
||||
|
||||
if (neighbors.length > 0) {
|
||||
for (let i = 0; i < neighbors.length; i ++) {
|
||||
const neighbor = neighbors[i];
|
||||
// 不检测触发器
|
||||
if (neighbor.isTrigger)
|
||||
return;
|
||||
|
||||
let _internalcollisionResult = new Out<CollisionResult>();
|
||||
if (collider.collidesWith(neighbor, motion, _internalcollisionResult)) {
|
||||
// 如果碰撞 则退回之前的移动量
|
||||
motion.subEqual(_internalcollisionResult.value.minimumTranslationVector);
|
||||
// 如果我们碰到多个对象,为了简单起见,只取第一个。
|
||||
if (_internalcollisionResult.value.collider != null) {
|
||||
collisionResult.value.collider = _internalcollisionResult.value.collider;
|
||||
collisionResult.value.minimumTranslationVector = _internalcollisionResult.value.minimumTranslationVector;
|
||||
collisionResult.value.normal = _internalcollisionResult.value.normal;
|
||||
collisionResult.value.point = _internalcollisionResult.value.point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ListPool.free(Collider, colliders);
|
||||
|
||||
return collisionResult.value.collider != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将calculatemomovement应用到实体并更新triggerHelper
|
||||
* @param motion
|
||||
*/
|
||||
public applyMovement(motion: Vector2) {
|
||||
// 移动实体到它的新位置,如果我们有一个碰撞,否则移动全部数量。当碰撞发生时,运动被更新
|
||||
this.entity.position = Vector2.add(this.entity.position, motion);
|
||||
|
||||
// 对所有是触发器的碰撞器与所有宽相位碰撞器进行重叠检查。任何重叠都会导致触发事件。
|
||||
if (this._triggerHelper)
|
||||
this._triggerHelper.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过调用calculateMovement和applyMovement来移动考虑碰撞的实体;
|
||||
* @param motion
|
||||
* @param collisionResult
|
||||
*/
|
||||
public move(motion: Vector2, collisionResult: Out<CollisionResult>) {
|
||||
this.calculateMovement(motion, collisionResult);
|
||||
this.applyMovement(motion);
|
||||
return collisionResult.value.collider != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 移动时考虑到碰撞,只用于向任何ITriggerListeners报告。
|
||||
* 物体总是会全量移动,所以如果需要的话,由调用者在撞击时销毁它。
|
||||
*/
|
||||
export class ProjectileMover extends Component {
|
||||
private _tempTriggerList: ITriggerListener[] = [];
|
||||
private _collider: Collider;
|
||||
|
||||
public onAddedToEntity() {
|
||||
let collider = null;
|
||||
for (let i = 0; i < this.entity.components.buffer.length; i++) {
|
||||
let component = this.entity.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
collider = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._collider = collider;
|
||||
Debug.warnIf(this._collider == null, "ProjectileMover没有Collider。ProjectilMover需要一个Collider!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在考虑到碰撞的情况下移动实体
|
||||
* @param motion
|
||||
*/
|
||||
public move(motion: Vector2): boolean {
|
||||
if (this._collider == null)
|
||||
return false;
|
||||
|
||||
let didCollide = false;
|
||||
|
||||
// 获取我们在新的位置上可能会碰撞到的任何东西
|
||||
this.entity.position = Vector2.add(this.entity.position, motion);
|
||||
|
||||
// 获取任何可能在新位置发生碰撞的东西
|
||||
let neighbors = Physics.boxcastBroadphase(this._collider.bounds, this._collider.collidesWithLayers.value);
|
||||
if (neighbors.length > 0)
|
||||
for (let i = 0; i < neighbors.length; i ++) {
|
||||
const neighbor = neighbors[i];
|
||||
if (this._collider.overlaps(neighbor) && neighbor.enabled){
|
||||
didCollide = true;
|
||||
this.notifyTriggerListeners(this._collider, neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
private notifyTriggerListeners(self: Collider, other: Collider) {
|
||||
// 通知我们重叠的碰撞器实体上的任何侦听器
|
||||
TriggerListenerHelper.getITriggerListener(other.entity, this._tempTriggerList);
|
||||
for (let i = 0; i < this._tempTriggerList.length; i++) {
|
||||
this._tempTriggerList[i].onTriggerEnter(self, other);
|
||||
}
|
||||
this._tempTriggerList.length = 0;
|
||||
|
||||
// 通知此实体上的任何侦听器
|
||||
TriggerListenerHelper.getITriggerListener(this.entity, this._tempTriggerList);
|
||||
for (let i = 0; i < this._tempTriggerList.length; i++) {
|
||||
this._tempTriggerList[i].onTriggerEnter(other, self);
|
||||
}
|
||||
this._tempTriggerList.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,64 @@
|
||||
module es {
|
||||
export class SceneComponent implements IComparer<SceneComponent> {
|
||||
/**
|
||||
* 这个场景组件被附加到的场景
|
||||
*/
|
||||
public scene: Scene;
|
||||
import type { Scene } from '../Scene';
|
||||
|
||||
/**
|
||||
* 如果启用了SceneComponent,则为true。状态的改变会导致调用onEnabled/onDisable。
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
/**
|
||||
* 场景组件基类
|
||||
* 附加到场景的组件,用于实现场景级别的功能
|
||||
*/
|
||||
export class SceneComponent {
|
||||
/** 组件所属的场景 */
|
||||
public scene!: Scene;
|
||||
/** 更新顺序 */
|
||||
public updateOrder: number = 0;
|
||||
/** 是否启用 */
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 如果启用了SceneComponent,则为true。状态的改变会导致调用onEnabled/onDisable。
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
/** 获取是否启用 */
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新此场景中SceneComponents的顺序
|
||||
*/
|
||||
public updateOrder: number = 0;
|
||||
|
||||
public _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 在启用此SceneComponent时调用
|
||||
*/
|
||||
public onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当禁用此SceneComponent时调用
|
||||
*/
|
||||
public onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当该SceneComponent从场景中移除时调用
|
||||
*/
|
||||
public onRemovedFromScene() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在实体更新之前每一帧调用
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用这个SceneComponent
|
||||
* @param isEnabled
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean): SceneComponent {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
/** 设置是否启用 */
|
||||
public set enabled(value: boolean) {
|
||||
if (this._enabled !== value) {
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SceneComponent的updateOrder并触发某种SceneComponent
|
||||
* @param updateOrder
|
||||
*/
|
||||
public setUpdateOrder(updateOrder: number) {
|
||||
if (this.updateOrder != updateOrder) {
|
||||
this.updateOrder = updateOrder;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public compare(other: SceneComponent): number {
|
||||
return this.updateOrder - other.updateOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当组件启用时调用
|
||||
*/
|
||||
public onEnabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当组件禁用时调用
|
||||
*/
|
||||
public onDisabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当组件从场景中移除时调用
|
||||
*/
|
||||
public onRemovedFromScene(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧更新
|
||||
*/
|
||||
public update(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较组件的更新顺序
|
||||
* @param other 其他组件
|
||||
* @returns 比较结果
|
||||
*/
|
||||
public compare(other: SceneComponent): number {
|
||||
return this.updateOrder - other.updateOrder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
import { Component } from '../Component';
|
||||
|
||||
/**
|
||||
* 组件类型定义
|
||||
*/
|
||||
export type ComponentType<T extends Component = Component> = new (...args: any[]) => T;
|
||||
|
||||
/**
|
||||
* 组件注册表
|
||||
* 管理组件类型的位掩码分配
|
||||
*/
|
||||
export class ComponentRegistry {
|
||||
private static componentTypes = new Map<Function, number>();
|
||||
private static nextBitIndex = 0;
|
||||
private static maxComponents = 64; // 支持最多64种组件类型
|
||||
|
||||
/**
|
||||
* 注册组件类型并分配位掩码
|
||||
* @param componentType 组件类型
|
||||
* @returns 分配的位索引
|
||||
*/
|
||||
public static register<T extends Component>(componentType: ComponentType<T>): number {
|
||||
if (this.componentTypes.has(componentType)) {
|
||||
return this.componentTypes.get(componentType)!;
|
||||
}
|
||||
|
||||
if (this.nextBitIndex >= this.maxComponents) {
|
||||
throw new Error(`Maximum number of component types (${this.maxComponents}) exceeded`);
|
||||
}
|
||||
|
||||
const bitIndex = this.nextBitIndex++;
|
||||
this.componentTypes.set(componentType, bitIndex);
|
||||
return bitIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型的位掩码
|
||||
* @param componentType 组件类型
|
||||
* @returns 位掩码
|
||||
*/
|
||||
public static getBitMask<T extends Component>(componentType: ComponentType<T>): bigint {
|
||||
const bitIndex = this.componentTypes.get(componentType);
|
||||
if (bitIndex === undefined) {
|
||||
throw new Error(`Component type ${componentType.name} is not registered`);
|
||||
}
|
||||
return BigInt(1) << BigInt(bitIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型的位索引
|
||||
* @param componentType 组件类型
|
||||
* @returns 位索引
|
||||
*/
|
||||
public static getBitIndex<T extends Component>(componentType: ComponentType<T>): number {
|
||||
const bitIndex = this.componentTypes.get(componentType);
|
||||
if (bitIndex === undefined) {
|
||||
throw new Error(`Component type ${componentType.name} is not registered`);
|
||||
}
|
||||
return bitIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件类型是否已注册
|
||||
* @param componentType 组件类型
|
||||
* @returns 是否已注册
|
||||
*/
|
||||
public static isRegistered<T extends Component>(componentType: ComponentType<T>): boolean {
|
||||
return this.componentTypes.has(componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的组件类型
|
||||
* @returns 组件类型映射
|
||||
*/
|
||||
public static getAllRegisteredTypes(): Map<Function, number> {
|
||||
return new Map(this.componentTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 高性能组件存储器
|
||||
* 使用SoA(Structure of Arrays)模式存储组件
|
||||
*/
|
||||
export class ComponentStorage<T extends Component> {
|
||||
private components: (T | null)[] = [];
|
||||
private entityToIndex = new Map<number, number>();
|
||||
private indexToEntity: number[] = [];
|
||||
private freeIndices: number[] = [];
|
||||
private componentType: ComponentType<T>;
|
||||
private _size = 0;
|
||||
|
||||
constructor(componentType: ComponentType<T>) {
|
||||
this.componentType = componentType;
|
||||
|
||||
// 确保组件类型已注册
|
||||
if (!ComponentRegistry.isRegistered(componentType)) {
|
||||
ComponentRegistry.register(componentType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加组件
|
||||
* @param entityId 实体ID
|
||||
* @param component 组件实例
|
||||
*/
|
||||
public addComponent(entityId: number, component: T): void {
|
||||
// 检查实体是否已有此组件
|
||||
if (this.entityToIndex.has(entityId)) {
|
||||
throw new Error(`Entity ${entityId} already has component ${this.componentType.name}`);
|
||||
}
|
||||
|
||||
let index: number;
|
||||
|
||||
if (this.freeIndices.length > 0) {
|
||||
// 重用空闲索引
|
||||
index = this.freeIndices.pop()!;
|
||||
this.components[index] = component;
|
||||
this.indexToEntity[index] = entityId;
|
||||
} else {
|
||||
// 添加到末尾
|
||||
index = this.components.length;
|
||||
this.components.push(component);
|
||||
this.indexToEntity.push(entityId);
|
||||
}
|
||||
|
||||
this.entityToIndex.set(entityId, index);
|
||||
this._size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件
|
||||
* @param entityId 实体ID
|
||||
* @returns 组件实例或null
|
||||
*/
|
||||
public getComponent(entityId: number): T | null {
|
||||
const index = this.entityToIndex.get(entityId);
|
||||
return index !== undefined ? this.components[index] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否有此组件
|
||||
* @param entityId 实体ID
|
||||
* @returns 是否有组件
|
||||
*/
|
||||
public hasComponent(entityId: number): boolean {
|
||||
return this.entityToIndex.has(entityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除组件
|
||||
* @param entityId 实体ID
|
||||
* @returns 被移除的组件或null
|
||||
*/
|
||||
public removeComponent(entityId: number): T | null {
|
||||
const index = this.entityToIndex.get(entityId);
|
||||
if (index === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const component = this.components[index];
|
||||
this.entityToIndex.delete(entityId);
|
||||
this.components[index] = null;
|
||||
this.freeIndices.push(index);
|
||||
this._size--;
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高效遍历所有组件
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
public forEach(callback: (component: T, entityId: number, index: number) => void): void {
|
||||
for (let i = 0; i < this.components.length; i++) {
|
||||
const component = this.components[i];
|
||||
if (component) {
|
||||
callback(component, this.indexToEntity[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有组件(密集数组)
|
||||
* @returns 组件数组
|
||||
*/
|
||||
public getDenseArray(): { components: T[]; entityIds: number[] } {
|
||||
const components: T[] = [];
|
||||
const entityIds: number[] = [];
|
||||
|
||||
for (let i = 0; i < this.components.length; i++) {
|
||||
const component = this.components[i];
|
||||
if (component) {
|
||||
components.push(component);
|
||||
entityIds.push(this.indexToEntity[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return { components, entityIds };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有组件
|
||||
*/
|
||||
public clear(): void {
|
||||
this.components.length = 0;
|
||||
this.entityToIndex.clear();
|
||||
this.indexToEntity.length = 0;
|
||||
this.freeIndices.length = 0;
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件数量
|
||||
*/
|
||||
public get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型
|
||||
*/
|
||||
public get type(): ComponentType<T> {
|
||||
return this.componentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩存储(移除空洞)
|
||||
*/
|
||||
public compact(): void {
|
||||
if (this.freeIndices.length === 0) {
|
||||
return; // 没有空洞,无需压缩
|
||||
}
|
||||
|
||||
const newComponents: T[] = [];
|
||||
const newIndexToEntity: number[] = [];
|
||||
const newEntityToIndex = new Map<number, number>();
|
||||
|
||||
let newIndex = 0;
|
||||
for (let i = 0; i < this.components.length; i++) {
|
||||
const component = this.components[i];
|
||||
if (component) {
|
||||
newComponents[newIndex] = component;
|
||||
newIndexToEntity[newIndex] = this.indexToEntity[i];
|
||||
newEntityToIndex.set(this.indexToEntity[i], newIndex);
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
this.components = newComponents;
|
||||
this.indexToEntity = newIndexToEntity;
|
||||
this.entityToIndex = newEntityToIndex;
|
||||
this.freeIndices.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalSlots: number;
|
||||
usedSlots: number;
|
||||
freeSlots: number;
|
||||
fragmentation: number;
|
||||
} {
|
||||
const totalSlots = this.components.length;
|
||||
const usedSlots = this._size;
|
||||
const freeSlots = this.freeIndices.length;
|
||||
const fragmentation = totalSlots > 0 ? freeSlots / totalSlots : 0;
|
||||
|
||||
return {
|
||||
totalSlots,
|
||||
usedSlots,
|
||||
freeSlots,
|
||||
fragmentation
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件存储管理器
|
||||
* 管理所有组件类型的存储器
|
||||
*/
|
||||
export class ComponentStorageManager {
|
||||
private storages = new Map<Function, ComponentStorage<any>>();
|
||||
|
||||
/**
|
||||
* 获取或创建组件存储器
|
||||
* @param componentType 组件类型
|
||||
* @returns 组件存储器
|
||||
*/
|
||||
public getStorage<T extends Component>(componentType: ComponentType<T>): ComponentStorage<T> {
|
||||
let storage = this.storages.get(componentType);
|
||||
|
||||
if (!storage) {
|
||||
storage = new ComponentStorage(componentType);
|
||||
this.storages.set(componentType, storage);
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加组件
|
||||
* @param entityId 实体ID
|
||||
* @param component 组件实例
|
||||
*/
|
||||
public addComponent<T extends Component>(entityId: number, component: T): void {
|
||||
const componentType = component.constructor as ComponentType<T>;
|
||||
const storage = this.getStorage(componentType);
|
||||
storage.addComponent(entityId, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件
|
||||
* @param entityId 实体ID
|
||||
* @param componentType 组件类型
|
||||
* @returns 组件实例或null
|
||||
*/
|
||||
public getComponent<T extends Component>(entityId: number, componentType: ComponentType<T>): T | null {
|
||||
const storage = this.storages.get(componentType);
|
||||
return storage ? storage.getComponent(entityId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否有组件
|
||||
* @param entityId 实体ID
|
||||
* @param componentType 组件类型
|
||||
* @returns 是否有组件
|
||||
*/
|
||||
public hasComponent<T extends Component>(entityId: number, componentType: ComponentType<T>): boolean {
|
||||
const storage = this.storages.get(componentType);
|
||||
return storage ? storage.hasComponent(entityId) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除组件
|
||||
* @param entityId 实体ID
|
||||
* @param componentType 组件类型
|
||||
* @returns 被移除的组件或null
|
||||
*/
|
||||
public removeComponent<T extends Component>(entityId: number, componentType: ComponentType<T>): T | null {
|
||||
const storage = this.storages.get(componentType);
|
||||
return storage ? storage.removeComponent(entityId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除实体的所有组件
|
||||
* @param entityId 实体ID
|
||||
*/
|
||||
public removeAllComponents(entityId: number): void {
|
||||
for (const storage of this.storages.values()) {
|
||||
storage.removeComponent(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param entityId 实体ID
|
||||
* @returns 组件位掩码
|
||||
*/
|
||||
public getComponentMask(entityId: number): bigint {
|
||||
let mask = BigInt(0);
|
||||
|
||||
for (const [componentType, storage] of this.storages.entries()) {
|
||||
if (storage.hasComponent(entityId)) {
|
||||
mask |= ComponentRegistry.getBitMask(componentType as ComponentType);
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩所有存储器
|
||||
*/
|
||||
public compactAll(): void {
|
||||
for (const storage of this.storages.values()) {
|
||||
storage.compact();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有存储器的统计信息
|
||||
*/
|
||||
public getAllStats(): Map<string, any> {
|
||||
const stats = new Map<string, any>();
|
||||
|
||||
for (const [componentType, storage] of this.storages.entries()) {
|
||||
const typeName = (componentType as any).name || 'Unknown';
|
||||
stats.set(typeName, storage.getStats());
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有存储器
|
||||
*/
|
||||
public clear(): void {
|
||||
for (const storage of this.storages.values()) {
|
||||
storage.clear();
|
||||
}
|
||||
this.storages.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
/**
|
||||
* 事件处理器函数类型
|
||||
*/
|
||||
export type EventHandler<T = any> = (event: T) => void;
|
||||
|
||||
/**
|
||||
* 异步事件处理器函数类型
|
||||
*/
|
||||
export type AsyncEventHandler<T = any> = (event: T) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 事件监听器配置
|
||||
*/
|
||||
export interface EventListenerConfig {
|
||||
/** 是否只执行一次 */
|
||||
once?: boolean;
|
||||
/** 优先级(数字越大优先级越高) */
|
||||
priority?: number;
|
||||
/** 是否异步执行 */
|
||||
async?: boolean;
|
||||
/** 执行上下文 */
|
||||
context?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部事件监听器
|
||||
*/
|
||||
interface InternalEventListener<T = any> {
|
||||
handler: EventHandler<T> | AsyncEventHandler<T>;
|
||||
config: EventListenerConfig;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件统计信息
|
||||
*/
|
||||
export interface EventStats {
|
||||
/** 事件类型 */
|
||||
eventType: string;
|
||||
/** 监听器数量 */
|
||||
listenerCount: number;
|
||||
/** 触发次数 */
|
||||
triggerCount: number;
|
||||
/** 总执行时间(毫秒) */
|
||||
totalExecutionTime: number;
|
||||
/** 平均执行时间(毫秒) */
|
||||
averageExecutionTime: number;
|
||||
/** 最后触发时间 */
|
||||
lastTriggerTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件批处理配置
|
||||
*/
|
||||
export interface EventBatchConfig {
|
||||
/** 批处理大小 */
|
||||
batchSize: number;
|
||||
/** 批处理延迟(毫秒) */
|
||||
delay: number;
|
||||
/** 是否启用批处理 */
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型安全的高性能事件系统
|
||||
* 支持同步/异步事件、优先级、批处理等功能
|
||||
*/
|
||||
export class TypeSafeEventSystem {
|
||||
private listeners = new Map<string, InternalEventListener[]>();
|
||||
private stats = new Map<string, EventStats>();
|
||||
private batchQueue = new Map<string, any[]>();
|
||||
private batchTimers = new Map<string, number>();
|
||||
private batchConfigs = new Map<string, EventBatchConfig>();
|
||||
private nextListenerId = 0;
|
||||
private isEnabled = true;
|
||||
private maxListeners = 100; // 每个事件类型的最大监听器数量
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件处理器
|
||||
* @param config 监听器配置
|
||||
* @returns 监听器ID(用于移除)
|
||||
*/
|
||||
public on<T>(
|
||||
eventType: string,
|
||||
handler: EventHandler<T>,
|
||||
config: EventListenerConfig = {}
|
||||
): string {
|
||||
return this.addListener(eventType, handler, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一次性事件监听器
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件处理器
|
||||
* @param config 监听器配置
|
||||
* @returns 监听器ID
|
||||
*/
|
||||
public once<T>(
|
||||
eventType: string,
|
||||
handler: EventHandler<T>,
|
||||
config: EventListenerConfig = {}
|
||||
): string {
|
||||
return this.addListener(eventType, handler, { ...config, once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加异步事件监听器
|
||||
* @param eventType 事件类型
|
||||
* @param handler 异步事件处理器
|
||||
* @param config 监听器配置
|
||||
* @returns 监听器ID
|
||||
*/
|
||||
public onAsync<T>(
|
||||
eventType: string,
|
||||
handler: AsyncEventHandler<T>,
|
||||
config: EventListenerConfig = {}
|
||||
): string {
|
||||
return this.addListener(eventType, handler, { ...config, async: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param eventType 事件类型
|
||||
* @param listenerId 监听器ID
|
||||
* @returns 是否成功移除
|
||||
*/
|
||||
public off(eventType: string, listenerId: string): boolean {
|
||||
const listeners = this.listeners.get(eventType);
|
||||
if (!listeners) return false;
|
||||
|
||||
const index = listeners.findIndex(l => l.id === listenerId);
|
||||
if (index === -1) return false;
|
||||
|
||||
listeners.splice(index, 1);
|
||||
|
||||
// 如果没有监听器了,清理相关数据
|
||||
if (listeners.length === 0) {
|
||||
this.listeners.delete(eventType);
|
||||
this.stats.delete(eventType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定事件类型的所有监听器
|
||||
* @param eventType 事件类型
|
||||
*/
|
||||
public offAll(eventType: string): void {
|
||||
this.listeners.delete(eventType);
|
||||
this.stats.delete(eventType);
|
||||
this.clearBatch(eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param eventType 事件类型
|
||||
* @param event 事件数据
|
||||
* @returns Promise(如果有异步监听器)
|
||||
*/
|
||||
public async emit<T>(eventType: string, event: T): Promise<void> {
|
||||
if (!this.isEnabled) return;
|
||||
|
||||
// 检查是否启用了批处理
|
||||
const batchConfig = this.batchConfigs.get(eventType);
|
||||
if (batchConfig?.enabled) {
|
||||
this.addToBatch(eventType, event);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.executeEvent(eventType, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步触发事件(忽略异步监听器)
|
||||
* @param eventType 事件类型
|
||||
* @param event 事件数据
|
||||
*/
|
||||
public emitSync<T>(eventType: string, event: T): void {
|
||||
if (!this.isEnabled) return;
|
||||
|
||||
const listeners = this.listeners.get(eventType);
|
||||
if (!listeners || listeners.length === 0) return;
|
||||
|
||||
const startTime = performance.now();
|
||||
const toRemove: string[] = [];
|
||||
|
||||
// 按优先级排序
|
||||
const sortedListeners = this.sortListenersByPriority(listeners);
|
||||
|
||||
for (const listener of sortedListeners) {
|
||||
if (listener.config.async) continue; // 跳过异步监听器
|
||||
|
||||
try {
|
||||
if (listener.config.context) {
|
||||
(listener.handler as EventHandler<T>).call(listener.config.context, event);
|
||||
} else {
|
||||
(listener.handler as EventHandler<T>)(event);
|
||||
}
|
||||
|
||||
if (listener.config.once) {
|
||||
toRemove.push(listener.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in event handler for ${eventType}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除一次性监听器
|
||||
this.removeListeners(eventType, toRemove);
|
||||
|
||||
// 更新统计信息
|
||||
this.updateStats(eventType, performance.now() - startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件批处理配置
|
||||
* @param eventType 事件类型
|
||||
* @param config 批处理配置
|
||||
*/
|
||||
public setBatchConfig(eventType: string, config: EventBatchConfig): void {
|
||||
this.batchConfigs.set(eventType, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即处理指定事件类型的批处理队列
|
||||
* @param eventType 事件类型
|
||||
*/
|
||||
public flushBatch(eventType: string): void {
|
||||
const batch = this.batchQueue.get(eventType);
|
||||
if (!batch || batch.length === 0) return;
|
||||
|
||||
// 清除定时器
|
||||
const timer = this.batchTimers.get(eventType);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.batchTimers.delete(eventType);
|
||||
}
|
||||
|
||||
// 处理批处理事件
|
||||
this.processBatch(eventType, batch);
|
||||
|
||||
// 清空队列
|
||||
this.batchQueue.delete(eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件统计信息
|
||||
* @param eventType 事件类型(可选)
|
||||
* @returns 统计信息
|
||||
*/
|
||||
public getStats(eventType?: string): EventStats | Map<string, EventStats> {
|
||||
if (eventType) {
|
||||
return this.stats.get(eventType) || this.createEmptyStats(eventType);
|
||||
}
|
||||
return new Map(this.stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
* @param eventType 事件类型(可选,不指定则重置所有)
|
||||
*/
|
||||
public resetStats(eventType?: string): void {
|
||||
if (eventType) {
|
||||
this.stats.delete(eventType);
|
||||
} else {
|
||||
this.stats.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用事件系统
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
public setEnabled(enabled: boolean): void {
|
||||
this.isEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有指定事件类型的监听器
|
||||
* @param eventType 事件类型
|
||||
* @returns 是否有监听器
|
||||
*/
|
||||
public hasListeners(eventType: string): boolean {
|
||||
const listeners = this.listeners.get(eventType);
|
||||
return listeners ? listeners.length > 0 : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定事件类型的监听器数量
|
||||
* @param eventType 事件类型
|
||||
* @returns 监听器数量
|
||||
*/
|
||||
public getListenerCount(eventType: string): number {
|
||||
const listeners = this.listeners.get(eventType);
|
||||
return listeners ? listeners.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有事件监听器和数据
|
||||
*/
|
||||
public clear(): void {
|
||||
this.listeners.clear();
|
||||
this.stats.clear();
|
||||
this.clearAllBatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置每个事件类型的最大监听器数量
|
||||
* @param max 最大数量
|
||||
*/
|
||||
public setMaxListeners(max: number): void {
|
||||
this.maxListeners = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加监听器的内部实现
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件处理器
|
||||
* @param config 配置
|
||||
* @returns 监听器ID
|
||||
*/
|
||||
private addListener<T>(
|
||||
eventType: string,
|
||||
handler: EventHandler<T> | AsyncEventHandler<T>,
|
||||
config: EventListenerConfig
|
||||
): string {
|
||||
let listeners = this.listeners.get(eventType);
|
||||
|
||||
if (!listeners) {
|
||||
listeners = [];
|
||||
this.listeners.set(eventType, listeners);
|
||||
}
|
||||
|
||||
// 检查监听器数量限制
|
||||
if (listeners.length >= this.maxListeners) {
|
||||
console.warn(`Maximum listeners (${this.maxListeners}) exceeded for event type: ${eventType}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const listenerId = `listener_${this.nextListenerId++}`;
|
||||
const listener: InternalEventListener<T> = {
|
||||
handler,
|
||||
config: {
|
||||
priority: 0,
|
||||
...config
|
||||
},
|
||||
id: listenerId
|
||||
};
|
||||
|
||||
listeners.push(listener);
|
||||
|
||||
// 初始化统计信息
|
||||
if (!this.stats.has(eventType)) {
|
||||
this.stats.set(eventType, this.createEmptyStats(eventType));
|
||||
}
|
||||
|
||||
return listenerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行事件的内部实现
|
||||
* @param eventType 事件类型
|
||||
* @param event 事件数据
|
||||
*/
|
||||
private async executeEvent<T>(eventType: string, event: T): Promise<void> {
|
||||
const listeners = this.listeners.get(eventType);
|
||||
if (!listeners || listeners.length === 0) return;
|
||||
|
||||
const startTime = performance.now();
|
||||
const toRemove: string[] = [];
|
||||
|
||||
// 按优先级排序
|
||||
const sortedListeners = this.sortListenersByPriority(listeners);
|
||||
|
||||
// 分离同步和异步监听器
|
||||
const syncListeners = sortedListeners.filter(l => !l.config.async);
|
||||
const asyncListeners = sortedListeners.filter(l => l.config.async);
|
||||
|
||||
// 执行同步监听器
|
||||
for (const listener of syncListeners) {
|
||||
try {
|
||||
if (listener.config.context) {
|
||||
(listener.handler as EventHandler<T>).call(listener.config.context, event);
|
||||
} else {
|
||||
(listener.handler as EventHandler<T>)(event);
|
||||
}
|
||||
|
||||
if (listener.config.once) {
|
||||
toRemove.push(listener.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in sync event handler for ${eventType}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行异步监听器
|
||||
const asyncPromises = asyncListeners.map(async (listener) => {
|
||||
try {
|
||||
if (listener.config.context) {
|
||||
await (listener.handler as AsyncEventHandler<T>).call(listener.config.context, event);
|
||||
} else {
|
||||
await (listener.handler as AsyncEventHandler<T>)(event);
|
||||
}
|
||||
|
||||
if (listener.config.once) {
|
||||
toRemove.push(listener.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in async event handler for ${eventType}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
// 等待所有异步监听器完成
|
||||
await Promise.all(asyncPromises);
|
||||
|
||||
// 移除一次性监听器
|
||||
this.removeListeners(eventType, toRemove);
|
||||
|
||||
// 更新统计信息
|
||||
this.updateStats(eventType, performance.now() - startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按优先级排序监听器
|
||||
* @param listeners 监听器数组
|
||||
* @returns 排序后的监听器数组
|
||||
*/
|
||||
private sortListenersByPriority<T>(listeners: InternalEventListener<T>[]): InternalEventListener<T>[] {
|
||||
return listeners.slice().sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定的监听器
|
||||
* @param eventType 事件类型
|
||||
* @param listenerIds 要移除的监听器ID数组
|
||||
*/
|
||||
private removeListeners(eventType: string, listenerIds: string[]): void {
|
||||
if (listenerIds.length === 0) return;
|
||||
|
||||
const listeners = this.listeners.get(eventType);
|
||||
if (!listeners) return;
|
||||
|
||||
for (const id of listenerIds) {
|
||||
const index = listeners.findIndex(l => l.id === id);
|
||||
if (index !== -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有监听器了,清理相关数据
|
||||
if (listeners.length === 0) {
|
||||
this.listeners.delete(eventType);
|
||||
this.stats.delete(eventType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件到批处理队列
|
||||
* @param eventType 事件类型
|
||||
* @param event 事件数据
|
||||
*/
|
||||
private addToBatch<T>(eventType: string, event: T): void {
|
||||
let batch = this.batchQueue.get(eventType);
|
||||
if (!batch) {
|
||||
batch = [];
|
||||
this.batchQueue.set(eventType, batch);
|
||||
}
|
||||
|
||||
batch.push(event);
|
||||
|
||||
const config = this.batchConfigs.get(eventType)!;
|
||||
|
||||
// 如果达到批处理大小,立即处理
|
||||
if (batch.length >= config.batchSize) {
|
||||
this.flushBatch(eventType);
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置延迟处理定时器
|
||||
if (!this.batchTimers.has(eventType)) {
|
||||
const timer = setTimeout(() => {
|
||||
this.flushBatch(eventType);
|
||||
}, config.delay);
|
||||
|
||||
this.batchTimers.set(eventType, timer as any);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理批处理事件
|
||||
* @param eventType 事件类型
|
||||
* @param batch 批处理事件数组
|
||||
*/
|
||||
private async processBatch<T>(eventType: string, batch: T[]): Promise<void> {
|
||||
// 创建批处理事件对象
|
||||
const batchEvent = {
|
||||
type: eventType,
|
||||
events: batch,
|
||||
count: batch.length,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// 触发批处理事件
|
||||
await this.executeEvent(`${eventType}:batch`, batchEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定事件类型的批处理
|
||||
* @param eventType 事件类型
|
||||
*/
|
||||
private clearBatch(eventType: string): void {
|
||||
this.batchQueue.delete(eventType);
|
||||
|
||||
const timer = this.batchTimers.get(eventType);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.batchTimers.delete(eventType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有批处理
|
||||
*/
|
||||
private clearAllBatches(): void {
|
||||
this.batchQueue.clear();
|
||||
|
||||
for (const timer of this.batchTimers.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
this.batchTimers.clear();
|
||||
this.batchConfigs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新事件统计信息
|
||||
* @param eventType 事件类型
|
||||
* @param executionTime 执行时间
|
||||
*/
|
||||
private updateStats(eventType: string, executionTime: number): void {
|
||||
let stats = this.stats.get(eventType);
|
||||
if (!stats) {
|
||||
stats = this.createEmptyStats(eventType);
|
||||
this.stats.set(eventType, stats);
|
||||
}
|
||||
|
||||
stats.triggerCount++;
|
||||
stats.totalExecutionTime += executionTime;
|
||||
stats.averageExecutionTime = stats.totalExecutionTime / stats.triggerCount;
|
||||
stats.lastTriggerTime = Date.now();
|
||||
stats.listenerCount = this.getListenerCount(eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空的统计信息
|
||||
* @param eventType 事件类型
|
||||
* @returns 空的统计信息
|
||||
*/
|
||||
private createEmptyStats(eventType: string): EventStats {
|
||||
return {
|
||||
eventType,
|
||||
listenerCount: 0,
|
||||
triggerCount: 0,
|
||||
totalExecutionTime: 0,
|
||||
averageExecutionTime: 0,
|
||||
lastTriggerTime: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局事件系统实例
|
||||
*/
|
||||
export const GlobalEventSystem = new TypeSafeEventSystem();
|
||||
|
||||
/**
|
||||
* 事件装饰器 - 用于自动注册事件监听器
|
||||
* @param eventType 事件类型
|
||||
* @param config 监听器配置
|
||||
*/
|
||||
export function EventListener(eventType: string, config: EventListenerConfig = {}) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// 在类实例化时自动注册监听器
|
||||
const initMethod = target.constructor.prototype.initEventListeners || function() {};
|
||||
target.constructor.prototype.initEventListeners = function() {
|
||||
initMethod.call(this);
|
||||
GlobalEventSystem.on(eventType, originalMethod.bind(this), config);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步事件装饰器
|
||||
* @param eventType 事件类型
|
||||
* @param config 监听器配置
|
||||
*/
|
||||
export function AsyncEventListener(eventType: string, config: EventListenerConfig = {}) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
const initMethod = target.constructor.prototype.initEventListeners || function() {};
|
||||
target.constructor.prototype.initEventListeners = function() {
|
||||
initMethod.call(this);
|
||||
GlobalEventSystem.onAsync(eventType, originalMethod.bind(this), config);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { Scene } from '../Scene';
|
||||
import { ComponentType, ComponentStorageManager } from './ComponentStorage';
|
||||
import { QuerySystem, QueryBuilder } from './QuerySystem';
|
||||
import { TypeSafeEventSystem } from './EventSystem';
|
||||
|
||||
/**
|
||||
* 实体构建器 - 提供流式API创建和配置实体
|
||||
*/
|
||||
export class EntityBuilder {
|
||||
private entity: Entity;
|
||||
private scene: Scene;
|
||||
private storageManager: ComponentStorageManager;
|
||||
|
||||
constructor(scene: Scene, storageManager: ComponentStorageManager) {
|
||||
this.scene = scene;
|
||||
this.storageManager = storageManager;
|
||||
this.entity = new Entity("", scene.identifierPool.checkOut());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体名称
|
||||
* @param name 实体名称
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public named(name: string): EntityBuilder {
|
||||
this.entity.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体标签
|
||||
* @param tag 标签
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public tagged(tag: number): EntityBuilder {
|
||||
this.entity.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加组件
|
||||
* @param component 组件实例
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public with<T extends Component>(component: T): EntityBuilder {
|
||||
this.entity.addComponent(component);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加多个组件
|
||||
* @param components 组件数组
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withComponents(...components: Component[]): EntityBuilder {
|
||||
for (const component of components) {
|
||||
this.entity.addComponent(component);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件性添加组件
|
||||
* @param condition 条件
|
||||
* @param component 组件实例
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withIf<T extends Component>(condition: boolean, component: T): EntityBuilder {
|
||||
if (condition) {
|
||||
this.entity.addComponent(component);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用工厂函数创建并添加组件
|
||||
* @param factory 组件工厂函数
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withFactory<T extends Component>(factory: () => T): EntityBuilder {
|
||||
const component = factory();
|
||||
this.entity.addComponent(component);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置组件属性
|
||||
* @param componentType 组件类型
|
||||
* @param configurator 配置函数
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public configure<T extends Component>(
|
||||
componentType: ComponentType<T>,
|
||||
configurator: (component: T) => void
|
||||
): EntityBuilder {
|
||||
const component = this.entity.getComponent(componentType);
|
||||
if (component) {
|
||||
configurator(component);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体位置(如果有Transform组件)
|
||||
* @param x X坐标
|
||||
* @param y Y坐标
|
||||
* @param z Z坐标(可选)
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public at(x: number, y: number, z: number = 0): EntityBuilder {
|
||||
// 直接使用Entity的position属性
|
||||
this.entity.position.x = x;
|
||||
this.entity.position.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体旋转(如果有Transform组件)
|
||||
* @param rotation 旋转角度
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public rotated(rotation: number): EntityBuilder {
|
||||
this.entity.rotation = rotation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体缩放(如果有Transform组件)
|
||||
* @param scaleX X轴缩放
|
||||
* @param scaleY Y轴缩放(可选,默认与X轴相同)
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public scaled(scaleX: number, scaleY?: number): EntityBuilder {
|
||||
this.entity.scale.x = scaleX;
|
||||
this.entity.scale.y = scaleY !== undefined ? scaleY : scaleX;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体为启用状态
|
||||
* @param enabled 是否启用
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public enabled(enabled: boolean = true): EntityBuilder {
|
||||
this.entity.enabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体为活跃状态
|
||||
* @param active 是否活跃
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public active(active: boolean = true): EntityBuilder {
|
||||
this.entity.active = active;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子实体
|
||||
* @param childBuilder 子实体构建器
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withChild(childBuilder: EntityBuilder): EntityBuilder {
|
||||
const child = childBuilder.build();
|
||||
this.entity.addChild(child);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加子实体
|
||||
* @param childBuilders 子实体构建器数组
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withChildren(...childBuilders: EntityBuilder[]): EntityBuilder {
|
||||
for (const childBuilder of childBuilders) {
|
||||
const child = childBuilder.build();
|
||||
this.entity.addChild(child);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用工厂函数创建子实体
|
||||
* @param childFactory 子实体工厂函数
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withChildFactory(childFactory: (parent: Entity) => EntityBuilder): EntityBuilder {
|
||||
const childBuilder = childFactory(this.entity);
|
||||
const child = childBuilder.build();
|
||||
this.entity.addChild(child);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件性添加子实体
|
||||
* @param condition 条件
|
||||
* @param childBuilder 子实体构建器
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public withChildIf(condition: boolean, childBuilder: EntityBuilder): EntityBuilder {
|
||||
if (condition) {
|
||||
const child = childBuilder.build();
|
||||
this.entity.addChild(child);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建并返回实体
|
||||
* @returns 构建的实体
|
||||
*/
|
||||
public build(): Entity {
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建实体并添加到场景
|
||||
* @returns 构建的实体
|
||||
*/
|
||||
public spawn(): Entity {
|
||||
this.scene.addEntity(this.entity);
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆当前构建器
|
||||
* @returns 新的实体构建器
|
||||
*/
|
||||
public clone(): EntityBuilder {
|
||||
const newBuilder = new EntityBuilder(this.scene, this.storageManager);
|
||||
// 这里需要深度克隆实体,简化实现
|
||||
newBuilder.entity = this.entity; // 实际应该是深度克隆
|
||||
return newBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景构建器 - 提供流式API创建和配置场景
|
||||
*/
|
||||
export class SceneBuilder {
|
||||
private scene: Scene;
|
||||
|
||||
constructor() {
|
||||
this.scene = new Scene();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置场景名称
|
||||
* @param name 场景名称
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public named(name: string): SceneBuilder {
|
||||
this.scene.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体
|
||||
* @param entity 实体
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public withEntity(entity: Entity): SceneBuilder {
|
||||
this.scene.addEntity(entity);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用实体构建器添加实体
|
||||
* @param builderFn 实体构建器函数
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public withEntityBuilder(builderFn: (builder: EntityBuilder) => EntityBuilder): SceneBuilder {
|
||||
const builder = new EntityBuilder(this.scene, this.scene.componentStorageManager);
|
||||
const configuredBuilder = builderFn(builder);
|
||||
const entity = configuredBuilder.build();
|
||||
this.scene.addEntity(entity);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加实体
|
||||
* @param entities 实体数组
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public withEntities(...entities: Entity[]): SceneBuilder {
|
||||
for (const entity of entities) {
|
||||
this.scene.addEntity(entity);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统
|
||||
* @param system 系统实例
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public withSystem(system: any): SceneBuilder {
|
||||
this.scene.addSystem(system);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加系统
|
||||
* @param systems 系统数组
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public withSystems(...systems: any[]): SceneBuilder {
|
||||
for (const system of systems) {
|
||||
this.scene.addSystem(system);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建并返回场景
|
||||
* @returns 构建的场景
|
||||
*/
|
||||
public build(): Scene {
|
||||
return this.scene;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件构建器 - 提供流式API创建组件
|
||||
*/
|
||||
export class ComponentBuilder<T extends Component> {
|
||||
private component: T;
|
||||
|
||||
constructor(componentClass: new (...args: any[]) => T, ...args: any[]) {
|
||||
this.component = new componentClass(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置组件属性
|
||||
* @param property 属性名
|
||||
* @param value 属性值
|
||||
* @returns 组件构建器
|
||||
*/
|
||||
public set<K extends keyof T>(property: K, value: T[K]): ComponentBuilder<T> {
|
||||
this.component[property] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用配置函数设置组件
|
||||
* @param configurator 配置函数
|
||||
* @returns 组件构建器
|
||||
*/
|
||||
public configure(configurator: (component: T) => void): ComponentBuilder<T> {
|
||||
configurator(this.component);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件性设置属性
|
||||
* @param condition 条件
|
||||
* @param property 属性名
|
||||
* @param value 属性值
|
||||
* @returns 组件构建器
|
||||
*/
|
||||
public setIf<K extends keyof T>(condition: boolean, property: K, value: T[K]): ComponentBuilder<T> {
|
||||
if (condition) {
|
||||
this.component[property] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建并返回组件
|
||||
* @returns 构建的组件
|
||||
*/
|
||||
public build(): T {
|
||||
return this.component;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ECS流式API主入口
|
||||
* 提供统一的流式接口
|
||||
*/
|
||||
export class ECSFluentAPI {
|
||||
private scene: Scene;
|
||||
private querySystem: QuerySystem;
|
||||
private eventSystem: TypeSafeEventSystem;
|
||||
|
||||
constructor(scene: Scene, querySystem: QuerySystem, eventSystem: TypeSafeEventSystem) {
|
||||
this.scene = scene;
|
||||
this.querySystem = querySystem;
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建实体构建器
|
||||
* @returns 实体构建器
|
||||
*/
|
||||
public createEntity(): EntityBuilder {
|
||||
return new EntityBuilder(this.scene, this.scene.componentStorageManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建场景构建器
|
||||
* @returns 场景构建器
|
||||
*/
|
||||
public createScene(): SceneBuilder {
|
||||
return new SceneBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建组件构建器
|
||||
* @param componentClass 组件类
|
||||
* @param args 构造参数
|
||||
* @returns 组件构建器
|
||||
*/
|
||||
public createComponent<T extends Component>(
|
||||
componentClass: new (...args: any[]) => T,
|
||||
...args: any[]
|
||||
): ComponentBuilder<T> {
|
||||
return new ComponentBuilder(componentClass, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建查询构建器
|
||||
* @returns 查询构建器
|
||||
*/
|
||||
public query(): QueryBuilder {
|
||||
return this.querySystem.createQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找实体(简化版)
|
||||
* @param componentTypes 组件类型
|
||||
* @returns 实体数组
|
||||
*/
|
||||
public find(...componentTypes: ComponentType[]): Entity[] {
|
||||
return this.querySystem.queryAll(...componentTypes).entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找第一个匹配的实体
|
||||
* @param componentTypes 组件类型
|
||||
* @returns 实体或null
|
||||
*/
|
||||
public findFirst(...componentTypes: ComponentType[]): Entity | null {
|
||||
const result = this.querySystem.queryAll(...componentTypes);
|
||||
return result.entities.length > 0 ? result.entities[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按名称查找实体
|
||||
* @param name 实体名称
|
||||
* @returns 实体或null
|
||||
*/
|
||||
public findByName(name: string): Entity | null {
|
||||
return this.scene.getEntityByName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按标签查找实体
|
||||
* @param tag 标签
|
||||
* @returns 实体数组
|
||||
*/
|
||||
public findByTag(tag: number): Entity[] {
|
||||
return this.scene.getEntitiesByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param eventType 事件类型
|
||||
* @param event 事件数据
|
||||
*/
|
||||
public emit<T>(eventType: string, event: T): void {
|
||||
this.eventSystem.emitSync(eventType, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步触发事件
|
||||
* @param eventType 事件类型
|
||||
* @param event 事件数据
|
||||
*/
|
||||
public async emitAsync<T>(eventType: string, event: T): Promise<void> {
|
||||
await this.eventSystem.emit(eventType, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件处理器
|
||||
* @returns 监听器ID
|
||||
*/
|
||||
public on<T>(eventType: string, handler: (event: T) => void): string {
|
||||
return this.eventSystem.on(eventType, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性监听事件
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件处理器
|
||||
* @returns 监听器ID
|
||||
*/
|
||||
public once<T>(eventType: string, handler: (event: T) => void): string {
|
||||
return this.eventSystem.once(eventType, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param eventType 事件类型
|
||||
* @param listenerId 监听器ID
|
||||
*/
|
||||
public off(eventType: string, listenerId: string): void {
|
||||
this.eventSystem.off(eventType, listenerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作实体
|
||||
* @param entities 实体数组
|
||||
* @returns 批量操作器
|
||||
*/
|
||||
public batch(entities: Entity[]): EntityBatchOperator {
|
||||
return new EntityBatchOperator(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景统计信息
|
||||
* @returns 统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
entityCount: number;
|
||||
systemCount: number;
|
||||
componentStats: Map<string, any>;
|
||||
queryStats: any;
|
||||
eventStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
entityCount: this.scene.entities.count,
|
||||
systemCount: this.scene.systems.length,
|
||||
componentStats: this.scene.componentStorageManager.getAllStats(),
|
||||
queryStats: this.querySystem.getStats(),
|
||||
eventStats: this.eventSystem.getStats() as Map<string, any>
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体批量操作器
|
||||
* 提供对多个实体的批量操作
|
||||
*/
|
||||
export class EntityBatchOperator {
|
||||
private entities: Entity[];
|
||||
|
||||
constructor(entities: Entity[]) {
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加组件
|
||||
* @param component 组件实例
|
||||
* @returns 批量操作器
|
||||
*/
|
||||
public addComponent<T extends Component>(component: T): EntityBatchOperator {
|
||||
for (const entity of this.entities) {
|
||||
entity.addComponent(component);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移除组件
|
||||
* @param componentType 组件类型
|
||||
* @returns 批量操作器
|
||||
*/
|
||||
public removeComponent<T extends Component>(componentType: ComponentType<T>): EntityBatchOperator {
|
||||
for (const entity of this.entities) {
|
||||
entity.removeComponentByType(componentType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置活跃状态
|
||||
* @param active 是否活跃
|
||||
* @returns 批量操作器
|
||||
*/
|
||||
public setActive(active: boolean): EntityBatchOperator {
|
||||
for (const entity of this.entities) {
|
||||
entity.active = active;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置标签
|
||||
* @param tag 标签
|
||||
* @returns 批量操作器
|
||||
*/
|
||||
public setTag(tag: number): EntityBatchOperator {
|
||||
for (const entity of this.entities) {
|
||||
entity.tag = tag;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量执行操作
|
||||
* @param operation 操作函数
|
||||
* @returns 批量操作器
|
||||
*/
|
||||
public forEach(operation: (entity: Entity, index: number) => void): EntityBatchOperator {
|
||||
this.entities.forEach(operation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤实体
|
||||
* @param predicate 过滤条件
|
||||
* @returns 新的批量操作器
|
||||
*/
|
||||
public filter(predicate: (entity: Entity) => boolean): EntityBatchOperator {
|
||||
return new EntityBatchOperator(this.entities.filter(predicate));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体数组
|
||||
* @returns 实体数组
|
||||
*/
|
||||
public toArray(): Entity[] {
|
||||
return this.entities.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体数量
|
||||
* @returns 实体数量
|
||||
*/
|
||||
public count(): number {
|
||||
return this.entities.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ECS流式API实例
|
||||
* @param scene 场景
|
||||
* @param querySystem 查询系统
|
||||
* @param eventSystem 事件系统
|
||||
* @returns ECS流式API实例
|
||||
*/
|
||||
export function createECSAPI(
|
||||
scene: Scene,
|
||||
querySystem: QuerySystem,
|
||||
eventSystem: TypeSafeEventSystem
|
||||
): ECSFluentAPI {
|
||||
return new ECSFluentAPI(scene, querySystem, eventSystem);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局ECS流式API实例(需要在使用前初始化)
|
||||
*/
|
||||
export let ECS: ECSFluentAPI;
|
||||
|
||||
/**
|
||||
* 初始化全局ECS API
|
||||
* @param scene 场景
|
||||
* @param querySystem 查询系统
|
||||
* @param eventSystem 事件系统
|
||||
*/
|
||||
export function initializeECS(
|
||||
scene: Scene,
|
||||
querySystem: QuerySystem,
|
||||
eventSystem: TypeSafeEventSystem
|
||||
): void {
|
||||
ECS = createECSAPI(scene, querySystem, eventSystem);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,18 @@
|
||||
module es {
|
||||
export enum CoreEvents {
|
||||
/**
|
||||
* 当场景发生变化时触发
|
||||
*/
|
||||
sceneChanged,
|
||||
/**
|
||||
* 每帧更新事件
|
||||
*/
|
||||
frameUpdated,
|
||||
/**
|
||||
* 当渲染发生时触发
|
||||
*/
|
||||
renderChanged,
|
||||
}
|
||||
/**
|
||||
* 核心事件枚举
|
||||
* 定义框架中的核心事件类型
|
||||
*/
|
||||
export enum CoreEvents {
|
||||
/**
|
||||
* 当场景发生变化时触发
|
||||
*/
|
||||
sceneChanged,
|
||||
/**
|
||||
* 每帧更新事件
|
||||
*/
|
||||
frameUpdated,
|
||||
/**
|
||||
* 当渲染发生时触发
|
||||
*/
|
||||
renderChanged,
|
||||
}
|
||||
|
||||
+1316
-524
File diff suppressed because it is too large
Load Diff
+331
-251
@@ -1,285 +1,365 @@
|
||||
///<reference path="../Math/Vector2.ts" />
|
||||
module es {
|
||||
/** 场景 */
|
||||
export class Scene {
|
||||
/** 这个场景中的实体列表 */
|
||||
public readonly entities: EntityList;
|
||||
/** 管理所有实体处理器 */
|
||||
public readonly entityProcessors: EntityProcessorList;
|
||||
import { Entity } from './Entity';
|
||||
import { EntityList } from './Utils/EntityList';
|
||||
import { EntityProcessorList } from './Utils/EntityProcessorList';
|
||||
import { IdentifierPool } from './Utils/IdentifierPool';
|
||||
import { EntitySystem } from './Systems/EntitySystem';
|
||||
import { ComponentStorageManager } from './Core/ComponentStorage';
|
||||
import { QuerySystem } from './Core/QuerySystem';
|
||||
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
|
||||
|
||||
public readonly _sceneComponents: SceneComponent[] = [];
|
||||
public readonly identifierPool: IdentifierPool;
|
||||
private _didSceneBegin: boolean;
|
||||
/**
|
||||
* 游戏场景类
|
||||
*
|
||||
* 管理游戏场景中的所有实体和系统,提供场景生命周期管理。
|
||||
* 场景是游戏世界的容器,负责协调实体和系统的运行。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class GameScene extends Scene {
|
||||
* public initialize(): void {
|
||||
* // 创建游戏实体
|
||||
* const player = this.createEntity("Player");
|
||||
*
|
||||
* // 添加系统
|
||||
* this.addEntityProcessor(new MovementSystem());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class Scene {
|
||||
/**
|
||||
* 场景名称
|
||||
*
|
||||
* 用于标识和调试的友好名称。
|
||||
*/
|
||||
public name: string = "";
|
||||
|
||||
constructor() {
|
||||
this.entities = new EntityList(this);
|
||||
/**
|
||||
* 场景中的实体集合
|
||||
*
|
||||
* 管理场景内所有实体的生命周期。
|
||||
*/
|
||||
public readonly entities: EntityList;
|
||||
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
this.identifierPool = new IdentifierPool();
|
||||
/**
|
||||
* 实体系统处理器集合
|
||||
*
|
||||
* 管理场景内所有实体系统的执行。
|
||||
*/
|
||||
public readonly entityProcessors: EntityProcessorList;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
/**
|
||||
* 实体ID池
|
||||
*
|
||||
* 用于分配和回收实体的唯一标识符。
|
||||
*/
|
||||
public readonly identifierPool: IdentifierPool;
|
||||
|
||||
/**
|
||||
* 初始化场景,可以在派生类中覆盖
|
||||
*
|
||||
* 这个方法会在场景创建时被调用。您可以在这个方法中添加实体和组件,
|
||||
* 或者执行一些必要的准备工作,以便场景能够开始运行。
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
/**
|
||||
* 组件存储管理器
|
||||
*
|
||||
* 高性能的组件存储和查询系统。
|
||||
*/
|
||||
public readonly componentStorageManager: ComponentStorageManager;
|
||||
|
||||
/**
|
||||
* 开始运行场景时调用此方法,可以在派生类中覆盖
|
||||
*
|
||||
* 这个方法会在场景开始运行时被调用。您可以在这个方法中执行场景开始时需要进行的操作。
|
||||
* 比如,您可以开始播放一段背景音乐、启动UI等等。
|
||||
*/
|
||||
public onStart() {
|
||||
}
|
||||
/**
|
||||
* 查询系统
|
||||
*
|
||||
* 基于位掩码的高性能实体查询系统。
|
||||
*/
|
||||
public readonly querySystem: QuerySystem;
|
||||
|
||||
/**
|
||||
* 卸载场景时调用此方法,可以在派生类中覆盖
|
||||
*
|
||||
* 这个方法会在场景被销毁时被调用。您可以在这个方法中销毁实体和组件、释放资源等等。
|
||||
* 您也可以在这个方法中执行一些必要的清理工作,以确保场景被完全卸载。
|
||||
*/
|
||||
public unload() {
|
||||
}
|
||||
/**
|
||||
* 事件系统
|
||||
*
|
||||
* 类型安全的事件系统。
|
||||
*/
|
||||
public readonly eventSystem: TypeSafeEventSystem;
|
||||
|
||||
/**
|
||||
* 开始场景,初始化物理系统、启动实体处理器等
|
||||
*
|
||||
* 这个方法会启动场景。它将重置物理系统、启动实体处理器等,并调用onStart方法。
|
||||
*/
|
||||
public begin() {
|
||||
// 重置物理系统
|
||||
Physics.reset();
|
||||
/**
|
||||
* 场景是否已开始运行
|
||||
*/
|
||||
private _didSceneBegin: boolean = false;
|
||||
|
||||
// 启动实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.begin();
|
||||
/**
|
||||
* 获取系统列表(兼容性属性)
|
||||
*/
|
||||
public get systems(): EntitySystem[] {
|
||||
return this.entityProcessors.processors;
|
||||
}
|
||||
|
||||
// 标记场景已开始运行并调用onStart方法
|
||||
this._didSceneBegin = true;
|
||||
this.onStart();
|
||||
}
|
||||
/**
|
||||
* 创建场景实例
|
||||
*/
|
||||
constructor() {
|
||||
this.entities = new EntityList(this);
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
this.identifierPool = new IdentifierPool();
|
||||
this.componentStorageManager = new ComponentStorageManager();
|
||||
this.querySystem = new QuerySystem();
|
||||
this.eventSystem = new TypeSafeEventSystem();
|
||||
|
||||
/**
|
||||
* 结束场景,清除实体、场景组件、物理系统等
|
||||
*
|
||||
* 这个方法会结束场景。它将移除所有实体并调用它们的onRemovedFromScene方法,清除物理系统,结束实体处理器等,并调用unload方法。
|
||||
*/
|
||||
public end() {
|
||||
// 标记场景已结束运行
|
||||
this._didSceneBegin = false;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
// 移除所有实体并调用它们的onRemovedFromScene方法
|
||||
this.entities.removeAllEntities();
|
||||
/**
|
||||
* 初始化场景
|
||||
*
|
||||
* 在场景创建时调用,子类可以重写此方法来设置初始实体和组件。
|
||||
*/
|
||||
public initialize(): void {
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._sceneComponents.length; i++) {
|
||||
this._sceneComponents[i].onRemovedFromScene();
|
||||
}
|
||||
this._sceneComponents.length = 0;
|
||||
/**
|
||||
* 场景开始运行时的回调
|
||||
*
|
||||
* 在场景开始运行时调用,可以在此方法中执行场景启动逻辑。
|
||||
*/
|
||||
public onStart(): void {
|
||||
}
|
||||
|
||||
// 清除物理系统
|
||||
Physics.clear();
|
||||
/**
|
||||
* 场景卸载时的回调
|
||||
*
|
||||
* 在场景被销毁时调用,可以在此方法中执行清理工作。
|
||||
*/
|
||||
public unload(): void {
|
||||
}
|
||||
|
||||
// 结束实体处理器
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.end();
|
||||
/**
|
||||
* 开始场景,启动实体处理器等
|
||||
*
|
||||
* 这个方法会启动场景。它将启动实体处理器等,并调用onStart方法。
|
||||
*/
|
||||
public begin() {
|
||||
// 启动实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.begin();
|
||||
|
||||
// 调用卸载方法
|
||||
this.unload();
|
||||
}
|
||||
// 标记场景已开始运行并调用onStart方法
|
||||
this._didSceneBegin = true;
|
||||
this.onStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新场景,更新实体组件、实体处理器等
|
||||
*/
|
||||
public update() {
|
||||
// 更新实体列表
|
||||
this.entities.updateLists();
|
||||
/**
|
||||
* 结束场景,清除实体、实体处理器等
|
||||
*
|
||||
* 这个方法会结束场景。它将移除所有实体,结束实体处理器等,并调用unload方法。
|
||||
*/
|
||||
public end() {
|
||||
// 标记场景已结束运行
|
||||
this._didSceneBegin = false;
|
||||
|
||||
// 更新场景组件
|
||||
for (let i = this._sceneComponents.length - 1; i >= 0; i--) {
|
||||
if (this._sceneComponents[i].enabled)
|
||||
this._sceneComponents[i].update();
|
||||
}
|
||||
// 移除所有实体
|
||||
this.entities.removeAllEntities();
|
||||
|
||||
// 更新实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.update();
|
||||
// 清空组件存储
|
||||
this.componentStorageManager.clear();
|
||||
|
||||
// 更新实体组
|
||||
this.entities.update();
|
||||
// 结束实体处理器
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.end();
|
||||
|
||||
// 更新实体处理器的后处理方法
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.lateUpdate();
|
||||
}
|
||||
// 调用卸载方法
|
||||
this.unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向组件列表添加并返回SceneComponent
|
||||
* @param component
|
||||
*/
|
||||
public addSceneComponent<T extends SceneComponent>(component: T): T {
|
||||
component.scene = this;
|
||||
component.onEnabled();
|
||||
this._sceneComponents.push(component);
|
||||
this._sceneComponents.sort(component.compare);
|
||||
return component;
|
||||
}
|
||||
/**
|
||||
* 更新场景,更新实体组件、实体处理器等
|
||||
*/
|
||||
public update() {
|
||||
// 更新实体列表
|
||||
this.entities.updateLists();
|
||||
|
||||
/**
|
||||
* 获取类型为T的第一个SceneComponent并返回它。如果没有找到组件,则返回null。
|
||||
* @param type
|
||||
*/
|
||||
public getSceneComponent<T extends SceneComponent>(type) {
|
||||
for (let i = 0; i < this._sceneComponents.length; i++) {
|
||||
let component = this._sceneComponents[i];
|
||||
if (component instanceof type)
|
||||
return component as T;
|
||||
}
|
||||
// 更新实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.update();
|
||||
|
||||
return null;
|
||||
}
|
||||
// 更新实体组
|
||||
this.entities.update();
|
||||
|
||||
/**
|
||||
* 获取类型为T的第一个SceneComponent并返回它。如果没有找到SceneComponent,则将创建SceneComponent。
|
||||
* @param type
|
||||
*/
|
||||
public getOrCreateSceneComponent<T extends SceneComponent>(type) {
|
||||
let comp = this.getSceneComponent<T>(type);
|
||||
if (comp == null)
|
||||
comp = this.addSceneComponent<T>(new type());
|
||||
// 更新实体处理器的后处理方法
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.lateUpdate();
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
/**
|
||||
* 将实体添加到此场景,并返回它
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public createEntity(name: string) {
|
||||
let entity = new Entity(name, this.identifierPool.checkOut());
|
||||
return this.addEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SceneComponents列表中删除一个SceneComponent
|
||||
* @param component
|
||||
*/
|
||||
public removeSceneComponent(component: SceneComponent) {
|
||||
const sceneComponentList = new es.List(this._sceneComponents);
|
||||
Insist.isTrue(sceneComponentList.contains(component), `SceneComponent${component}不在SceneComponents列表中!`);
|
||||
sceneComponentList.remove(component);
|
||||
component.onRemovedFromScene();
|
||||
}
|
||||
/**
|
||||
* 在场景的实体列表中添加一个实体
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public addEntity(entity: Entity) {
|
||||
this.entities.add(entity);
|
||||
entity.scene = this;
|
||||
|
||||
/**
|
||||
* 将实体添加到此场景,并返回它
|
||||
* @param name
|
||||
*/
|
||||
public createEntity(name: string) {
|
||||
let entity = new Entity(name, this.identifierPool.checkOut());
|
||||
return this.addEntity(entity);
|
||||
}
|
||||
// 将实体添加到查询系统
|
||||
this.querySystem.addEntity(entity);
|
||||
|
||||
/**
|
||||
* 在场景的实体列表中添加一个实体
|
||||
* @param entity
|
||||
*/
|
||||
public addEntity(entity: Entity) {
|
||||
Insist.isFalse(new es.List(this.entities.buffer).contains(entity), `您试图将同一实体添加到场景两次: ${entity}`);
|
||||
this.entities.add(entity);
|
||||
entity.scene = this;
|
||||
// 触发实体添加事件
|
||||
this.eventSystem.emitSync('entity:added', { entity, scene: this });
|
||||
|
||||
for (let i = 0; i < entity.transform.childCount; i++)
|
||||
this.addEntity(entity.transform.getChild(i).entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除所有实体
|
||||
*/
|
||||
public destroyAllEntities() {
|
||||
for (let i = 0; i < this.entities.count; i++) {
|
||||
this.entities.buffer[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索并返回第一个具有名称的实体
|
||||
* @param name
|
||||
*/
|
||||
public findEntity(name: string): Entity {
|
||||
return this.entities.findEntity(name);
|
||||
}
|
||||
|
||||
public findEntityById(id: number): Entity {
|
||||
return this.entities.findEntityById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回具有给定标记的所有实体
|
||||
* @param tag
|
||||
*/
|
||||
public findEntitiesWithTag(tag: number): Entity[] {
|
||||
return this.entities.entitiesWithTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回提一个具有该标记的实体
|
||||
* @param tag
|
||||
* @returns
|
||||
*/
|
||||
public findEntityWithTag(tag: number): Entity {
|
||||
return this.entities.entityWithTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个启用加载的类型为T的组件
|
||||
* @param type
|
||||
*/
|
||||
public findComponentOfType<T extends Component>(type: new (...args) => T): T {
|
||||
return this.entities.findComponentOfType<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回类型为T的所有已启用已加载组件的列表
|
||||
* @param type
|
||||
*/
|
||||
public findComponentsOfType<T extends Component>(type: new (...args) => T): T[] {
|
||||
return this.entities.findComponentsOfType<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回场景中包含特定组件的实体列表
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
public findEntitiesOfComponent(...types): Entity[] {
|
||||
return this.entities.findEntitiesOfComponent(...types);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem) {
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
|
||||
processor.setUpdateOrder(this.entityProcessors.count - 1);
|
||||
this.entityProcessors.clearDirty();
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除EntitySystem处理器
|
||||
* @param processor
|
||||
*/
|
||||
public removeEntityProcessor(processor: EntitySystem) {
|
||||
this.entityProcessors.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取EntitySystem处理器
|
||||
*/
|
||||
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T {
|
||||
return this.entityProcessors.getProcessor<T>(type);
|
||||
/**
|
||||
* 从场景中删除所有实体
|
||||
*/
|
||||
public destroyAllEntities() {
|
||||
for (let i = 0; i < this.entities.count; i++) {
|
||||
this.entities.buffer[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索并返回第一个具有名称的实体
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public findEntity(name: string): Entity | null {
|
||||
return this.entities.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找实体
|
||||
* @param id 实体ID
|
||||
*/
|
||||
public findEntityById(id: number): Entity | null {
|
||||
return this.entities.findEntityById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体
|
||||
* @param tag 实体标签
|
||||
*/
|
||||
public findEntitiesByTag(tag: number): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
for (const entity of this.entities.buffer) {
|
||||
if (entity.tag === tag) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找实体(别名方法)
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public getEntityByName(name: string): Entity | null {
|
||||
return this.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体(别名方法)
|
||||
* @param tag 实体标签
|
||||
*/
|
||||
public getEntitiesByTag(tag: number): Entity[] {
|
||||
return this.findEntitiesByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem) {
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
|
||||
processor.setUpdateOrder(this.entityProcessors.count - 1);
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统到场景(addEntityProcessor的别名)
|
||||
* @param system 系统
|
||||
*/
|
||||
public addSystem(system: EntitySystem) {
|
||||
return this.addEntityProcessor(system);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除EntitySystem处理器
|
||||
* @param processor 要删除的处理器
|
||||
*/
|
||||
public removeEntityProcessor(processor: EntitySystem) {
|
||||
this.entityProcessors.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的EntitySystem处理器
|
||||
* @param type 处理器类型
|
||||
*/
|
||||
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
|
||||
return this.entityProcessors.getProcessor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
entityCount: number;
|
||||
processorCount: number;
|
||||
componentStorageStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
entityCount: this.entities.count,
|
||||
processorCount: this.entityProcessors.count,
|
||||
componentStorageStats: this.componentStorageManager.getAllStats()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩组件存储(清理碎片)
|
||||
*/
|
||||
public compactComponentStorage(): void {
|
||||
this.componentStorageManager.compactAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景的调试信息
|
||||
*/
|
||||
public getDebugInfo(): {
|
||||
name: string;
|
||||
entityCount: number;
|
||||
processorCount: number;
|
||||
isRunning: boolean;
|
||||
entities: Array<{
|
||||
name: string;
|
||||
id: number;
|
||||
componentCount: number;
|
||||
componentTypes: string[];
|
||||
}>;
|
||||
processors: Array<{
|
||||
name: string;
|
||||
updateOrder: number;
|
||||
entityCount: number;
|
||||
}>;
|
||||
componentStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
name: this.constructor.name,
|
||||
entityCount: this.entities.count,
|
||||
processorCount: this.entityProcessors.count,
|
||||
isRunning: this._didSceneBegin,
|
||||
entities: this.entities.buffer.map(entity => ({
|
||||
name: entity.name,
|
||||
id: entity.id,
|
||||
componentCount: entity.components.length,
|
||||
componentTypes: entity.components.map(c => c.constructor.name)
|
||||
})),
|
||||
processors: this.entityProcessors.processors.map(processor => ({
|
||||
name: processor.constructor.name,
|
||||
updateOrder: processor.updateOrder,
|
||||
entityCount: (processor as any)._entities?.length || 0
|
||||
})),
|
||||
componentStats: this.componentStorageManager.getAllStats()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* SceneTransition用于从一个场景过渡到另一个场景,或在一个场景内进行效果转换。
|
||||
* 如果sceneLoadAction为null,框架将执行场景内过渡,而不是加载新的场景中间过渡。
|
||||
*/
|
||||
export abstract class SceneTransition {
|
||||
/** 该函数应返回新加载的场景 */
|
||||
protected sceneLoadAction: () => Scene;
|
||||
|
||||
/**
|
||||
* 在loadNextScene执行时调用
|
||||
* 这在进行场景间转换时非常有用,这样您就可以知道何时可以重新设置相机或重置任何实体
|
||||
*/
|
||||
public onScreenObscured: Function;
|
||||
|
||||
/**
|
||||
* 转换完成后调用,以便调用其他工作,例如启动另一个场景转换
|
||||
*/
|
||||
public onTransitionCompleted: Function;
|
||||
|
||||
/**
|
||||
* 指示此转换是否将加载新场景的标志
|
||||
*/
|
||||
public _loadsNewScene: boolean = false;
|
||||
|
||||
private _hasPreviousSceneRender: boolean = false;
|
||||
|
||||
public get hasPreviousSceneRender() {
|
||||
if (!this._hasPreviousSceneRender) {
|
||||
this._hasPreviousSceneRender = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将此用于两部分过渡。例如,褪色会先褪色为黑色,然后当_isNewSceneLoaded变为true时会褪色。
|
||||
* 对于场景内转换,应在中点将isNewSceneLoaded设置为true,就像加载了新场景一样
|
||||
*/
|
||||
public _isNewSceneLoaded: boolean = false;
|
||||
|
||||
protected constructor(sceneLoadAction: () => Scene) {
|
||||
this.sceneLoadAction = sceneLoadAction;
|
||||
this._loadsNewScene = sceneLoadAction != null;
|
||||
}
|
||||
|
||||
protected * LoadNextScene() {
|
||||
// 如果我们有渲染界面,可以在这让玩家知道屏幕是模糊的(正在加载)
|
||||
if (this.onScreenObscured != null)
|
||||
this.onScreenObscured();
|
||||
|
||||
// 如果我们不加载一个新场景,我们只需设置标志
|
||||
if (!this._loadsNewScene) {
|
||||
this._isNewSceneLoaded = true;
|
||||
yield "break";
|
||||
}
|
||||
|
||||
Core.scene = this.sceneLoadAction();
|
||||
this._isNewSceneLoaded = true;
|
||||
|
||||
while (!this._isNewSceneLoaded)
|
||||
yield null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在前一个场景出现第一次(也是唯一一次)后调用。
|
||||
* 此时,可以在生成一帧后加载新场景(因此第一次渲染调用发生在场景加载之前)
|
||||
*/
|
||||
public * onBeginTransition(): any {
|
||||
yield null;
|
||||
yield Core.startCoroutine(this.LoadNextScene());
|
||||
|
||||
this.transitionComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在渲染场景之前调用
|
||||
*/
|
||||
public preRender() { }
|
||||
|
||||
/**
|
||||
* 在这里进行所有渲染
|
||||
*/
|
||||
public render() { }
|
||||
|
||||
/**
|
||||
* 当过渡完成且新场景已设置时,将调用此函数
|
||||
*/
|
||||
protected transitionComplete() {
|
||||
Core.Instance._sceneTransition = null;
|
||||
|
||||
if (this.onTransitionCompleted != null)
|
||||
this.onTransitionCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
///<reference path="./EntitySystem.ts"/>
|
||||
module es {
|
||||
/**
|
||||
* 这个类是一个实体系统的基类,其可以被子类继承并在子类中实现具体的实体处理逻辑。
|
||||
* 该类提供了实体的添加、删除、更新等基本操作,并支持设置系统的更新时序、检查系统是否需要处理实体、获取系统的场景等方法
|
||||
*/
|
||||
export abstract class DelayedIteratingSystem extends EntitySystem {
|
||||
private delay = 0;
|
||||
private running = false;
|
||||
private acc = 0;
|
||||
|
||||
constructor(matcher: Matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
const processed = entities.length;
|
||||
|
||||
if (processed === 0) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
this.delay = Number.MAX_VALUE;
|
||||
|
||||
for (let i = 0; i < processed; i++) {
|
||||
const entity = entities[i];
|
||||
this.processDelta(entity, this.acc);
|
||||
const remaining = this.getRemainingDelay(entity);
|
||||
|
||||
if (remaining <= 0) {
|
||||
this.processExpired(entity);
|
||||
} else {
|
||||
this.offerDelay(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
this.acc = 0;
|
||||
}
|
||||
|
||||
protected checkProcessing() {
|
||||
if (this.running) {
|
||||
this.acc += Time.deltaTime;
|
||||
return this.acc >= this.delay;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只有当提供的延迟比系统当前计划执行的时间短时,才会重新启动系统。
|
||||
* 如果系统已经停止(不运行),那么提供的延迟将被用来重新启动系统,无论其值如何
|
||||
* 如果系统已经在倒计时,并且提供的延迟大于剩余时间,系统将忽略它。
|
||||
* 如果提供的延迟时间短于剩余时间,系统将重新启动,以提供的延迟时间运行。
|
||||
* @param offeredDelay 提供的延迟时间,单位为秒
|
||||
*/
|
||||
public offerDelay(offeredDelay: number) {
|
||||
if (!this.running) {
|
||||
this.running = true;
|
||||
this.delay = offeredDelay;
|
||||
} else {
|
||||
this.delay = Math.min(this.delay, offeredDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统被命令处理实体后的初始延迟
|
||||
*/
|
||||
public getInitialTimeDelay() {
|
||||
return this.delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统计划运行前的时间
|
||||
* 如果系统没有运行,则返回零
|
||||
*/
|
||||
public getRemainingTimeUntilProcessing(): number {
|
||||
if (this.running) {
|
||||
return this.delay - this.acc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查系统是否正在倒计时处理
|
||||
*/
|
||||
public isRunning(): boolean {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止系统运行,中止当前倒计时
|
||||
*/
|
||||
public stop() {
|
||||
this.running = false;
|
||||
this.acc = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理给定实体的延迟时间的一部分,抽象出累积的 Delta 值。
|
||||
* @param entity 要处理的实体
|
||||
* @param accumulatedDelta 本系统最后一次执行后的累积 delta 时间
|
||||
*/
|
||||
protected abstract processDelta(entity: Entity, accumulatedDelta: number);
|
||||
|
||||
/**
|
||||
* 处理已到期的实体。
|
||||
* @param entity 要处理的实体
|
||||
*/
|
||||
protected abstract processExpired(entity: Entity);
|
||||
|
||||
/**
|
||||
* 获取给定实体剩余的延迟时间。
|
||||
* @param entity 要检查的实体
|
||||
* @returns 剩余的延迟时间(以秒为单位)
|
||||
*/
|
||||
protected abstract getRemainingDelay(entity: Entity): number;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
///<reference path="./EntitySystem.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 定义一个处理实体的抽象类,继承自 EntitySystem 类。
|
||||
* 子类需要实现 processEntity 方法,用于实现具体的实体处理逻辑。
|
||||
*/
|
||||
export abstract class EntityProcessingSystem extends EntitySystem {
|
||||
/**
|
||||
* 是否启用系统,默认为启用。
|
||||
*/
|
||||
public enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化实体匹配器。
|
||||
* @param matcher 实体匹配器
|
||||
*/
|
||||
constructor(matcher: Matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个实体,由子类实现。
|
||||
* @param entity 待处理的实体
|
||||
*/
|
||||
public abstract processEntity(entity: Entity): void;
|
||||
|
||||
/**
|
||||
* 在晚于 update 的时间更新实体,由子类实现。
|
||||
* @param entity 待处理的实体
|
||||
*/
|
||||
public lateProcessEntity(entity: Entity): void {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历系统的所有实体,逐个进行实体处理。
|
||||
* @param entities 实体数组
|
||||
*/
|
||||
protected process(entities: Entity[]) {
|
||||
// 如果实体数组为空,则直接返回
|
||||
if (entities.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历实体数组,逐个进行实体处理
|
||||
for (let i = 0, len = entities.length; i < len; i++) {
|
||||
const entity = entities[i];
|
||||
this.processEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在晚于 update 的时间更新实体。
|
||||
* @param entities 实体数组
|
||||
*/
|
||||
protected lateProcess(entities: Entity[]) {
|
||||
// 如果实体数组为空,则直接返回
|
||||
if (entities.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历实体数组,逐个进行实体处理
|
||||
for (let i = 0, len = entities.length; i < len; i++) {
|
||||
const entity = entities[i];
|
||||
this.lateProcessEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断系统是否需要进行实体处理。
|
||||
* 如果启用了系统,则需要进行实体处理,返回 true;
|
||||
* 否则不需要进行实体处理,返回 false。
|
||||
*/
|
||||
protected checkProcessing(): boolean {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +1,318 @@
|
||||
///<reference path="../../Utils/Collections/HashMap.ts"/>
|
||||
module es {
|
||||
import { Entity } from '../Entity';
|
||||
import { Core } from '../../Core';
|
||||
import { Matcher } from '../Utils/Matcher';
|
||||
import { PerformanceMonitor } from '../../Utils/PerformanceMonitor';
|
||||
import type { Scene } from '../Scene';
|
||||
|
||||
/**
|
||||
* 实体系统的基类
|
||||
*
|
||||
* 用于处理一组符合特定条件的实体。系统是ECS架构中的逻辑处理单元,
|
||||
* 负责对拥有特定组件组合的实体执行业务逻辑。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class MovementSystem extends EntitySystem {
|
||||
* constructor() {
|
||||
* super(Matcher.empty().all(Transform, Velocity));
|
||||
* }
|
||||
*
|
||||
* protected process(entities: Entity[]): void {
|
||||
* for (const entity of entities) {
|
||||
* const transform = entity.getComponent(Transform);
|
||||
* const velocity = entity.getComponent(Velocity);
|
||||
* transform.position.add(velocity.value);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class EntitySystem {
|
||||
private _entities: Entity[] = [];
|
||||
private _updateOrder: number = 0;
|
||||
private _enabled: boolean = true;
|
||||
private _performanceMonitor = PerformanceMonitor.instance;
|
||||
private _systemName: string;
|
||||
|
||||
/**
|
||||
* 实体系统的基类,用于处理一组实体。
|
||||
* 获取系统处理的实体列表
|
||||
*/
|
||||
export abstract class EntitySystem {
|
||||
private _entities: Entity[] = [];
|
||||
private _updateOrder: number = 0;
|
||||
private _startTime = 0;
|
||||
private _endTime = 0;
|
||||
private _useTime = 0;
|
||||
public get entities(): readonly Entity[] {
|
||||
return this._entities;
|
||||
}
|
||||
|
||||
/** 获取系统在当前帧所消耗的时间 仅在debug模式下生效 */
|
||||
public get useTime() {
|
||||
return this._useTime;
|
||||
}
|
||||
/**
|
||||
* 获取系统的更新时序
|
||||
*/
|
||||
public get updateOrder(): number {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的更新时序
|
||||
*/
|
||||
public get updateOrder() {
|
||||
return this._updateOrder;
|
||||
}
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
/**
|
||||
* 获取系统的启用状态
|
||||
*/
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
constructor(matcher?: Matcher) {
|
||||
this._matcher = matcher ? matcher : Matcher.empty();
|
||||
this.initialize();
|
||||
}
|
||||
/**
|
||||
* 设置系统的启用状态
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this._enabled = value;
|
||||
}
|
||||
|
||||
private _scene: Scene;
|
||||
/**
|
||||
* 获取系统名称
|
||||
*/
|
||||
public get systemName(): string {
|
||||
return this._systemName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个系统所属的场景
|
||||
*/
|
||||
public get scene() {
|
||||
return this._scene;
|
||||
}
|
||||
constructor(matcher?: Matcher) {
|
||||
this._matcher = matcher ? matcher : Matcher.empty();
|
||||
this._systemName = this.constructor.name;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public set scene(value: Scene) {
|
||||
this._scene = value;
|
||||
this._entities = [];
|
||||
}
|
||||
private _scene!: Scene;
|
||||
|
||||
private _matcher: Matcher;
|
||||
/**
|
||||
* 这个系统所属的场景
|
||||
*/
|
||||
public get scene(): Scene {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
public get matcher() {
|
||||
return this._matcher;
|
||||
}
|
||||
public set scene(value: Scene) {
|
||||
this._scene = value;
|
||||
this._entities = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更新时序
|
||||
* @param order 更新时序
|
||||
*/
|
||||
public setUpdateOrder(order: number) {
|
||||
this._updateOrder = order;
|
||||
this.scene.entityProcessors.setDirty();
|
||||
}
|
||||
private _matcher: Matcher;
|
||||
|
||||
public initialize() {
|
||||
/**
|
||||
* 获取实体匹配器
|
||||
*/
|
||||
public get matcher(): Matcher {
|
||||
return this._matcher;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 设置更新时序
|
||||
* @param order 更新时序
|
||||
*/
|
||||
public setUpdateOrder(order: number): void {
|
||||
this._updateOrder = order;
|
||||
this.scene.entityProcessors.setDirty();
|
||||
}
|
||||
|
||||
public onChanged(entity: Entity) {
|
||||
let contains = !!this._entities.find(e => e.id == entity.id);
|
||||
let interest = this._matcher.isInterestedEntity(entity);
|
||||
/**
|
||||
* 系统初始化
|
||||
*
|
||||
* 在系统创建时调用,子类可以重写此方法进行初始化操作。
|
||||
*/
|
||||
public initialize(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
if (interest && !contains)
|
||||
this.add(entity);
|
||||
else if (!interest && contains)
|
||||
this.remove(entity);
|
||||
}
|
||||
/**
|
||||
* 当实体的组件发生变化时调用
|
||||
*
|
||||
* 检查实体是否仍然符合系统的匹配条件,并相应地添加或移除实体。
|
||||
*
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public onChanged(entity: Entity): void {
|
||||
const contains = this._entities.includes(entity);
|
||||
const interest = this._matcher.isInterestedEntity(entity);
|
||||
|
||||
public add(entity: Entity) {
|
||||
if (!this._entities.find(e => e.id == entity.id))
|
||||
this._entities.push(entity);
|
||||
this.onAdded(entity);
|
||||
}
|
||||
|
||||
public onAdded(entity: Entity) {
|
||||
}
|
||||
|
||||
public remove(entity: Entity) {
|
||||
new es.List(this._entities).remove(entity);
|
||||
this.onRemoved(entity);
|
||||
}
|
||||
|
||||
public onRemoved(entity: Entity) {
|
||||
}
|
||||
|
||||
public update() {
|
||||
if (this.checkProcessing()) {
|
||||
this.begin();
|
||||
this.process(this._entities);
|
||||
}
|
||||
}
|
||||
|
||||
public lateUpdate() {
|
||||
if (this.checkProcessing()) {
|
||||
this.lateProcess(this._entities);
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在系统处理开始前调用
|
||||
* 在下一个系统开始处理或新的处理回合开始之前(以先到者为准),使用此方法创建的任何实体都不会激活
|
||||
*/
|
||||
protected begin() {
|
||||
if (!Core.Instance.debug)
|
||||
return;
|
||||
|
||||
this._startTime = Date.now();
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
}
|
||||
|
||||
protected lateProcess(entities: Entity[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统处理完毕后调用
|
||||
*/
|
||||
protected end() {
|
||||
if (!Core.Instance.debug)
|
||||
return;
|
||||
|
||||
this._endTime = Date.now();
|
||||
this._useTime = this._endTime - this._startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统是否需要处理
|
||||
*
|
||||
* 在启用系统时有用,但仅偶尔需要处理
|
||||
* 这只影响处理,不影响事件或订阅列表
|
||||
* @returns 如果系统应该处理,则为true,如果不处理则为false。
|
||||
*/
|
||||
protected checkProcessing() {
|
||||
return true;
|
||||
if (interest && !contains) {
|
||||
this.add(entity);
|
||||
} else if (!interest && contains) {
|
||||
this.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体到系统
|
||||
*
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public add(entity: Entity): void {
|
||||
if (!this._entities.includes(entity)) {
|
||||
this._entities.push(entity);
|
||||
this.onAdded(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体被添加到系统时调用
|
||||
*
|
||||
* 子类可以重写此方法来处理实体添加事件。
|
||||
*
|
||||
* @param entity 被添加的实体
|
||||
*/
|
||||
protected onAdded(entity: Entity): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 从系统中移除实体
|
||||
*
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
public remove(entity: Entity): void {
|
||||
const index = this._entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this._entities.splice(index, 1);
|
||||
this.onRemoved(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体从系统中移除时调用
|
||||
*
|
||||
* 子类可以重写此方法来处理实体移除事件。
|
||||
*
|
||||
* @param entity 被移除的实体
|
||||
*/
|
||||
protected onRemoved(entity: Entity): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统
|
||||
*
|
||||
* 在每帧调用,处理系统的主要逻辑。
|
||||
*/
|
||||
public update(): void {
|
||||
if (!this._enabled || !this.checkProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
|
||||
|
||||
try {
|
||||
this.begin();
|
||||
this.process(this._entities);
|
||||
} finally {
|
||||
this._performanceMonitor.endMonitoring(this._systemName, startTime, this._entities.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期更新系统
|
||||
*
|
||||
* 在所有系统的update方法执行完毕后调用。
|
||||
*/
|
||||
public lateUpdate(): void {
|
||||
if (!this._enabled || !this.checkProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`);
|
||||
|
||||
try {
|
||||
this.lateProcess(this._entities);
|
||||
this.end();
|
||||
} finally {
|
||||
this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, this._entities.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在系统处理开始前调用
|
||||
*
|
||||
* 子类可以重写此方法进行预处理操作。
|
||||
*/
|
||||
protected begin(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体列表
|
||||
*
|
||||
* 系统的核心逻辑,子类必须实现此方法来定义具体的处理逻辑。
|
||||
*
|
||||
* @param entities 要处理的实体列表
|
||||
*/
|
||||
protected process(entities: Entity[]): void {
|
||||
// 子类必须实现此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期处理实体列表
|
||||
*
|
||||
* 在主要处理逻辑之后执行,子类可以重写此方法。
|
||||
*
|
||||
* @param entities 要处理的实体列表
|
||||
*/
|
||||
protected lateProcess(entities: Entity[]): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统处理完毕后调用
|
||||
*
|
||||
* 子类可以重写此方法进行后处理操作。
|
||||
*/
|
||||
protected end(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查系统是否需要处理
|
||||
*
|
||||
* 在启用系统时有用,但仅偶尔需要处理。
|
||||
* 这只影响处理,不影响事件或订阅列表。
|
||||
*
|
||||
* @returns 如果系统应该处理,则为true,如果不处理则为false
|
||||
*/
|
||||
protected checkProcessing(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的性能数据
|
||||
*
|
||||
* @returns 性能数据或undefined
|
||||
*/
|
||||
public getPerformanceData() {
|
||||
return this._performanceMonitor.getSystemData(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的性能统计
|
||||
*
|
||||
* @returns 性能统计或undefined
|
||||
*/
|
||||
public getPerformanceStats() {
|
||||
return this._performanceMonitor.getSystemStats(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置系统的性能数据
|
||||
*/
|
||||
public resetPerformanceData(): void {
|
||||
this._performanceMonitor.resetSystem(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统信息的字符串表示
|
||||
*
|
||||
* @returns 系统信息字符串
|
||||
*/
|
||||
public toString(): string {
|
||||
const entityCount = this._entities.length;
|
||||
const perfData = this.getPerformanceData();
|
||||
const perfInfo = perfData ? ` (${perfData.executionTime.toFixed(2)}ms)` : '';
|
||||
|
||||
return `${this._systemName}[${entityCount} entities]${perfInfo}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
///<reference path="./IntervalSystem.ts"/>
|
||||
module es {
|
||||
/**
|
||||
* 定时遍历处理实体的系统,用于按指定的时间间隔遍历并处理感兴趣的实体。
|
||||
*/
|
||||
export abstract class IntervalIteratingSystem extends IntervalSystem {
|
||||
|
||||
constructor(matcher: Matcher, interval: number) {
|
||||
super(matcher, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理本系统感兴趣的实体
|
||||
* @param entity
|
||||
*/
|
||||
public abstract processEntity(entity: Entity);
|
||||
|
||||
/**
|
||||
* 遍历处理实体。
|
||||
* @param entities 本系统感兴趣的实体列表
|
||||
*/
|
||||
protected process(entities: Entity[]) {
|
||||
entities.forEach(entity => this.processEntity(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,58 @@
|
||||
module es {
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Matcher } from '../Utils/Matcher';
|
||||
import { Time } from '../../Utils/Time';
|
||||
|
||||
/**
|
||||
* 间隔系统抽象类
|
||||
* 定义一个按时间间隔处理的抽象类,继承自EntitySystem类
|
||||
* 子类需要实现process方法,用于实现具体的处理逻辑
|
||||
*/
|
||||
export abstract class IntervalSystem extends EntitySystem {
|
||||
/** 累积增量以跟踪间隔 */
|
||||
private acc: number = 0;
|
||||
/** 更新之间需要等待多长时间 */
|
||||
private readonly interval: number;
|
||||
/** 时间间隔的余数,用于计算下一次需要等待的时间 */
|
||||
private intervalRemainder: number = 0;
|
||||
|
||||
/**
|
||||
* 定义一个按时间间隔处理的抽象类,继承自 EntitySystem 类。
|
||||
* 子类需要实现 process 方法,用于实现具体的处理逻辑。
|
||||
* 构造函数,初始化时间间隔
|
||||
* @param matcher 实体匹配器
|
||||
* @param interval 时间间隔
|
||||
*/
|
||||
export abstract class IntervalSystem extends EntitySystem {
|
||||
/**
|
||||
* 累积增量以跟踪间隔
|
||||
*/
|
||||
private acc: number = 0;
|
||||
constructor(matcher: Matcher, interval: number) {
|
||||
super(matcher);
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新之间需要等待多长时间
|
||||
*/
|
||||
private readonly interval: number;
|
||||
/**
|
||||
* 判断是否需要进行处理
|
||||
* 如果需要进行处理,则更新累积增量和时间间隔余数,返回true
|
||||
* 否则返回false
|
||||
*/
|
||||
protected override checkProcessing(): boolean {
|
||||
// 更新累积增量
|
||||
this.acc += Time.deltaTime;
|
||||
|
||||
/**
|
||||
* 时间间隔的余数,用于计算下一次需要等待的时间
|
||||
*/
|
||||
private intervalRemainder: number = 0;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化时间间隔。
|
||||
* @param matcher 实体匹配器
|
||||
* @param interval 时间间隔
|
||||
*/
|
||||
constructor(matcher: Matcher, interval: number) {
|
||||
super(matcher);
|
||||
this.interval = interval;
|
||||
// 如果累积增量超过时间间隔,则进行处理
|
||||
if (this.acc >= this.interval) {
|
||||
// 更新时间间隔余数
|
||||
this.intervalRemainder = this.acc - this.interval;
|
||||
// 重置累积增量
|
||||
this.acc = 0;
|
||||
// 返回true,表示需要进行处理
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要进行处理。
|
||||
* 如果需要进行处理,则更新累积增量和时间间隔余数,返回 true;
|
||||
* 否则返回 false。
|
||||
*/
|
||||
protected checkProcessing(): boolean {
|
||||
// 更新累积增量
|
||||
this.acc += Time.deltaTime;
|
||||
// 返回false,表示不需要进行处理
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果累积增量超过时间间隔,则进行处理
|
||||
if (this.acc >= this.interval) {
|
||||
// 更新时间间隔余数
|
||||
this.intervalRemainder = this.acc - this.interval;
|
||||
// 重置累积增量
|
||||
this.acc = 0;
|
||||
// 返回 true,表示需要进行处理
|
||||
return true;
|
||||
}
|
||||
|
||||
// 返回 false,表示不需要进行处理
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本系统上次处理后的实际 delta 值。
|
||||
* 实际 delta 值等于时间间隔加上时间间隔余数。
|
||||
*/
|
||||
protected getIntervalDelta(): number {
|
||||
return this.interval + this.intervalRemainder;
|
||||
}
|
||||
/**
|
||||
* 获取本系统上次处理后的实际delta值
|
||||
* 实际delta值等于时间间隔加上时间间隔余数
|
||||
*/
|
||||
protected getIntervalDelta(): number {
|
||||
return this.interval + this.intervalRemainder;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
module es {
|
||||
/**
|
||||
* 定义一个被动的实体系统,继承自 EntitySystem 类。
|
||||
* 被动的实体系统不会对实体进行任何修改,只会被动地接收实体的变化事件。
|
||||
*/
|
||||
export abstract class PassiveSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作。
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public onChanged(entity: Entity) { }
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Entity } from '../Entity';
|
||||
|
||||
/**
|
||||
* 不进行任何处理,只进行开始和结束计时。
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected process(entities: Entity[]) {
|
||||
// 调用 begin 和 end 方法,开始和结束计时
|
||||
this.begin();
|
||||
this.end();
|
||||
}
|
||||
/**
|
||||
* 被动实体系统
|
||||
* 定义一个被动的实体系统,继承自EntitySystem类
|
||||
* 被动的实体系统不会对实体进行任何修改,只会被动地接收实体的变化事件
|
||||
*/
|
||||
export abstract class PassiveSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public override onChanged(entity: Entity): void { }
|
||||
|
||||
/**
|
||||
* 不进行任何处理
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 被动系统不进行任何处理
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
module es {
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Entity } from '../Entity';
|
||||
|
||||
/**
|
||||
* 处理系统抽象类
|
||||
* 定义一个处理实体的抽象类,继承自EntitySystem类
|
||||
* 子类需要实现processSystem方法,用于实现具体的处理逻辑
|
||||
*/
|
||||
export abstract class ProcessingSystem extends EntitySystem {
|
||||
/**
|
||||
* 定义一个处理实体的抽象类,继承自 EntitySystem 类。
|
||||
* 子类需要实现 processSystem 方法,用于实现具体的处理逻辑。
|
||||
* 当实体发生变化时,不进行任何操作
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
export abstract class ProcessingSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作。
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public onChanged(entity: Entity) { }
|
||||
public override onChanged(entity: Entity): void { }
|
||||
|
||||
/**
|
||||
* 处理实体,每帧调用 processSystem 方法进行处理。
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected process(entities: Entity[]) {
|
||||
// 调用 begin 和 end 方法,开始和结束计时
|
||||
this.begin();
|
||||
// 调用子类实现的 processSystem 方法进行实体处理
|
||||
this.processSystem();
|
||||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体的具体方法,由子类实现。
|
||||
*/
|
||||
public abstract processSystem(): void;
|
||||
/**
|
||||
* 处理实体,每帧调用processSystem方法进行处理
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 调用子类实现的processSystem方法进行实体处理
|
||||
this.processSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体的具体方法,由子类实现
|
||||
*/
|
||||
public abstract processSystem(): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// ECS系统导出
|
||||
export { EntitySystem } from './EntitySystem';
|
||||
export { ProcessingSystem } from './ProcessingSystem';
|
||||
export { PassiveSystem } from './PassiveSystem';
|
||||
export { IntervalSystem } from './IntervalSystem';
|
||||
+263
-478
@@ -1,500 +1,285 @@
|
||||
module es {
|
||||
export enum ComponentTransform {
|
||||
position,
|
||||
scale,
|
||||
rotation,
|
||||
import { Vector2 } from '../Math/Vector2';
|
||||
import { MathHelper } from '../Math/MathHelper';
|
||||
|
||||
/**
|
||||
* 变换组件类
|
||||
*
|
||||
* 管理游戏对象的空间变换信息,包括位置、旋转和缩放。
|
||||
* 支持父子层级关系,可以构建复杂的变换层次结构。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const transform = new Transform();
|
||||
* transform.setPosition(100, 200);
|
||||
* transform.setRotationDegrees(45);
|
||||
* transform.setScale(2, 2);
|
||||
*
|
||||
* // 设置父子关系
|
||||
* childTransform.setParent(transform);
|
||||
* ```
|
||||
*/
|
||||
export class Transform {
|
||||
/**
|
||||
* 本地位置坐标
|
||||
*
|
||||
* 相对于父变换的位置,如果没有父变换则为世界坐标。
|
||||
*/
|
||||
public position: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 本地旋转角度
|
||||
*
|
||||
* 以弧度为单位的旋转角度,相对于父变换的旋转。
|
||||
*/
|
||||
public rotation: number = 0;
|
||||
|
||||
/**
|
||||
* 本地缩放比例
|
||||
*
|
||||
* 相对于父变换的缩放比例。
|
||||
*/
|
||||
public scale: Vector2 = Vector2.one;
|
||||
|
||||
/**
|
||||
* 父变换引用
|
||||
*
|
||||
* 指向父级变换,如果为null则表示这是根变换。
|
||||
*/
|
||||
public parent: Transform | null = null;
|
||||
|
||||
/**
|
||||
* 子变换集合
|
||||
*
|
||||
* 存储所有子级变换的数组。
|
||||
*/
|
||||
private _children: Transform[] = [];
|
||||
|
||||
/**
|
||||
* 创建变换实例
|
||||
*
|
||||
* @param position - 初始位置,默认为零向量
|
||||
* @param rotation - 初始旋转角度(弧度),默认为0
|
||||
* @param scale - 初始缩放,默认为单位向量
|
||||
*/
|
||||
constructor(position?: Vector2, rotation: number = 0, scale?: Vector2) {
|
||||
if (position) this.position = position.clone();
|
||||
this.rotation = rotation;
|
||||
if (scale) this.scale = scale.clone();
|
||||
}
|
||||
|
||||
export enum DirtyType {
|
||||
clean = 0,
|
||||
positionDirty = 1,
|
||||
scaleDirty = 2,
|
||||
rotationDirty = 4,
|
||||
/**
|
||||
* 获取旋转角度(度数)
|
||||
*
|
||||
* @returns 以度数为单位的旋转角度
|
||||
*/
|
||||
public get rotationDegrees(): number {
|
||||
return MathHelper.toDegrees(this.rotation);
|
||||
}
|
||||
|
||||
export class Transform {
|
||||
/** 与此转换关联的实体 */
|
||||
public readonly entity: Entity;
|
||||
public hierarchyDirty: DirtyType;
|
||||
public _localDirty: boolean;
|
||||
public _localPositionDirty: boolean;
|
||||
public _localScaleDirty: boolean;
|
||||
public _localRotationDirty: boolean;
|
||||
public _positionDirty: boolean;
|
||||
public _worldToLocalDirty: boolean;
|
||||
public _worldInverseDirty: boolean;
|
||||
/**
|
||||
* 值会根据位置、旋转和比例自动重新计算
|
||||
*/
|
||||
public _localTransform: Matrix2D = Matrix2D.identity;
|
||||
/**
|
||||
* 值将自动从本地和父矩阵重新计算。
|
||||
*/
|
||||
public _worldTransform = Matrix2D.identity;
|
||||
public _rotationMatrix: Matrix2D = Matrix2D.identity;
|
||||
public _translationMatrix: Matrix2D = Matrix2D.identity;
|
||||
public _scaleMatrix: Matrix2D = Matrix2D.identity;
|
||||
public _children: Transform[] = [];
|
||||
/**
|
||||
* 设置旋转角度(度数)
|
||||
*
|
||||
* @param value - 以度数为单位的旋转角度
|
||||
*/
|
||||
public set rotationDegrees(value: number) {
|
||||
this.rotation = MathHelper.toRadians(value);
|
||||
}
|
||||
|
||||
constructor(entity: Entity) {
|
||||
this.entity = entity;
|
||||
this.scale = this._localScale = Vector2.one;
|
||||
/**
|
||||
* 获取世界坐标位置
|
||||
*
|
||||
* 计算并返回在世界坐标系中的绝对位置。
|
||||
*
|
||||
* @returns 世界坐标位置
|
||||
*/
|
||||
public get worldPosition(): Vector2 {
|
||||
if (!this.parent) {
|
||||
return this.position.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个转换的所有子元素
|
||||
*/
|
||||
public get childCount() {
|
||||
return this._children.length;
|
||||
// 计算世界位置
|
||||
const parentWorld = this.parent.worldPosition;
|
||||
const cos = Math.cos(this.parent.worldRotation);
|
||||
const sin = Math.sin(this.parent.worldRotation);
|
||||
const parentScale = this.parent.worldScale;
|
||||
|
||||
const scaledPos = Vector2.multiply(this.position, parentScale);
|
||||
const rotatedX = scaledPos.x * cos - scaledPos.y * sin;
|
||||
const rotatedY = scaledPos.x * sin + scaledPos.y * cos;
|
||||
|
||||
return new Vector2(parentWorld.x + rotatedX, parentWorld.y + rotatedY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取世界旋转角度
|
||||
*
|
||||
* 计算并返回在世界坐标系中的绝对旋转角度。
|
||||
*
|
||||
* @returns 世界旋转角度(弧度)
|
||||
*/
|
||||
public get worldRotation(): number {
|
||||
if (!this.parent) {
|
||||
return this.rotation;
|
||||
}
|
||||
return this.parent.worldRotation + this.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间的旋转度
|
||||
*/
|
||||
public get rotationDegrees(): number {
|
||||
return MathHelper.toDegrees(this._rotation);
|
||||
/**
|
||||
* 获取世界缩放比例
|
||||
*
|
||||
* 计算并返回在世界坐标系中的绝对缩放比例。
|
||||
*
|
||||
* @returns 世界缩放比例
|
||||
*/
|
||||
public get worldScale(): Vector2 {
|
||||
if (!this.parent) {
|
||||
return this.scale.clone();
|
||||
}
|
||||
return Vector2.multiply(this.parent.worldScale, this.scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间的旋转度
|
||||
* @param value
|
||||
*/
|
||||
public set rotationDegrees(value: number) {
|
||||
this.setRotation(MathHelper.toRadians(value));
|
||||
}
|
||||
/**
|
||||
* 获取子变换数量
|
||||
*
|
||||
* @returns 子变换的数量
|
||||
*/
|
||||
public get childCount(): number {
|
||||
return this._children.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转相对于父变换旋转的角度
|
||||
*/
|
||||
public get localRotationDegrees(): number {
|
||||
return MathHelper.toDegrees(this._localRotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转相对于父变换旋转的角度
|
||||
* @param value
|
||||
*/
|
||||
public set localRotationDegrees(value: number) {
|
||||
this.localRotation = MathHelper.toRadians(value);
|
||||
}
|
||||
|
||||
public get localToWorldTransform(): Matrix2D {
|
||||
this.updateTransform();
|
||||
return this._worldTransform;
|
||||
}
|
||||
|
||||
public _parent: Transform;
|
||||
|
||||
/**
|
||||
* 获取此转换的父转换
|
||||
*/
|
||||
public get parent() {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此转换的父转换
|
||||
* @param value
|
||||
*/
|
||||
public set parent(value: Transform) {
|
||||
this.setParent(value);
|
||||
}
|
||||
|
||||
public _worldToLocalTransform = Matrix2D.identity;
|
||||
|
||||
public get worldToLocalTransform(): Matrix2D {
|
||||
if (this._worldToLocalDirty) {
|
||||
if (this.parent == null) {
|
||||
this._worldToLocalTransform = Matrix2D.identity;
|
||||
} else {
|
||||
this.parent.updateTransform();
|
||||
this._worldToLocalTransform = Matrix2D.invert(this.parent._worldTransform);
|
||||
}
|
||||
|
||||
this._worldToLocalDirty = false;
|
||||
}
|
||||
|
||||
return this._worldToLocalTransform;
|
||||
}
|
||||
|
||||
public _worldInverseTransform = Matrix2D.identity;
|
||||
|
||||
public get worldInverseTransform(): Matrix2D {
|
||||
this.updateTransform();
|
||||
if (this._worldInverseDirty) {
|
||||
this._worldInverseTransform = Matrix2D.invert(this._worldTransform);
|
||||
this._worldInverseDirty = false;
|
||||
}
|
||||
|
||||
return this._worldInverseTransform;
|
||||
}
|
||||
|
||||
public _position: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 变换在世界空间中的位置
|
||||
*/
|
||||
public get position(): Vector2 {
|
||||
this.updateTransform();
|
||||
if (this._positionDirty) {
|
||||
if (this.parent == null) {
|
||||
this._position = this._localPosition;
|
||||
} else {
|
||||
this.parent.updateTransform();
|
||||
Vector2Ext.transformR(this._localPosition, this.parent._worldTransform, this._position);
|
||||
}
|
||||
|
||||
this._positionDirty = false;
|
||||
}
|
||||
|
||||
return this._position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间中的位置
|
||||
* @param value
|
||||
*/
|
||||
public set position(value: Vector2) {
|
||||
this.setPosition(value.x, value.y);
|
||||
}
|
||||
|
||||
public _scale: Vector2 = Vector2.one;
|
||||
|
||||
/**
|
||||
* 变换在世界空间的缩放
|
||||
*/
|
||||
public get scale(): Vector2 {
|
||||
this.updateTransform();
|
||||
return this._scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间的缩放
|
||||
* @param value
|
||||
*/
|
||||
public set scale(value: Vector2) {
|
||||
this.setScale(value);
|
||||
}
|
||||
|
||||
public _rotation: number = 0;
|
||||
|
||||
/**
|
||||
* 在世界空间中以弧度旋转的变换
|
||||
*/
|
||||
public get rotation(): number {
|
||||
this.updateTransform();
|
||||
return this._rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间的旋转度
|
||||
* @param value
|
||||
*/
|
||||
public set rotation(value: number) {
|
||||
this.setRotation(value);
|
||||
}
|
||||
|
||||
public _localPosition: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 转换相对于父转换的位置。如果转换没有父元素,则与transform.position相同
|
||||
*/
|
||||
public get localPosition(): Vector2 {
|
||||
this.updateTransform();
|
||||
return this._localPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换相对于父转换的位置。如果转换没有父元素,则与transform.position相同
|
||||
* @param value
|
||||
*/
|
||||
public set localPosition(value: Vector2) {
|
||||
this.setLocalPosition(value);
|
||||
}
|
||||
|
||||
public _localScale: Vector2 = Vector2.one;
|
||||
|
||||
/**
|
||||
* 转换相对于父元素的比例。如果转换没有父元素,则与transform.scale相同
|
||||
*/
|
||||
public get localScale(): Vector2 {
|
||||
this.updateTransform();
|
||||
return this._localScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换相对于父元素的比例。如果转换没有父元素,则与transform.scale相同
|
||||
* @param value
|
||||
*/
|
||||
public set localScale(value: Vector2) {
|
||||
this.setLocalScale(value);
|
||||
}
|
||||
|
||||
public _localRotation: number = 0;
|
||||
|
||||
/**
|
||||
* 相对于父变换的旋转,变换的旋转。如果转换没有父元素,则与transform.rotation相同
|
||||
*/
|
||||
public get localRotation(): number {
|
||||
this.updateTransform();
|
||||
return this._localRotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相对于父变换的旋转,变换的旋转。如果转换没有父元素,则与transform.rotation相同
|
||||
* @param value
|
||||
*/
|
||||
public set localRotation(value: number) {
|
||||
this.setLocalRotation(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回在索引处的转换子元素
|
||||
* @param index
|
||||
*/
|
||||
public getChild(index: number): Transform {
|
||||
/**
|
||||
* 获取指定索引的子变换
|
||||
*
|
||||
* @param index - 子变换的索引
|
||||
* @returns 子变换实例,如果索引无效则返回null
|
||||
*/
|
||||
public getChild(index: number): Transform | null {
|
||||
if (index >= 0 && index < this._children.length) {
|
||||
return this._children[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此转换的父转换
|
||||
* @param parent
|
||||
*/
|
||||
public setParent(parent: Transform): Transform {
|
||||
if (this._parent == parent)
|
||||
return this;
|
||||
/**
|
||||
* 设置父变换
|
||||
*
|
||||
* 建立或断开与父变换的层级关系。
|
||||
*
|
||||
* @param parent - 新的父变换,传入null表示断开父子关系
|
||||
*/
|
||||
public setParent(parent: Transform | null): void {
|
||||
if (this.parent === parent) return;
|
||||
|
||||
if (this._parent != null) {
|
||||
const index = this._parent._children.findIndex(t => t == this);
|
||||
if (index != -1)
|
||||
this._parent._children.splice(index, 1);
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
parent._children.push(this);
|
||||
}
|
||||
|
||||
this._parent = parent;
|
||||
this.setDirty(DirtyType.positionDirty);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置转换在世界空间中的位置
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
public setPosition(x: number, y: number): Transform {
|
||||
let position = new Vector2(x, y);
|
||||
if (position.equals(this._position))
|
||||
return this;
|
||||
|
||||
this._position = position;
|
||||
if (this.parent != null) {
|
||||
this.localPosition = Vector2.transform(this._position, this.worldToLocalTransform);
|
||||
} else {
|
||||
this.localPosition = position;
|
||||
}
|
||||
this._positionDirty = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置转换相对于父转换的位置。如果转换没有父元素,则与transform.position相同
|
||||
* @param localPosition
|
||||
*/
|
||||
public setLocalPosition(localPosition: Vector2): Transform {
|
||||
if (localPosition.equals(this._localPosition))
|
||||
return this;
|
||||
|
||||
this._localPosition = localPosition;
|
||||
this._localDirty = this._positionDirty = this._localPositionDirty = this._localRotationDirty = this._localScaleDirty = true;
|
||||
this.setDirty(DirtyType.positionDirty);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置变换在世界空间的旋转度
|
||||
* @param radians
|
||||
*/
|
||||
public setRotation(radians: number): Transform {
|
||||
this._rotation = radians;
|
||||
if (this.parent != null) {
|
||||
this.localRotation = this.parent.rotation + radians;
|
||||
} else {
|
||||
this.localRotation = radians;
|
||||
}
|
||||
this.setDirty(DirtyType.rotationDirty);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变换在世界空间的旋转度
|
||||
* @param degrees
|
||||
*/
|
||||
public setRotationDegrees(degrees: number): Transform {
|
||||
return this.setRotation(MathHelper.toRadians(degrees));
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转精灵的顶部,使其朝向位置
|
||||
* @param pos
|
||||
*/
|
||||
public lookAt(pos: Vector2) {
|
||||
const sign = this.position.x > pos.x ? -1 : 1;
|
||||
const vectorToAlignTo = this.position.sub(pos).normalize();
|
||||
this.rotation = sign * Math.acos(vectorToAlignTo.dot(Vector2.unitY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 相对于父变换的旋转设置变换的旋转。如果转换没有父元素,则与transform.rotation相同
|
||||
* @param radians
|
||||
*/
|
||||
public setLocalRotation(radians: number) {
|
||||
this._localRotation = radians;
|
||||
this._localDirty = this._positionDirty = this._localPositionDirty = this._localRotationDirty = this._localScaleDirty = true;
|
||||
this.setDirty(DirtyType.rotationDirty);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相对于父变换的旋转设置变换的旋转。如果转换没有父元素,则与transform.rotation相同
|
||||
* @param degrees
|
||||
*/
|
||||
public setLocalRotationDegrees(degrees: number): Transform {
|
||||
return this.setLocalRotation(MathHelper.toRadians(degrees));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变换在世界空间中的缩放
|
||||
* @param scale
|
||||
*/
|
||||
public setScale(scale: Vector2): Transform {
|
||||
this._scale = scale;
|
||||
if (this.parent != null) {
|
||||
this.localScale = Vector2.divide(scale, this.parent._scale);
|
||||
} else {
|
||||
this.localScale = scale;
|
||||
}
|
||||
this.setDirty(DirtyType.scaleDirty);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置转换相对于父对象的比例。如果转换没有父元素,则与transform.scale相同
|
||||
* @param scale
|
||||
*/
|
||||
public setLocalScale(scale: Vector2): Transform {
|
||||
this._localScale = scale;
|
||||
this._localDirty = this._positionDirty = this._localScaleDirty = true;
|
||||
this.setDirty(DirtyType.scaleDirty);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对精灵坐标进行四舍五入
|
||||
*/
|
||||
public roundPosition() {
|
||||
this.position = Vector2Ext.round(this._position);
|
||||
}
|
||||
|
||||
public updateTransform() {
|
||||
if (this.hierarchyDirty != DirtyType.clean) {
|
||||
if (this.parent != null)
|
||||
this.parent.updateTransform();
|
||||
|
||||
if (this._localDirty) {
|
||||
if (this._localPositionDirty) {
|
||||
Matrix2D.createTranslation(this._localPosition.x, this._localPosition.y, this._translationMatrix);
|
||||
this._localPositionDirty = false;
|
||||
}
|
||||
|
||||
if (this._localRotationDirty) {
|
||||
Matrix2D.createRotation(this._localRotation, this._rotationMatrix);
|
||||
this._localRotationDirty = false;
|
||||
}
|
||||
|
||||
if (this._localScaleDirty) {
|
||||
Matrix2D.createScale(this._localScale.x, this._localScale.y, this._scaleMatrix);
|
||||
this._localScaleDirty = false;
|
||||
}
|
||||
|
||||
Matrix2D.multiply(this._scaleMatrix, this._rotationMatrix, this._localTransform);
|
||||
Matrix2D.multiply(this._localTransform, this._translationMatrix, this._localTransform);
|
||||
|
||||
if (this.parent == null) {
|
||||
this._worldTransform = this._localTransform;
|
||||
this._rotation = this._localRotation;
|
||||
this._scale = this._localScale;
|
||||
this._worldInverseDirty = true;
|
||||
}
|
||||
|
||||
this._localDirty = false;
|
||||
}
|
||||
|
||||
if (this.parent != null) {
|
||||
Matrix2D.multiply(this._localTransform, this.parent._worldTransform, this._worldTransform);
|
||||
this._rotation = this._localRotation + this.parent._rotation;
|
||||
this._scale = this.parent._scale.multiply(this._localScale);;
|
||||
this._worldInverseDirty = true;
|
||||
}
|
||||
|
||||
this._worldToLocalDirty = true;
|
||||
this._positionDirty = true;
|
||||
this.hierarchyDirty = DirtyType.clean;
|
||||
// 从旧父级移除
|
||||
if (this.parent) {
|
||||
const index = this.parent._children.indexOf(this);
|
||||
if (index >= 0) {
|
||||
this.parent._children.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public setDirty(dirtyFlagType: DirtyType) {
|
||||
if ((this.hierarchyDirty & dirtyFlagType) == 0) {
|
||||
this.hierarchyDirty |= dirtyFlagType;
|
||||
|
||||
switch (dirtyFlagType) {
|
||||
case DirtyType.positionDirty:
|
||||
this.entity.onTransformChanged(ComponentTransform.position);
|
||||
break;
|
||||
case DirtyType.rotationDirty:
|
||||
this.entity.onTransformChanged(ComponentTransform.rotation);
|
||||
break;
|
||||
case DirtyType.scaleDirty:
|
||||
this.entity.onTransformChanged(ComponentTransform.scale);
|
||||
break;
|
||||
}
|
||||
|
||||
// 告诉子项发生了变换
|
||||
for (let i = 0; i < this._children.length; i++)
|
||||
this._children[i].setDirty(dirtyFlagType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从另一个transform属性进行拷贝
|
||||
* @param transform
|
||||
*/
|
||||
public copyFrom(transform: Transform) {
|
||||
this._position = transform.position.clone();
|
||||
this._localPosition = transform._localPosition.clone();
|
||||
this._rotation = transform._rotation;
|
||||
this._localRotation = transform._localRotation;
|
||||
this._scale = transform._scale;
|
||||
this._localScale = transform._localScale;
|
||||
|
||||
this.setDirty(DirtyType.positionDirty);
|
||||
this.setDirty(DirtyType.rotationDirty);
|
||||
this.setDirty(DirtyType.scaleDirty);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `[Transform: parent: ${this.parent}, position: ${this.position}, rotation: ${this.rotation},
|
||||
scale: ${this.scale}, localPosition: ${this._localPosition}, localRotation: ${this._localRotation},
|
||||
localScale: ${this._localScale}]`;
|
||||
// 设置新父级
|
||||
this.parent = parent;
|
||||
if (parent) {
|
||||
parent._children.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置本地位置
|
||||
*
|
||||
* @param x - X坐标
|
||||
* @param y - Y坐标
|
||||
*/
|
||||
public setPosition(x: number, y: number): void {
|
||||
this.position.set(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置本地旋转角度(弧度)
|
||||
*
|
||||
* @param radians - 旋转角度(弧度)
|
||||
*/
|
||||
public setRotation(radians: number): void {
|
||||
this.rotation = radians;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置本地旋转角度(度数)
|
||||
*
|
||||
* @param degrees - 旋转角度(度数)
|
||||
*/
|
||||
public setRotationDegrees(degrees: number): void {
|
||||
this.rotation = MathHelper.toRadians(degrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置本地缩放比例
|
||||
*
|
||||
* @param scale - 缩放向量
|
||||
*/
|
||||
public setScale(scale: Vector2): void;
|
||||
/**
|
||||
* 设置本地缩放比例
|
||||
*
|
||||
* @param x - X轴缩放比例
|
||||
* @param y - Y轴缩放比例
|
||||
*/
|
||||
public setScale(x: number, y: number): void;
|
||||
public setScale(scaleOrX: Vector2 | number, y?: number): void {
|
||||
if (typeof scaleOrX === 'number') {
|
||||
this.scale.set(scaleOrX, y!);
|
||||
} else {
|
||||
this.scale.copyFrom(scaleOrX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 朝向指定位置
|
||||
*
|
||||
* 调整旋转角度使变换朝向目标位置。
|
||||
*
|
||||
* @param target - 目标位置
|
||||
*/
|
||||
public lookAt(target: Vector2): void {
|
||||
const direction = target.subtract(this.worldPosition);
|
||||
this.rotation = Math.atan2(direction.y, direction.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平移变换
|
||||
*
|
||||
* 在当前位置基础上添加偏移量。
|
||||
*
|
||||
* @param offset - 位置偏移量
|
||||
*/
|
||||
public translate(offset: Vector2): void {
|
||||
this.position = this.position.add(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转
|
||||
* @param radians 旋转角度(弧度)
|
||||
*/
|
||||
public rotate(radians: number): void {
|
||||
this.rotation += radians;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制另一个变换的值
|
||||
* @param other 另一个变换
|
||||
*/
|
||||
public copyFrom(other: Transform): void {
|
||||
this.position.copyFrom(other.position);
|
||||
this.rotation = other.rotation;
|
||||
this.scale.copyFrom(other.scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆变换
|
||||
*/
|
||||
public clone(): Transform {
|
||||
const transform = new Transform(this.position, this.rotation, this.scale);
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
+157
-21
@@ -1,28 +1,164 @@
|
||||
module es {
|
||||
/**
|
||||
* 位操作类,用于操作一个位数组。
|
||||
*/
|
||||
export class Bits {
|
||||
private _bit: { [index: number]: number } = {};
|
||||
/**
|
||||
* 高性能位操作类
|
||||
* 用于快速操作位数组,支持组件匹配等场景
|
||||
*/
|
||||
export class Bits {
|
||||
private _words: number[] = [];
|
||||
private static readonly WORD_SIZE = 32;
|
||||
|
||||
/**
|
||||
* 设置指定位置的位值。
|
||||
* @param index 位置索引
|
||||
* @param value 位值(0 或 1)
|
||||
*/
|
||||
public set(index: number, value: number) {
|
||||
this._bit[index] = value;
|
||||
constructor() {
|
||||
this._words = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定位置的位为1
|
||||
* @param index 位置索引
|
||||
*/
|
||||
public set(index: number): void {
|
||||
const wordIndex = Math.floor(index / Bits.WORD_SIZE);
|
||||
const bitIndex = index % Bits.WORD_SIZE;
|
||||
|
||||
// 确保数组足够大
|
||||
while (this._words.length <= wordIndex) {
|
||||
this._words.push(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定位置的位值。
|
||||
* @param index 位置索引
|
||||
* @returns 位值(0 或 1)
|
||||
*/
|
||||
public get(index: number): number {
|
||||
let v = this._bit[index];
|
||||
return v == null ? 0 : v;
|
||||
this._words[wordIndex] |= (1 << bitIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定位置的位(设为0)
|
||||
* @param index 位置索引
|
||||
*/
|
||||
public clear(index: number): void {
|
||||
const wordIndex = Math.floor(index / Bits.WORD_SIZE);
|
||||
const bitIndex = index % Bits.WORD_SIZE;
|
||||
|
||||
if (wordIndex < this._words.length) {
|
||||
this._words[wordIndex] &= ~(1 << bitIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定位置的位值
|
||||
* @param index 位置索引
|
||||
* @returns 位值(true或false)
|
||||
*/
|
||||
public get(index: number): boolean {
|
||||
const wordIndex = Math.floor(index / Bits.WORD_SIZE);
|
||||
const bitIndex = index % Bits.WORD_SIZE;
|
||||
|
||||
if (wordIndex >= this._words.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (this._words[wordIndex] & (1 << bitIndex)) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含所有指定的位
|
||||
* @param other 另一个Bits对象
|
||||
* @returns 如果包含所有位则返回true
|
||||
*/
|
||||
public containsAll(other: Bits): boolean {
|
||||
const maxLength = Math.max(this._words.length, other._words.length);
|
||||
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
const thisWord = i < this._words.length ? this._words[i] : 0;
|
||||
const otherWord = i < other._words.length ? other._words[i] : 0;
|
||||
|
||||
if ((thisWord & otherWord) !== otherWord) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含任意一个指定的位
|
||||
* @param other 另一个Bits对象
|
||||
* @returns 如果包含任意位则返回true
|
||||
*/
|
||||
public intersects(other: Bits): boolean {
|
||||
const minLength = Math.min(this._words.length, other._words.length);
|
||||
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
if ((this._words[i] & other._words[i]) !== 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否不包含任何指定的位
|
||||
* @param other 另一个Bits对象
|
||||
* @returns 如果不包含任何位则返回true
|
||||
*/
|
||||
public excludes(other: Bits): boolean {
|
||||
return !this.intersects(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有位
|
||||
*/
|
||||
public clearAll(): void {
|
||||
this._words.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为空(没有设置任何位)
|
||||
* @returns 如果为空则返回true
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
for (const word of this._words) {
|
||||
if (word !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设置的位数量
|
||||
* @returns 设置的位数量
|
||||
*/
|
||||
public cardinality(): number {
|
||||
let count = 0;
|
||||
for (const word of this._words) {
|
||||
count += this.popCount(word);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算一个32位整数中设置的位数量
|
||||
* @param n 32位整数
|
||||
* @returns 设置的位数量
|
||||
*/
|
||||
private popCount(n: number): number {
|
||||
n = n - ((n >>> 1) & 0x55555555);
|
||||
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
|
||||
return (((n + (n >>> 4)) & 0xF0F0F0F) * 0x1010101) >>> 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制另一个Bits对象
|
||||
* @param other 要复制的Bits对象
|
||||
*/
|
||||
public copyFrom(other: Bits): void {
|
||||
this._words = [...other._words];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建当前Bits的副本
|
||||
* @returns 新的Bits对象
|
||||
*/
|
||||
public clone(): Bits {
|
||||
const newBits = new Bits();
|
||||
newBits.copyFrom(this);
|
||||
return newBits;
|
||||
}
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
///<reference path="../Components/IUpdatable.ts" />
|
||||
module es {
|
||||
export class ComponentList {
|
||||
/**
|
||||
* 比较IUpdatable对象的更新顺序。
|
||||
*/
|
||||
public static compareUpdatableOrder: IUpdatableComparer = new IUpdatableComparer();
|
||||
public _entity: Entity;
|
||||
/**
|
||||
* 实体的组件列表。
|
||||
*/
|
||||
public _components: Component[] = [];
|
||||
|
||||
/**
|
||||
* 可更新的组件列表。
|
||||
*/
|
||||
public _updatableComponents: IUpdatable[] = [];
|
||||
|
||||
/**
|
||||
* 等待添加到实体的组件列表。
|
||||
*/
|
||||
public _componentsToAdd: { [index: number]: Component } = {};
|
||||
|
||||
/**
|
||||
* 等待从实体中移除的组件列表。
|
||||
*/
|
||||
public _componentsToRemove: { [index: number]: Component } = {};
|
||||
|
||||
/**
|
||||
* 等待添加到实体的组件列表(作为数组)。
|
||||
*/
|
||||
public _componentsToAddList: Component[] = [];
|
||||
|
||||
/**
|
||||
* 等待从实体中移除的组件列表(作为数组)。
|
||||
*/
|
||||
public _componentsToRemoveList: Component[] = [];
|
||||
|
||||
/**
|
||||
* 临时的组件缓冲列表。
|
||||
*/
|
||||
public _tempBufferList: Component[] = [];
|
||||
|
||||
/**
|
||||
* 指示组件列表是否已排序的标志。
|
||||
*/
|
||||
public _isComponentListUnsorted: boolean;
|
||||
|
||||
/**
|
||||
* 按组件类型组织的组件列表字典。
|
||||
*/
|
||||
private componentsByType = new Map<new (...args: any[]) => Component, es.Component[]>();
|
||||
|
||||
/**
|
||||
* 按组件类型组织的等待添加到实体的组件列表字典。
|
||||
*/
|
||||
private componentsToAddByType = new Map<new (...args: any[]) => Component, es.Component[]>();
|
||||
|
||||
|
||||
constructor(entity: Entity) {
|
||||
this._entity = entity;
|
||||
}
|
||||
|
||||
public get count() {
|
||||
return this._components.length;
|
||||
}
|
||||
|
||||
public get buffer() {
|
||||
return this._components;
|
||||
}
|
||||
|
||||
public markEntityListUnsorted() {
|
||||
this._isComponentListUnsorted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将组件添加到实体的组件列表中,并添加到组件类型字典中。
|
||||
* @param component 要添加的组件。
|
||||
*/
|
||||
public add(component: Component) {
|
||||
// 将组件添加到_componentsToAdd和_componentsToAddList中,并添加到相应的组件类型字典中
|
||||
this._componentsToAdd[component.id] = component;
|
||||
this._componentsToAddList.push(component);
|
||||
this.addComponentsToAddByType(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体的组件列表中移除组件,并从相应的组件类型字典中移除组件。
|
||||
* @param component 要从实体中移除的组件。
|
||||
*/
|
||||
public remove(component: Component) {
|
||||
// 如果组件在_componentsToAdd中,则将其从_componentsToAddList中移除,并从相应的组件类型字典中移除组件
|
||||
if (this._componentsToAdd[component.id]) {
|
||||
const index = this._componentsToAddList.findIndex((c) => c.id === component.id);
|
||||
if (index !== -1) {
|
||||
this._componentsToAddList.splice(index, 1);
|
||||
}
|
||||
delete this._componentsToAdd[component.id];
|
||||
this.removeComponentsToAddByType(component);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果组件不在_componentsToAdd中,则将其添加到_componentsToRemove和_componentsToRemoveList中
|
||||
this._componentsToRemove[component.id] = component;
|
||||
this._componentsToRemoveList.push(component);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 立即从组件列表中删除所有组件
|
||||
*/
|
||||
public removeAllComponents() {
|
||||
if (this._components.length > 0) {
|
||||
for (let i = 0, s = this._components.length; i < s; ++i) {
|
||||
this.handleRemove(this._components[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.componentsByType.clear();
|
||||
this.componentsToAddByType.clear();
|
||||
this._components.length = 0;
|
||||
this._updatableComponents.length = 0;
|
||||
this._componentsToAdd = {};
|
||||
this._componentsToRemove = {};
|
||||
this._componentsToAddList.length = 0;
|
||||
this._componentsToRemoveList.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体的所有组件上注销并从相关数据结构中删除它们。
|
||||
*/
|
||||
public deregisterAllComponents() {
|
||||
if (this._components.length > 0) {
|
||||
for (const component of this._components) {
|
||||
// 处理IUpdatable
|
||||
if (isIUpdatable(component)) {
|
||||
// 创建一个新的List实例,从_updatableComponents中移除组件,以避免并发修改异常
|
||||
new es.List(this._updatableComponents).remove(component);
|
||||
}
|
||||
|
||||
// 从位掩码中减去组件类型的索引,通知实体处理器一个组件已被移除
|
||||
this.decreaseBits(component);
|
||||
this._entity.scene.entityProcessors.onComponentRemoved(this._entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册实体的所有组件,并将它们添加到相应的数据结构中。
|
||||
*/
|
||||
public registerAllComponents() {
|
||||
if (this._components.length > 0) {
|
||||
for (const component of this._components) {
|
||||
if (isIUpdatable(component)) {
|
||||
// 如果组件是可更新的,则将其添加到_updatableComponents中
|
||||
this._updatableComponents.push(component);
|
||||
}
|
||||
|
||||
// 将组件类型的索引添加到实体的位掩码中,通知实体处理器一个组件已被添加
|
||||
this.addBits(component);
|
||||
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体的位掩码中减去组件类型的索引。
|
||||
* @param component 要从实体中删除的组件。
|
||||
*/
|
||||
private decreaseBits(component: Component) {
|
||||
const bits = this._entity.componentBits;
|
||||
|
||||
// 获取组件类型的索引,将其对应位掩码减1
|
||||
const typeIndex = ComponentTypeManager.getIndexFor(TypeUtils.getType(component));
|
||||
bits.set(typeIndex, bits.get(typeIndex) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在实体的位掩码中添加组件类型的索引。
|
||||
* @param component 要添加到实体的组件。
|
||||
*/
|
||||
private addBits(component: Component) {
|
||||
const bits = this._entity.componentBits;
|
||||
|
||||
// 获取组件类型的索引,将其对应位掩码加1
|
||||
const typeIndex = ComponentTypeManager.getIndexFor(TypeUtils.getType(component));
|
||||
bits.set(typeIndex, bits.get(typeIndex) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新实体的组件列表和相关数据结构。
|
||||
* 如果有组件要添加或删除,它将相应地更新组件列表和其他数据结构。
|
||||
*/
|
||||
public updateLists() {
|
||||
// 处理要删除的组件
|
||||
if (this._componentsToRemoveList.length > 0) {
|
||||
for (const component of this._componentsToRemoveList) {
|
||||
// 从实体中删除组件,从组件列表和相关数据结构中删除组件
|
||||
this.handleRemove(component);
|
||||
|
||||
// 从_components数组中删除组件
|
||||
const index = this._components.findIndex((c) => c.id === component.id);
|
||||
if (index !== -1) {
|
||||
this._components.splice(index, 1);
|
||||
}
|
||||
|
||||
// 从组件类型字典中删除组件
|
||||
this.removeComponentsByType(component);
|
||||
}
|
||||
|
||||
// 清空_componentsToRemove和_componentsToRemoveList
|
||||
this._componentsToRemove = {};
|
||||
this._componentsToRemoveList.length = 0;
|
||||
}
|
||||
|
||||
// 处理要添加的组件
|
||||
if (this._componentsToAddList.length > 0) {
|
||||
for (const component of this._componentsToAddList) {
|
||||
// 如果组件可以更新,则添加到可更新组件列表中
|
||||
if (isIUpdatable(component)) {
|
||||
this._updatableComponents.push(component);
|
||||
}
|
||||
|
||||
// 更新实体的组件位掩码,通知实体处理器一个组件已经添加
|
||||
this.addBits(component);
|
||||
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
|
||||
|
||||
// 将组件添加到相应类型的fastList中,将组件添加到_components数组中
|
||||
this.addComponentsByType(component);
|
||||
this._components.push(component);
|
||||
|
||||
// 将组件添加到_tempBufferList中,稍后调用onAddedToEntity和onEnabled
|
||||
this._tempBufferList.push(component);
|
||||
}
|
||||
|
||||
// 清空_componentsToAdd、_componentsToAddList和componentsToAddByType,设置_isComponentListUnsorted标志
|
||||
this._componentsToAdd = {};
|
||||
this._componentsToAddList.length = 0;
|
||||
this.componentsToAddByType.clear();
|
||||
this._isComponentListUnsorted = true;
|
||||
}
|
||||
|
||||
// 调用新添加组件的onAddedToEntity和onEnabled方法
|
||||
if (this._tempBufferList.length > 0) {
|
||||
for (const component of this._tempBufferList) {
|
||||
component.onAddedToEntity();
|
||||
|
||||
// 如果组件已启用,则调用onEnabled方法
|
||||
if (component.enabled) {
|
||||
component.onEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
// 清空_tempBufferList
|
||||
this._tempBufferList.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public handleRemove(component: Component) {
|
||||
// 如果组件可以更新,从可更新组件列表中删除该组件
|
||||
if (isIUpdatable(component) && this._updatableComponents.length > 0) {
|
||||
const index = this._updatableComponents.findIndex((c) => (<any>c as Component).id === component.id);
|
||||
if (index !== -1) {
|
||||
this._updatableComponents.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新实体的组件位掩码
|
||||
this.decreaseBits(component);
|
||||
|
||||
// 通知实体处理器一个组件已被删除
|
||||
this._entity.scene.entityProcessors.onComponentRemoved(this._entity);
|
||||
|
||||
// 调用组件的onRemovedFromEntity方法,将其entity属性设置为null
|
||||
component.onRemovedFromEntity();
|
||||
component.entity = null;
|
||||
}
|
||||
|
||||
private removeComponentsByType(component: Component) {
|
||||
// 获取存储指定类型组件的fastList数组
|
||||
const fastList = this.componentsByType.get(TypeUtils.getType(component));
|
||||
|
||||
// 在fastList中查找要删除的组件
|
||||
const index = fastList.findIndex((c) => c.id === component.id);
|
||||
if (index !== -1) {
|
||||
// 找到组件后,使用splice方法将其从fastList中删除
|
||||
fastList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private addComponentsByType(component: Component) {
|
||||
// 获取存储指定类型组件的fastList数组
|
||||
let fastList = this.componentsByType.get(TypeUtils.getType(component));
|
||||
|
||||
// 如果fastList不存在,则创建一个空数组
|
||||
if (!fastList) {
|
||||
fastList = [];
|
||||
}
|
||||
|
||||
// 在fastList中添加组件
|
||||
fastList.push(component);
|
||||
|
||||
// 更新componentsByType字典,以便它包含fastList数组
|
||||
this.componentsByType.set(TypeUtils.getType(component), fastList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从待添加组件列表中移除指定类型的组件。
|
||||
* @param component 要移除的组件
|
||||
*/
|
||||
private removeComponentsToAddByType(component: Component) {
|
||||
// 获取待添加组件列表中指定类型的组件列表
|
||||
let fastList = this.componentsToAddByType.get(TypeUtils.getType(component));
|
||||
|
||||
// 在该列表中查找指定组件
|
||||
let fastIndex = fastList.findIndex(c => c.id == component.id);
|
||||
|
||||
// 如果找到了指定组件,则从列表中移除它
|
||||
if (fastIndex != -1) {
|
||||
fastList.splice(fastIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向待添加组件列表中添加指定类型的组件。
|
||||
* @param component 要添加的组件
|
||||
*/
|
||||
private addComponentsToAddByType(component: Component) {
|
||||
// 获取待添加组件列表中指定类型的组件列表
|
||||
let fastList = this.componentsToAddByType.get(TypeUtils.getType(component));
|
||||
|
||||
// 如果指定类型的组件列表不存在,则创建一个新的列表
|
||||
if (!fastList) fastList = [];
|
||||
|
||||
// 向指定类型的组件列表中添加组件
|
||||
fastList.push(component);
|
||||
|
||||
// 更新待添加组件列表中指定类型的组件列表
|
||||
this.componentsToAddByType.set(TypeUtils.getType(component), fastList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的第一个组件实例。
|
||||
* @param type 组件类型
|
||||
* @param onlyReturnInitializedComponents 是否仅返回已初始化的组件
|
||||
* @returns 指定类型的第一个组件实例,如果不存在则返回 null
|
||||
*/
|
||||
public getComponent<T extends Component>(
|
||||
type: new (...args: any[]) => T,
|
||||
onlyReturnInitializedComponents: boolean
|
||||
): T {
|
||||
// 获取指定类型的组件列表
|
||||
let fastList = this.componentsByType.get(type);
|
||||
|
||||
// 如果指定类型的组件列表存在并且不为空,则返回第一个组件实例
|
||||
if (fastList && fastList.length > 0) return fastList[0] as T;
|
||||
|
||||
// 如果不仅返回已初始化的组件,则检查待添加组件列表中是否存在指定类型的组件
|
||||
if (!onlyReturnInitializedComponents) {
|
||||
let fastToAddList = this.componentsToAddByType.get(type);
|
||||
if (fastToAddList && fastToAddList.length > 0)
|
||||
return fastToAddList[0] as T;
|
||||
}
|
||||
|
||||
// 如果指定类型的组件列表为空且待添加组件列表中也不存在该类型的组件,则返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的所有组件实例。
|
||||
* @param typeName 组件类型名称
|
||||
* @param components 存储组件实例的数组
|
||||
* @returns 存储了指定类型的所有组件实例的数组
|
||||
*/
|
||||
public getComponents(typeName: any, components?: any[]) {
|
||||
// 如果没有传入组件实例数组,则创建一个新数组
|
||||
if (!components) components = [];
|
||||
|
||||
// 获取指定类型的组件列表,并将其添加到组件实例数组中
|
||||
let fastList = this.componentsByType.get(typeName);
|
||||
if (fastList) components = components.concat(fastList);
|
||||
|
||||
// 获取待添加组件列表中的指定类型的组件列表,并将其添加到组件实例数组中
|
||||
let fastToAddList = this.componentsToAddByType.get(typeName);
|
||||
if (fastToAddList) components = components.concat(fastToAddList);
|
||||
|
||||
// 返回存储了指定类型的所有组件实例的数组
|
||||
return components;
|
||||
}
|
||||
|
||||
public update() {
|
||||
this.updateLists();
|
||||
if (this._updatableComponents.length > 0) {
|
||||
for (let i = 0, s = this._updatableComponents.length; i < s; ++i) {
|
||||
let updateComponent = this._updatableComponents[i];
|
||||
if (updateComponent.enabled)
|
||||
updateComponent.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: ComponentTransform) {
|
||||
if (this._components.length > 0) {
|
||||
for (let i = 0, s = this._components.length; i < s; ++i) {
|
||||
let component = this._components[i];
|
||||
if (component.enabled)
|
||||
component.onEntityTransformChanged(comp);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._componentsToAddList.length > 0) {
|
||||
for (let i = 0, s = this._componentsToAddList.length; i < s; ++i) {
|
||||
let component = this._componentsToAddList[i];
|
||||
if (component.enabled)
|
||||
component.onEntityTransformChanged(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onEntityEnabled() {
|
||||
if (this._components.length > 0) {
|
||||
for (let i = 0, s = this._components.length; i < s; i++)
|
||||
this._components[i].onEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
public onEntityDisabled() {
|
||||
if (this._components.length > 0) {
|
||||
for (let i = 0, s = this._components.length; i < s; i++)
|
||||
this._components[i].onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 组件类型工厂,用于生成和管理组件类型。
|
||||
* 维护了一个类型映射表,将组件类型与其唯一索引相对应,以便在运行时高效地检查实体是否包含特定的组件类型。
|
||||
*/
|
||||
export class ComponentTypeFactory {
|
||||
/** 组件类型与其唯一索引的映射表 */
|
||||
private componentTypes: Record<string, ComponentType> = {};
|
||||
/** 组件类型列表,按索引访问组件类型 */
|
||||
public readonly types: Bag<ComponentType> = new Bag<ComponentType>();
|
||||
/** 当前组件类型的计数器 */
|
||||
private componentTypeCount = 0;
|
||||
|
||||
/**
|
||||
* 获取给定组件类型的唯一索引。
|
||||
* 如果该组件类型尚未存在于类型映射表中,则创建一个新的组件类型,并将其添加到映射表和类型列表中。
|
||||
* @param c 要查找或创建的组件类型
|
||||
* @returns 组件类型的唯一索引
|
||||
*/
|
||||
public getIndexFor(c: new (...args: any[]) => any): number {
|
||||
return this.getTypeFor(c).getIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定组件类型的ComponentType对象。
|
||||
* 如果该组件类型尚未存在于类型映射表中,则创建一个新的ComponentType对象,并将其添加到映射表和类型列表中。
|
||||
* @param c 要查找或创建的组件类型
|
||||
* @returns 组件类型的ComponentType对象
|
||||
*/
|
||||
public getTypeFor(c: new (...args: any[]) => any): ComponentType {
|
||||
// 如果给定的组件类型是一个已有的索引,则直接返回对应的ComponentType对象
|
||||
if (typeof c === "number") {
|
||||
return this.types.get(c);
|
||||
}
|
||||
|
||||
// 获取给定组件类型对应的类名
|
||||
const className = getClassName(c);
|
||||
|
||||
// 如果类型映射表中不存在该组件类型,则创建一个新的ComponentType对象
|
||||
if (!this.componentTypes[className]) {
|
||||
const index = this.componentTypeCount++;
|
||||
const type = new ComponentType(c, index);
|
||||
this.componentTypes[className] = type;
|
||||
this.types.set(index, type);
|
||||
}
|
||||
|
||||
// 返回对应的ComponentType对象
|
||||
return this.componentTypes[className];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,99 @@
|
||||
module es {
|
||||
import { Component } from '../Component';
|
||||
import { Bits } from './Bits';
|
||||
|
||||
/**
|
||||
* 组件类型管理器
|
||||
* 负责管理组件类型的注册和ID分配
|
||||
*/
|
||||
export class ComponentTypeManager {
|
||||
private static _instance: ComponentTypeManager;
|
||||
private _componentTypes = new Map<Function, number>();
|
||||
private _typeNames = new Map<number, string>();
|
||||
private _nextTypeId = 0;
|
||||
|
||||
/**
|
||||
* 组件类型管理器,维护了一个组件类型和它们对应的位掩码之间的映射关系。
|
||||
* 用于实现实体匹配器中组件类型的比较操作,以确定实体是否符合给定的匹配器条件。
|
||||
* 获取单例实例
|
||||
*/
|
||||
export class ComponentTypeManager {
|
||||
/** 存储组件类型和它们对应的位掩码的Map */
|
||||
private static _componentTypesMask: Map<any, number> = new Map<any, number>();
|
||||
public static get instance(): ComponentTypeManager {
|
||||
if (!ComponentTypeManager._instance) {
|
||||
ComponentTypeManager._instance = new ComponentTypeManager();
|
||||
}
|
||||
return ComponentTypeManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定的组件类型添加到组件类型列表中,并分配一个唯一的位掩码。
|
||||
* @param type 要添加的组件类型
|
||||
*/
|
||||
public static add(type) {
|
||||
if (!this._componentTypesMask.has(type)) {
|
||||
this._componentTypesMask.set(type, this._componentTypesMask.size);
|
||||
}
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 获取组件类型的ID
|
||||
* @param componentType 组件类型构造函数
|
||||
* @returns 组件类型ID
|
||||
*/
|
||||
public getTypeId<T extends Component>(componentType: new (...args: any[]) => T): number {
|
||||
let typeId = this._componentTypes.get(componentType);
|
||||
|
||||
if (typeId === undefined) {
|
||||
typeId = this._nextTypeId++;
|
||||
this._componentTypes.set(componentType, typeId);
|
||||
this._typeNames.set(typeId, componentType.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定组件类型的位掩码。
|
||||
* 如果该组件类型还没有分配位掩码,则将其添加到列表中,并分配一个唯一的位掩码。
|
||||
* @param type 要获取位掩码的组件类型
|
||||
* @returns 组件类型的位掩码
|
||||
*/
|
||||
public static getIndexFor(type) {
|
||||
let v = -1;
|
||||
if (!this._componentTypesMask.has(type)) {
|
||||
this.add(type);
|
||||
v = this._componentTypesMask.get(type);
|
||||
} else {
|
||||
v = this._componentTypesMask.get(type);
|
||||
}
|
||||
return typeId;
|
||||
}
|
||||
|
||||
return v;
|
||||
/**
|
||||
* 获取组件类型名称
|
||||
* @param typeId 组件类型ID
|
||||
* @returns 组件类型名称
|
||||
*/
|
||||
public getTypeName(typeId: number): string {
|
||||
return this._typeNames.get(typeId) || 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建包含指定组件类型的Bits对象
|
||||
* @param componentTypes 组件类型构造函数数组
|
||||
* @returns Bits对象
|
||||
*/
|
||||
public createBits(...componentTypes: (new (...args: any[]) => Component)[]): Bits {
|
||||
const bits = new Bits();
|
||||
|
||||
for (const componentType of componentTypes) {
|
||||
const typeId = this.getTypeId(componentType);
|
||||
bits.set(typeId);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param components 组件数组
|
||||
* @returns Bits对象
|
||||
*/
|
||||
public getEntityBits(components: Component[]): Bits {
|
||||
const bits = new Bits();
|
||||
|
||||
for (const component of components) {
|
||||
const typeId = this.getTypeId(component.constructor as new (...args: any[]) => Component);
|
||||
bits.set(typeId);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置管理器(主要用于测试)
|
||||
*/
|
||||
public reset(): void {
|
||||
this._componentTypes.clear();
|
||||
this._typeNames.clear();
|
||||
this._nextTypeId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的组件类型数量
|
||||
*/
|
||||
public get registeredTypeCount(): number {
|
||||
return this._componentTypes.size;
|
||||
}
|
||||
}
|
||||
+242
-410
@@ -1,456 +1,288 @@
|
||||
module es {
|
||||
export class EntityList {
|
||||
/**
|
||||
* 场景引用
|
||||
*/
|
||||
public scene: Scene;
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
|
||||
/**
|
||||
* 实体列表
|
||||
*/
|
||||
public _entities: Entity[] = [];
|
||||
/**
|
||||
* 高性能实体列表管理器
|
||||
* 管理场景中的所有实体,支持快速查找和批量操作
|
||||
*/
|
||||
export class EntityList {
|
||||
public buffer: Entity[] = [];
|
||||
private _scene: any; // 临时使用any,避免循环依赖
|
||||
|
||||
/**
|
||||
* 待添加的实体字典
|
||||
*/
|
||||
public _entitiesToAdded: { [index: number]: Entity } = {};
|
||||
// 索引映射,提升查找性能
|
||||
private _idToEntity = new Map<number, Entity>();
|
||||
private _nameToEntities = new Map<string, Entity[]>();
|
||||
|
||||
/**
|
||||
* 待移除的实体字典
|
||||
*/
|
||||
public _entitiesToRemove: { [index: number]: Entity } = {};
|
||||
// 延迟操作队列
|
||||
private _entitiesToAdd: Entity[] = [];
|
||||
private _entitiesToRemove: Entity[] = [];
|
||||
private _isUpdating = false;
|
||||
|
||||
/**
|
||||
* 待添加的实体列表
|
||||
*/
|
||||
public _entitiesToAddedList: Entity[] = [];
|
||||
public get count(): number {
|
||||
return this.buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 待移除的实体列表
|
||||
*/
|
||||
public _entitiesToRemoveList: Entity[] = [];
|
||||
constructor(scene: any) {
|
||||
this._scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体列表是否已排序
|
||||
*/
|
||||
public _isEntityListUnsorted: boolean;
|
||||
/**
|
||||
* 添加实体(立即添加或延迟添加)
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public add(entity: Entity): void {
|
||||
if (this._isUpdating) {
|
||||
// 如果正在更新中,延迟添加
|
||||
this._entitiesToAdd.push(entity);
|
||||
} else {
|
||||
this.addImmediate(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体字典,以实体标签为键
|
||||
*/
|
||||
public _entityDict: Map<number, Set<Entity>> = new Map<number, Set<Entity>>();
|
||||
|
||||
/**
|
||||
* 未排序的标签集合
|
||||
*/
|
||||
public _unsortedTags: Set<number> = new Set<number>();
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
/**
|
||||
* 立即添加实体
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
private addImmediate(entity: Entity): void {
|
||||
// 检查是否已存在
|
||||
if (this._idToEntity.has(entity.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
public get count() {
|
||||
return this._entities.length;
|
||||
this.buffer.push(entity);
|
||||
this._idToEntity.set(entity.id, entity);
|
||||
|
||||
// 更新名称索引
|
||||
this.updateNameIndex(entity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除实体(立即移除或延迟移除)
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
public remove(entity: Entity): void {
|
||||
if (this._isUpdating) {
|
||||
// 如果正在更新中,延迟移除
|
||||
this._entitiesToRemove.push(entity);
|
||||
} else {
|
||||
this.removeImmediate(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即移除实体
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
private removeImmediate(entity: Entity): void {
|
||||
const index = this.buffer.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this.buffer.splice(index, 1);
|
||||
this._idToEntity.delete(entity.id);
|
||||
|
||||
// 更新名称索引
|
||||
this.updateNameIndex(entity, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有实体
|
||||
*/
|
||||
public removeAllEntities(): void {
|
||||
for (let i = this.buffer.length - 1; i >= 0; i--) {
|
||||
this.buffer[i].destroy();
|
||||
}
|
||||
|
||||
public get buffer() {
|
||||
return this._entities;
|
||||
}
|
||||
this.buffer.length = 0;
|
||||
this._idToEntity.clear();
|
||||
this._nameToEntities.clear();
|
||||
this._entitiesToAdd.length = 0;
|
||||
this._entitiesToRemove.length = 0;
|
||||
}
|
||||
|
||||
public markEntityListUnsorted() {
|
||||
this._isEntityListUnsorted = true;
|
||||
}
|
||||
|
||||
public markTagUnsorted(tag: number) {
|
||||
this._unsortedTags.add(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个实体添加到列表中。所有的生命周期方法将在下一帧中被调用
|
||||
* @param entity
|
||||
*/
|
||||
public add(entity: Entity) {
|
||||
this._entitiesToAdded[entity.id] = entity;
|
||||
this._entitiesToAddedList.push(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中移除实体。
|
||||
* @param entity 要从场景中移除的实体。
|
||||
*/
|
||||
public remove(entity: Entity) {
|
||||
// 如果实体在添加列表中,则将其从添加列表中移除
|
||||
if (this._entitiesToAdded[entity.id]) {
|
||||
const index = this._entitiesToAddedList.findIndex((e) => e.id === entity.id);
|
||||
if (index !== -1) {
|
||||
this._entitiesToAddedList.splice(index, 1);
|
||||
}
|
||||
delete this._entitiesToAdded[entity.id];
|
||||
return;
|
||||
/**
|
||||
* 更新实体列表,处理延迟操作
|
||||
*/
|
||||
public updateLists(): void {
|
||||
// 处理延迟添加的实体
|
||||
if (this._entitiesToAdd.length > 0) {
|
||||
for (const entity of this._entitiesToAdd) {
|
||||
this.addImmediate(entity);
|
||||
}
|
||||
this._entitiesToAdd.length = 0;
|
||||
}
|
||||
|
||||
// 如果实体不在添加列表中,则将其添加到移除列表中并将其添加到移除字典中
|
||||
this._entitiesToRemoveList.push(entity);
|
||||
if (!this._entitiesToRemove[entity.id]) {
|
||||
this._entitiesToRemove[entity.id] = entity;
|
||||
// 处理延迟移除的实体
|
||||
if (this._entitiesToRemove.length > 0) {
|
||||
for (const entity of this._entitiesToRemove) {
|
||||
this.removeImmediate(entity);
|
||||
}
|
||||
this._entitiesToRemove.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中移除所有实体。
|
||||
*/
|
||||
public removeAllEntities() {
|
||||
// 清除字典和列表,以及是否已排序的标志
|
||||
this._unsortedTags.clear();
|
||||
this._entitiesToAdded = {};
|
||||
this._entitiesToAddedList.length = 0;
|
||||
this._isEntityListUnsorted = false;
|
||||
/**
|
||||
* 更新所有实体
|
||||
*/
|
||||
public update(): void {
|
||||
this._isUpdating = true;
|
||||
|
||||
// 调用updateLists方法,以处理要移除的实体
|
||||
this.updateLists();
|
||||
|
||||
// 标记并移除所有实体
|
||||
for (const entity of this._entities) {
|
||||
entity._isDestroyed = true;
|
||||
entity.onRemovedFromScene();
|
||||
entity.scene = null;
|
||||
}
|
||||
|
||||
// 清空实体列表和实体字典
|
||||
this._entities.length = 0;
|
||||
this._entityDict.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否已经被添加到场景中。
|
||||
* @param entity 要检查的实体
|
||||
* @returns 如果实体已经被添加到场景中,则返回true;否则返回false
|
||||
*/
|
||||
public contains(entity: Entity): boolean {
|
||||
// 检查实体是否存在于_entitiesToAdded字典中
|
||||
return !!this._entitiesToAdded[entity.id];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取具有指定标签的实体列表。
|
||||
* 如果列表不存在,则创建一个新列表并返回。
|
||||
* @param tag 实体标签
|
||||
* @returns 具有指定标签的实体列表
|
||||
*/
|
||||
public getTagList(tag: number): Set<Entity> {
|
||||
// 尝试从_entityDict中获取具有指定标签的实体列表
|
||||
let list = this._entityDict.get(tag);
|
||||
|
||||
// 如果列表不存在,则创建一个新的Set实例,并添加到_entityDict中
|
||||
if (!list) {
|
||||
list = new Set<Entity>();
|
||||
this._entityDict.set(tag, list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体到标签列表中。
|
||||
* @param entity 实体
|
||||
*/
|
||||
public addToTagList(entity: Entity) {
|
||||
// 获取标签列表
|
||||
const list = this.getTagList(entity.tag);
|
||||
|
||||
// 将实体添加到标签列表中
|
||||
list.add(entity);
|
||||
|
||||
// 添加未排序标志
|
||||
this._unsortedTags.add(entity.tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从标签列表中移除实体。
|
||||
* @param entity 实体
|
||||
*/
|
||||
public removeFromTagList(entity: Entity) {
|
||||
// 获取实体的标签列表
|
||||
const list = this._entityDict.get(entity.tag);
|
||||
|
||||
// 如果标签列表存在,则从中移除实体
|
||||
if (list) {
|
||||
list.delete(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新场景中所有启用的实体的Update方法
|
||||
* 如果实体的UpdateInterval为1或Time.frameCount模除UpdateInterval为0,则每帧调用Update
|
||||
*/
|
||||
public update() {
|
||||
for (let i = 0; i < this._entities.length; i++) {
|
||||
const entity = this._entities[i];
|
||||
if (entity.enabled && (entity.updateInterval === 1 || Time.frameCount % entity.updateInterval === 0)) {
|
||||
try {
|
||||
for (let i = 0; i < this.buffer.length; i++) {
|
||||
const entity = this.buffer[i];
|
||||
if (entity.enabled && !entity.isDestroyed) {
|
||||
entity.update();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this._isUpdating = false;
|
||||
}
|
||||
|
||||
// 处理延迟操作
|
||||
this.updateLists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新场景中实体的列表。
|
||||
*/
|
||||
public updateLists() {
|
||||
// 处理要移除的实体
|
||||
if (this._entitiesToRemoveList.length > 0) {
|
||||
for (const entity of this._entitiesToRemoveList) {
|
||||
// 从标签列表中删除实体
|
||||
this.removeFromTagList(entity);
|
||||
/**
|
||||
* 根据名称查找实体(使用索引,O(1)复杂度)
|
||||
* @param name 实体名称
|
||||
* @returns 找到的第一个实体或null
|
||||
*/
|
||||
public findEntity(name: string): Entity | null {
|
||||
const entities = this._nameToEntities.get(name);
|
||||
return entities && entities.length > 0 ? entities[0] : null;
|
||||
}
|
||||
|
||||
// 从场景实体列表中删除实体
|
||||
const index = this._entities.findIndex((e) => e.id === entity.id);
|
||||
if (index !== -1) {
|
||||
this._entities.splice(index, 1);
|
||||
}
|
||||
/**
|
||||
* 根据名称查找所有实体
|
||||
* @param name 实体名称
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesByName(name: string): Entity[] {
|
||||
return this._nameToEntities.get(name) || [];
|
||||
}
|
||||
|
||||
// 调用实体的onRemovedFromScene方法,并将其scene属性设置为null
|
||||
entity.onRemovedFromScene();
|
||||
entity.scene = null;
|
||||
/**
|
||||
* 根据ID查找实体(使用索引,O(1)复杂度)
|
||||
* @param id 实体ID
|
||||
* @returns 找到的实体或null
|
||||
*/
|
||||
public findEntityById(id: number): Entity | null {
|
||||
return this._idToEntity.get(id) || null;
|
||||
}
|
||||
|
||||
// 通知场景实体处理器,一个实体已被移除
|
||||
this.scene.entityProcessors.onEntityRemoved(entity);
|
||||
}
|
||||
/**
|
||||
* 根据标签查找实体
|
||||
* @param tag 标签
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesByTag(tag: number): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
|
||||
// 清空要移除的实体列表和字典
|
||||
this._entitiesToRemove = {};
|
||||
this._entitiesToRemoveList.length = 0;
|
||||
}
|
||||
|
||||
// 处理要添加的实体
|
||||
if (this._entitiesToAddedList.length > 0) {
|
||||
// 添加实体到场景实体列表和标签列表中
|
||||
for (const entity of this._entitiesToAddedList) {
|
||||
this._entities.push(entity);
|
||||
entity.scene = this.scene;
|
||||
this.addToTagList(entity);
|
||||
}
|
||||
|
||||
// 通知场景实体处理器,有新的实体已添加
|
||||
for (const entity of this._entitiesToAddedList) {
|
||||
this.scene.entityProcessors.onEntityAdded(entity);
|
||||
}
|
||||
|
||||
// 调用实体的onAddedToScene方法,以允许它们执行任何场景相关的操作
|
||||
for (const entity of this._entitiesToAddedList) {
|
||||
entity.onAddedToScene();
|
||||
}
|
||||
|
||||
// 清空要添加的实体列表和字典
|
||||
this._entitiesToAdded = {};
|
||||
this._entitiesToAddedList.length = 0;
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.tag === tag) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个找到的名字为name的实体。如果没有找到则返回null
|
||||
* @param name
|
||||
*/
|
||||
public findEntity(name: string) {
|
||||
if (this._entities.length > 0) {
|
||||
for (let i = 0, s = this._entities.length; i < s; ++i) {
|
||||
let entity = this._entities[i];
|
||||
if (entity.name == name)
|
||||
return entity;
|
||||
}
|
||||
/**
|
||||
* 根据组件类型查找实体
|
||||
* @param componentType 组件类型
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.hasComponent(componentType)) {
|
||||
result.push(entity);
|
||||
}
|
||||
|
||||
if (this._entitiesToAddedList.length > 0) {
|
||||
for (let i = 0, s = this._entitiesToAddedList.length; i < s; ++i) {
|
||||
let entity = this._entitiesToAddedList[i];
|
||||
if (entity.name == name)
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过实体ID在场景中查找对应实体
|
||||
* @param id 实体ID
|
||||
* @returns 返回找到的实体,如果没有找到则返回 null
|
||||
*/
|
||||
public findEntityById(id: number) {
|
||||
// 遍历场景中所有实体
|
||||
if (this._entities.length > 0) {
|
||||
for (let i = 0, s = this._entities.length; i < s; ++i) {
|
||||
let entity = this._entities[i];
|
||||
// 如果实体的ID匹配,返回该实体
|
||||
if (entity.id == id)
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 在未添加的实体列表中查找
|
||||
return this._entitiesToAdded[id];
|
||||
/**
|
||||
* 批量操作:对所有实体执行指定操作
|
||||
* @param action 要执行的操作
|
||||
*/
|
||||
public forEach(action: (entity: Entity) => void): void {
|
||||
for (const entity of this.buffer) {
|
||||
action(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作:对符合条件的实体执行指定操作
|
||||
* @param predicate 筛选条件
|
||||
* @param action 要执行的操作
|
||||
*/
|
||||
public forEachWhere(predicate: (entity: Entity) => boolean, action: (entity: Entity) => void): void {
|
||||
for (const entity of this.buffer) {
|
||||
if (predicate(entity)) {
|
||||
action(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新名称索引
|
||||
* @param entity 实体
|
||||
* @param isAdd 是否为添加操作
|
||||
*/
|
||||
private updateNameIndex(entity: Entity, isAdd: boolean): void {
|
||||
if (!entity.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签对应的实体列表
|
||||
* @param tag 实体的标签
|
||||
* @returns 返回所有拥有该标签的实体列表
|
||||
*/
|
||||
public entitiesWithTag(tag: number): Entity[] {
|
||||
// 从字典中获取对应标签的实体列表
|
||||
const list = this.getTagList(tag);
|
||||
if (isAdd) {
|
||||
let entities = this._nameToEntities.get(entity.name);
|
||||
if (!entities) {
|
||||
entities = [];
|
||||
this._nameToEntities.set(entity.name, entities);
|
||||
}
|
||||
entities.push(entity);
|
||||
} else {
|
||||
const entities = this._nameToEntities.get(entity.name);
|
||||
if (entities) {
|
||||
const index = entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
entities.splice(index, 1);
|
||||
|
||||
// 从对象池中获取 Entity 类型的数组
|
||||
const returnList = ListPool.obtain<Entity>(Entity);
|
||||
|
||||
if (list.size > 0) {
|
||||
// 将实体列表中的实体添加到返回列表中
|
||||
for (const entity of list) {
|
||||
returnList.push(entity);
|
||||
// 如果数组为空,删除映射
|
||||
if (entities.length === 0) {
|
||||
this._nameToEntities.delete(entity.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回已填充好实体的返回列表
|
||||
return returnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个找到该tag的实体
|
||||
* @param tag
|
||||
* @returns
|
||||
*/
|
||||
public entityWithTag(tag: number) {
|
||||
let list = this.getTagList(tag);
|
||||
|
||||
if (list.size > 0) {
|
||||
for (let entity of list) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中查找具有给定类型的组件。
|
||||
* @param type 要查找的组件类型。
|
||||
* @returns 如果找到,则返回该组件;否则返回null。
|
||||
*/
|
||||
public findComponentOfType<T extends Component>(type: new (...args: any[]) => T): T | null {
|
||||
// 遍历场景中的所有实体,查找具有给定类型的组件
|
||||
for (const entity of this._entities) {
|
||||
if (entity.enabled) {
|
||||
const comp = entity.getComponent(type);
|
||||
if (comp) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历待添加的实体列表中的所有实体,查找具有给定类型的组件
|
||||
for (const entity of this._entitiesToAddedList) {
|
||||
if (entity.enabled) {
|
||||
const comp = entity.getComponent(type);
|
||||
if (comp) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找不到具有给定类型的组件,则返回null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在场景中查找具有给定类型的所有组件。
|
||||
* @param type 要查找的组件类型。
|
||||
* @returns 具有给定类型的所有组件的列表。
|
||||
*/
|
||||
public findComponentsOfType<T extends Component>(type: new (...args: any[]) => T): T[] {
|
||||
// 从池中获取一个可重用的组件列表
|
||||
const comps = ListPool.obtain<T>(type);
|
||||
|
||||
// 遍历场景中的所有实体,查找具有给定类型的组件并添加到组件列表中
|
||||
for (const entity of this._entities) {
|
||||
if (entity.enabled) {
|
||||
entity.getComponents(type, comps);
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历待添加的实体列表中的所有实体,查找具有给定类型的组件并添加到组件列表中
|
||||
for (const entity of this._entitiesToAddedList) {
|
||||
if (entity.enabled) {
|
||||
entity.getComponents(type, comps);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回具有给定类型的所有组件的列表
|
||||
return comps;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回拥有指定类型组件的所有实体
|
||||
* @param types 要查询的组件类型列表
|
||||
* @returns 返回拥有指定类型组件的所有实体
|
||||
*/
|
||||
public findEntitiesOfComponent(...types: any[]): Entity[] {
|
||||
const entities = [];
|
||||
|
||||
// 遍历所有已存在的实体
|
||||
for (const entity of this._entities) {
|
||||
// 只有启用的实体才会被考虑
|
||||
if (entity.enabled) {
|
||||
// 如果types数组为空,直接将实体添加到结果数组中
|
||||
if (types.length === 0) {
|
||||
entities.push(entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 对于每个指定的组件类型,检查实体是否具有该组件
|
||||
let meet = true;
|
||||
for (const type of types) {
|
||||
const hasComp = entity.hasComponent(type);
|
||||
if (!hasComp) {
|
||||
meet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果实体满足要求,将其添加到结果数组中
|
||||
if (meet) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历所有等待添加的实体,和上面的操作类似
|
||||
for (const entity of this._entitiesToAddedList) {
|
||||
if (entity.enabled) {
|
||||
if (types.length === 0) {
|
||||
entities.push(entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
let meet = true;
|
||||
for (const type of types) {
|
||||
const hasComp = entity.hasComponent(type);
|
||||
if (!hasComp) {
|
||||
meet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (meet) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
/**
|
||||
* 获取实体列表的统计信息
|
||||
* @returns 统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalEntities: number;
|
||||
activeEntities: number;
|
||||
pendingAdd: number;
|
||||
pendingRemove: number;
|
||||
nameIndexSize: number;
|
||||
} {
|
||||
let activeCount = 0;
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.enabled && !entity.isDestroyed) {
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalEntities: this.buffer.length,
|
||||
activeEntities: activeCount,
|
||||
pendingAdd: this._entitiesToAdd.length,
|
||||
pendingRemove: this._entitiesToRemove.length,
|
||||
nameIndexSize: this._nameToEntities.size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,182 +1,108 @@
|
||||
module es {
|
||||
export class EntityProcessorList {
|
||||
private _processors: EntitySystem[] = []; // 处理器列表
|
||||
private _orderDirty: boolean = false; // 处理器排序标志
|
||||
import { EntitySystem } from '../Systems/EntitySystem';
|
||||
|
||||
/** 获取处理器列表 */
|
||||
public get processors() {
|
||||
return this._processors;
|
||||
}
|
||||
/**
|
||||
* 实体处理器列表管理器
|
||||
* 管理场景中的所有实体系统
|
||||
*/
|
||||
export class EntityProcessorList {
|
||||
private _processors: EntitySystem[] = [];
|
||||
private _isDirty = false;
|
||||
|
||||
/** 获取处理器数量 */
|
||||
public get count() {
|
||||
return this._processors.length;
|
||||
}
|
||||
/**
|
||||
* 设置为脏状态,需要重新排序
|
||||
*/
|
||||
public setDirty(): void {
|
||||
this._isDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加处理器
|
||||
* @param processor 要添加的处理器
|
||||
*/
|
||||
public add(processor: EntitySystem): void {
|
||||
this._processors.push(processor);
|
||||
}
|
||||
/**
|
||||
* 添加实体处理器
|
||||
* @param processor 要添加的处理器
|
||||
*/
|
||||
public add(processor: EntitySystem): void {
|
||||
this._processors.push(processor);
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除处理器
|
||||
* @param processor 要移除的处理器
|
||||
*/
|
||||
public remove(processor: EntitySystem): void {
|
||||
// 使用 es.List 类的 remove() 方法从处理器列表中移除指定处理器
|
||||
new es.List(this._processors).remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在实体上添加组件时被调用
|
||||
* @param entity 添加组件的实体
|
||||
*/
|
||||
public onComponentAdded(entity: Entity): void {
|
||||
this.notifyEntityChanged(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在实体上移除组件时被调用
|
||||
* @param entity 移除组件的实体
|
||||
*/
|
||||
public onComponentRemoved(entity: Entity): void {
|
||||
this.notifyEntityChanged(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加实体时被调用
|
||||
* @param entity 添加的实体
|
||||
*/
|
||||
public onEntityAdded(entity: Entity): void {
|
||||
this.notifyEntityChanged(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中移除实体时被调用
|
||||
* @param entity 移除的实体
|
||||
*/
|
||||
public onEntityRemoved(entity: Entity): void {
|
||||
this.removeFromProcessors(entity);
|
||||
}
|
||||
|
||||
/** 在处理器列表上开始循环 */
|
||||
public begin(): void {
|
||||
}
|
||||
|
||||
/** 更新处理器列表 */
|
||||
public update(): void {
|
||||
// 如果处理器列表为空,则直接返回
|
||||
if (this._processors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果需要重新排序处理器列表
|
||||
if (this._orderDirty) {
|
||||
// 对处理器列表进行排序
|
||||
this._processors.sort((a, b) => a.updateOrder - b.updateOrder);
|
||||
|
||||
// 重新设置处理器的更新顺序
|
||||
for (let i = 0, s = this._processors.length; i < s; ++i) {
|
||||
const processor = this._processors[i];
|
||||
processor.setUpdateOrder(i);
|
||||
}
|
||||
|
||||
// 将标志设置为“未脏”
|
||||
this.clearDirty();
|
||||
}
|
||||
|
||||
// 调用每个处理器的 update() 方法
|
||||
for (let i = 0, s = this._processors.length; i < s; ++i) {
|
||||
const processor = this._processors[i];
|
||||
processor.update();
|
||||
}
|
||||
}
|
||||
|
||||
/** 在处理器列表上完成循环 */
|
||||
public end(): void {
|
||||
}
|
||||
|
||||
/** 设置处理器排序标志 */
|
||||
public setDirty(): void {
|
||||
this._orderDirty = true;
|
||||
}
|
||||
|
||||
/** 清除处理器排序标志 */
|
||||
public clearDirty(): void {
|
||||
this._orderDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的处理器
|
||||
* @param type 指定类型的构造函数
|
||||
* @returns 指定类型的处理器
|
||||
*/
|
||||
public getProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T {
|
||||
// 如果处理器列表为空,则返回null
|
||||
if (this._processors.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 遍历处理器列表,查找指定类型的处理器
|
||||
for (let i = 0, s = this._processors.length; i < s; ++i) {
|
||||
const processor = this._processors[i];
|
||||
|
||||
// 如果当前处理器是指定类型的实例,则返回当前处理器
|
||||
if (processor instanceof type) {
|
||||
return processor as T;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到指定类型的处理器,则返回null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知处理器实体已更改
|
||||
* @param entity 发生更改的实体
|
||||
*/
|
||||
protected notifyEntityChanged(entity: Entity): void {
|
||||
if (this._processors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历处理器列表,调用每个处理器的 onChanged() 方法
|
||||
for (let i = 0, s = this._processors.length; i < s; ++i) {
|
||||
const processor = this._processors[i];
|
||||
processor.onChanged(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从处理器列表中移除实体
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
protected removeFromProcessors(entity: Entity): void {
|
||||
if (this._processors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历处理器列表,调用每个处理器的 remove() 方法
|
||||
for (let i = 0, s = this._processors.length; i < s; ++i) {
|
||||
const processor = this._processors[i];
|
||||
processor.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/** 在处理器列表上进行后期更新 */
|
||||
public lateUpdate(): void {
|
||||
if (this._processors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用每个处理器的 lateUpdate() 方法
|
||||
for (let i = 0, s = this._processors.length; i < s; ++i) {
|
||||
const processor = this._processors[i];
|
||||
processor.lateUpdate();
|
||||
}
|
||||
/**
|
||||
* 移除实体处理器
|
||||
* @param processor 要移除的处理器
|
||||
*/
|
||||
public remove(processor: EntitySystem): void {
|
||||
const index = this._processors.indexOf(processor);
|
||||
if (index !== -1) {
|
||||
this._processors.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的处理器
|
||||
* @param type 处理器类型
|
||||
*/
|
||||
public getProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
|
||||
for (const processor of this._processors) {
|
||||
if (processor instanceof type) {
|
||||
return processor as T;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始处理
|
||||
*/
|
||||
public begin(): void {
|
||||
this.sortProcessors();
|
||||
for (const processor of this._processors) {
|
||||
processor.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束处理
|
||||
*/
|
||||
public end(): void {
|
||||
// 清理处理器
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有处理器
|
||||
*/
|
||||
public update(): void {
|
||||
this.sortProcessors();
|
||||
for (const processor of this._processors) {
|
||||
processor.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期更新所有处理器
|
||||
*/
|
||||
public lateUpdate(): void {
|
||||
for (const processor of this._processors) {
|
||||
processor.lateUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序处理器
|
||||
*/
|
||||
private sortProcessors(): void {
|
||||
if (this._isDirty) {
|
||||
this._processors.sort((a, b) => a.updateOrder - b.updateOrder);
|
||||
this._isDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取处理器列表 */
|
||||
public get processors() {
|
||||
return this._processors;
|
||||
}
|
||||
|
||||
/** 获取处理器数量 */
|
||||
public get count() {
|
||||
return this._processors.length;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
module es {
|
||||
export class HashHelpers {
|
||||
// 哈希冲突阈值,超过此值将使用另一种哈希算法
|
||||
public static readonly hashCollisionThreshold: number = 100;
|
||||
// 哈希值用于计算哈希表索引的质数
|
||||
public static readonly hashPrime: number = 101;
|
||||
// 一组预定义的质数,用于计算哈希表容量
|
||||
public static readonly primes = [
|
||||
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
|
||||
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
|
||||
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
|
||||
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
|
||||
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
|
||||
];
|
||||
// 可分配的最大数组长度,用于避免 OutOfMemoryException
|
||||
public static readonly maxPrimeArrayLength = 0x7FEFFFFD;
|
||||
|
||||
/**
|
||||
* 判断一个数是否为质数
|
||||
* @param candidate 要判断的数
|
||||
* @returns 是否为质数
|
||||
*/
|
||||
public static isPrime(candidate: number): boolean {
|
||||
if ((candidate & 1) !== 0) { // 位运算判断奇偶性
|
||||
let limit = Math.sqrt(candidate);
|
||||
for (let divisor = 3; divisor <= limit; divisor += 2) { // 奇数因子判断
|
||||
if ((candidate % divisor) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return (candidate === 2); // 2是质数
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取大于等于指定值的最小质数
|
||||
* @param min 指定值
|
||||
* @returns 大于等于指定值的最小质数
|
||||
*/
|
||||
public static getPrime(min: number): number {
|
||||
if (min < 0) {
|
||||
throw new Error("参数错误 min 不能小于0");
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.primes.length; i++) {
|
||||
let prime = this.primes[i];
|
||||
if (prime >= min) {
|
||||
return prime;
|
||||
}
|
||||
}
|
||||
|
||||
// 在预定义的质数列表之外,需要计算最小的质数
|
||||
for (let i = (min | 1); i < Number.MAX_VALUE; i += 2) { // 从 min 向上计算奇数
|
||||
if (this.isPrime(i) && ((i - 1) % this.hashPrime !== 0)) { // i是质数且不是hashPrime的倍数
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展哈希表容量
|
||||
* @param oldSize 原哈希表容量
|
||||
* @returns 扩展后的哈希表容量
|
||||
*/
|
||||
public static expandPrime(oldSize: number): number {
|
||||
let newSize = 2 * oldSize;
|
||||
|
||||
// 在遇到容量溢出之前,允许哈希特表增长到最大可能的大小
|
||||
// 请注意,即使当_items.Length溢出时,这项检查也会起作用
|
||||
if (newSize > this.maxPrimeArrayLength && this.maxPrimeArrayLength > oldSize) {
|
||||
return this.maxPrimeArrayLength;
|
||||
}
|
||||
|
||||
return this.getPrime(newSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算字符串的哈希值
|
||||
* @param str 要计算哈希值的字符串
|
||||
* @returns 哈希值
|
||||
*/
|
||||
public static getHashCode(str: string): number {
|
||||
let hash = 0;
|
||||
if (str.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char; // 采用 FNV-1a 哈希算法
|
||||
hash = hash & hash; // 将hash值转换为32位整数
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
module es {
|
||||
export class IdentifierPool {
|
||||
private ids: Bag<number>;
|
||||
private nextAvailableId_ = 0;
|
||||
/**
|
||||
* ID池管理器
|
||||
* 用于管理实体ID的分配和回收
|
||||
*/
|
||||
export class IdentifierPool {
|
||||
private _nextAvailableId = 0;
|
||||
private _ids: number[] = [];
|
||||
|
||||
constructor() {
|
||||
this.ids = new Bag<number>();
|
||||
/**
|
||||
* 获取一个可用的ID
|
||||
*/
|
||||
public checkOut(): number {
|
||||
if (this._ids.length > 0) {
|
||||
return this._ids.pop()!;
|
||||
}
|
||||
return this._nextAvailableId++;
|
||||
}
|
||||
|
||||
public checkOut(): number {
|
||||
if (this.ids.size() > 0) {
|
||||
return this.ids.removeLast();
|
||||
}
|
||||
|
||||
return this.nextAvailableId_++;
|
||||
}
|
||||
|
||||
public checkIn(id: number): void {
|
||||
this.ids.add(id);
|
||||
}
|
||||
/**
|
||||
* 回收一个ID
|
||||
* @param id 要回收的ID
|
||||
*/
|
||||
public checkIn(id: number): void {
|
||||
this._ids.push(id);
|
||||
}
|
||||
}
|
||||
+146
-67
@@ -1,89 +1,168 @@
|
||||
module es {
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { Bits } from './Bits';
|
||||
import { ComponentTypeManager } from './ComponentTypeManager';
|
||||
|
||||
/**
|
||||
* 高性能实体匹配器
|
||||
* 用于快速匹配符合条件的实体
|
||||
*/
|
||||
export class Matcher {
|
||||
protected allSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected exclusionSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected oneSet: (new (...args: any[]) => Component)[] = [];
|
||||
|
||||
// 缓存的位掩码,避免重复计算
|
||||
private _allBits?: Bits;
|
||||
private _exclusionBits?: Bits;
|
||||
private _oneBits?: Bits;
|
||||
private _isDirty = true;
|
||||
|
||||
public static empty(): Matcher {
|
||||
return new Matcher();
|
||||
}
|
||||
|
||||
public getAllSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.allSet;
|
||||
}
|
||||
|
||||
public getExclusionSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.exclusionSet;
|
||||
}
|
||||
|
||||
public getOneSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.oneSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义一个实体匹配器类。
|
||||
* 检查实体是否匹配条件
|
||||
* @param entity 要检查的实体
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
export class Matcher {
|
||||
protected allSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected exclusionSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected oneSet: (new (...args: any[]) => Component)[] = [];
|
||||
public isInterestedEntity(entity: Entity): boolean {
|
||||
const entityBits = this.getEntityBits(entity);
|
||||
return this.isInterested(entityBits);
|
||||
}
|
||||
|
||||
public static empty() {
|
||||
return new Matcher();
|
||||
/**
|
||||
* 检查组件位掩码是否匹配条件
|
||||
* @param componentBits 组件位掩码
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
public isInterested(componentBits: Bits): boolean {
|
||||
this.updateBitsIfDirty();
|
||||
|
||||
// 检查必须包含的组件
|
||||
if (this._allBits && !componentBits.containsAll(this._allBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getAllSet() {
|
||||
return this.allSet;
|
||||
// 检查排除的组件
|
||||
if (this._exclusionBits && componentBits.intersects(this._exclusionBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getExclusionSet() {
|
||||
return this.exclusionSet;
|
||||
// 检查至少包含其中之一的组件
|
||||
if (this._oneBits && !componentBits.intersects(this._oneBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getOneSet() {
|
||||
return this.oneSet;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加所有包含的组件类型
|
||||
* @param types 所有包含的组件类型列表
|
||||
*/
|
||||
public all(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.allSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加排除包含的组件类型
|
||||
* @param types 排除包含的组件类型列表
|
||||
*/
|
||||
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.exclusionSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加至少包含其中之一的组件类型
|
||||
* @param types 至少包含其中之一的组件类型列表
|
||||
*/
|
||||
public one(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.oneSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param entity 实体
|
||||
* @returns 组件位掩码
|
||||
*/
|
||||
private getEntityBits(entity: Entity): Bits {
|
||||
const components = entity.components;
|
||||
return ComponentTypeManager.instance.getEntityBits(components);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果位掩码已过期,则更新它们
|
||||
*/
|
||||
private updateBitsIfDirty(): void {
|
||||
if (!this._isDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
public isInterestedEntity(e: Entity) {
|
||||
return this.isInterested(e.componentBits);
|
||||
const typeManager = ComponentTypeManager.instance;
|
||||
|
||||
// 更新必须包含的组件位掩码
|
||||
if (this.allSet.length > 0) {
|
||||
this._allBits = typeManager.createBits(...this.allSet);
|
||||
} else {
|
||||
this._allBits = undefined;
|
||||
}
|
||||
|
||||
public isInterested(components: Bits) {
|
||||
if (this.allSet.length !== 0) {
|
||||
for (let i = 0; i < this.allSet.length; i++) {
|
||||
const type = this.allSet[i];
|
||||
if (!components.get(ComponentTypeManager.getIndexFor(type))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.exclusionSet.length !== 0) {
|
||||
for (let i = 0; i < this.exclusionSet.length; i++) {
|
||||
const type = this.exclusionSet[i];
|
||||
if (components.get(ComponentTypeManager.getIndexFor(type))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.oneSet.length !== 0) {
|
||||
for (let i = 0; i < this.oneSet.length; i++) {
|
||||
const type = this.oneSet[i];
|
||||
if (components.get(ComponentTypeManager.getIndexFor(type))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// 更新排除的组件位掩码
|
||||
if (this.exclusionSet.length > 0) {
|
||||
this._exclusionBits = typeManager.createBits(...this.exclusionSet);
|
||||
} else {
|
||||
this._exclusionBits = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加所有包含的组件类型。
|
||||
* @param types 所有包含的组件类型列表
|
||||
*/
|
||||
public all(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.allSet.push(...types);
|
||||
return this;
|
||||
// 更新至少包含其中之一的组件位掩码
|
||||
if (this.oneSet.length > 0) {
|
||||
this._oneBits = typeManager.createBits(...this.oneSet);
|
||||
} else {
|
||||
this._oneBits = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加排除包含的组件类型。
|
||||
* @param types 排除包含的组件类型列表
|
||||
*/
|
||||
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.exclusionSet.push(...types);
|
||||
return this;
|
||||
this._isDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建匹配器的字符串表示(用于调试)
|
||||
* @returns 字符串表示
|
||||
*/
|
||||
public toString(): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (this.allSet.length > 0) {
|
||||
parts.push(`all: [${this.allSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加至少包含其中之一的组件类型。
|
||||
* @param types 至少包含其中之一的组件类型列表
|
||||
*/
|
||||
public one(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.oneSet.push(...types);
|
||||
return this;
|
||||
if (this.exclusionSet.length > 0) {
|
||||
parts.push(`exclude: [${this.exclusionSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (this.oneSet.length > 0) {
|
||||
parts.push(`one: [${this.oneSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
return `Matcher(${parts.join(', ')})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
module es {
|
||||
export class StringUtils {
|
||||
/**
|
||||
* 特殊符号字符串
|
||||
*/
|
||||
private static specialSigns: string[] = [
|
||||
'&', '&',
|
||||
'<', '<',
|
||||
'>', '>',
|
||||
'"', '"',
|
||||
"'", ''',
|
||||
'®', '®',
|
||||
'©', '©',
|
||||
'™', '™',
|
||||
];
|
||||
|
||||
/**
|
||||
* 匹配中文字符
|
||||
* @param str 需要匹配的字符串
|
||||
* @return
|
||||
*/
|
||||
public static matchChineseWord(str: string): string[] {
|
||||
//中文字符的unicode值[\u4E00-\u9FA5]
|
||||
let patternA: RegExp = /[\u4E00-\u9FA5]+/gim;
|
||||
return str.match(patternA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字符串左端的空白字符
|
||||
* @param target 目标字符串
|
||||
* @return
|
||||
*/
|
||||
public static lTrim(target: string): string {
|
||||
let startIndex: number = 0;
|
||||
while (this.isWhiteSpace(target.charAt(startIndex))) {
|
||||
startIndex++;
|
||||
}
|
||||
return target.slice(startIndex, target.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字符串右端的空白字符
|
||||
* @param target 目标字符串
|
||||
* @return
|
||||
*/
|
||||
public static rTrim(target: string): string {
|
||||
let endIndex: number = target.length - 1;
|
||||
while (this.isWhiteSpace(target.charAt(endIndex))) {
|
||||
endIndex--;
|
||||
}
|
||||
return target.slice(0, endIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个去除2段空白字符的字符串
|
||||
* @param target
|
||||
* @return 返回一个去除2段空白字符的字符串
|
||||
*/
|
||||
public static trim(target: string): string {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
return this.rTrim(this.lTrim(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该字符是否为空白字符
|
||||
* @param str
|
||||
* @return 返回该字符是否为空白字符
|
||||
*/
|
||||
public static isWhiteSpace(str: string): boolean {
|
||||
if (str == " " || str == "\t" || str == "\r" || str == "\n")
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该字符是否为空字符或者为null
|
||||
* @param str
|
||||
* @returns
|
||||
*/
|
||||
public static isNullOrEmpty(str: string): boolean {
|
||||
if (str == "" || str == null || str == undefined)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回执行替换后的字符串
|
||||
* @param mainStr 待查找字符串
|
||||
* @param targetStr 目标字符串
|
||||
* @param replaceStr 替换字符串
|
||||
* @param caseMark 是否忽略大小写
|
||||
* @return 返回执行替换后的字符串
|
||||
*/
|
||||
public static replaceMatch(mainStr: string, targetStr: string,
|
||||
replaceStr: string, caseMark: boolean = false): string {
|
||||
let len: number = mainStr.length;
|
||||
let tempStr: string = "";
|
||||
let isMatch: boolean = false;
|
||||
let tempTarget: string = caseMark == true ? targetStr.toLowerCase() : targetStr;
|
||||
for (let i: number = 0; i < len; i++) {
|
||||
isMatch = false;
|
||||
if (mainStr.charAt(i) == tempTarget.charAt(0)) {
|
||||
if (mainStr.substr(i, tempTarget.length) == tempTarget) {
|
||||
isMatch = true;
|
||||
}
|
||||
}
|
||||
if (isMatch) {
|
||||
tempStr += replaceStr;
|
||||
i = i + tempTarget.length - 1;
|
||||
} else {
|
||||
tempStr += mainStr.charAt(i);
|
||||
}
|
||||
}
|
||||
return tempStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用html实体换掉字符窜中的特殊字符
|
||||
* @param str 需要替换的字符串
|
||||
* @param reversion 是否翻转替换:将转义符号替换为正常的符号
|
||||
* @return 换掉特殊字符后的字符串
|
||||
*/
|
||||
public static htmlSpecialChars(str: string, reversion: boolean = false): string {
|
||||
let len: number = this.specialSigns.length;
|
||||
for (let i: number = 0; i < len; i += 2) {
|
||||
let from: string;
|
||||
let to: string;
|
||||
from = this.specialSigns[i];
|
||||
to = this.specialSigns[i + 1];
|
||||
if (reversion) {
|
||||
let temp: string = from;
|
||||
from = to;
|
||||
to = temp;
|
||||
}
|
||||
str = this.replaceMatch(str, from, to);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 给数字字符前面添 "0"
|
||||
*
|
||||
* @param str 要进行处理的字符串
|
||||
* @param width 处理后字符串的长度,
|
||||
* 如果str.length >= width,将不做任何处理直接返回原始的str。
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static zfill(str: string, width: number = 2): string {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
width = Math.floor(width);
|
||||
let slen: number = str.length;
|
||||
if (slen >= width) {
|
||||
return str;
|
||||
}
|
||||
|
||||
let negative: boolean = false;
|
||||
if (str.substr(0, 1) == '-') {
|
||||
negative = true;
|
||||
str = str.substr(1);
|
||||
}
|
||||
|
||||
let len: number = width - slen;
|
||||
for (let i: number = 0; i < len; i++) {
|
||||
str = '0' + str;
|
||||
}
|
||||
|
||||
if (negative) {
|
||||
str = '-' + str;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 翻转字符串
|
||||
* @param str 字符串
|
||||
* @return 翻转后的字符串
|
||||
*/
|
||||
public static reverse(str: string): string {
|
||||
if (str.length > 1)
|
||||
return this.reverse(str.substring(1)) + str.substring(0, 1);
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 截断某段字符串
|
||||
* @param str 目标字符串
|
||||
* @param start 需要截断的起始索引
|
||||
* @param en 截断长度
|
||||
* @param order 顺序,true从字符串头部开始计算,false从字符串尾巴开始结算。
|
||||
* @return 截断后的字符串
|
||||
*/
|
||||
public static cutOff(str: string, start: number,
|
||||
len: number, order: boolean = true): string {
|
||||
start = Math.floor(start);
|
||||
len = Math.floor(len);
|
||||
let length: number = str.length;
|
||||
if (start > length) start = length;
|
||||
let s: number = start;
|
||||
let e: number = start + len;
|
||||
let newStr: string;
|
||||
if (order) {
|
||||
newStr = str.substring(0, s) + str.substr(e, length);
|
||||
} else {
|
||||
s = length - 1 - start - len;
|
||||
e = s + len;
|
||||
newStr = str.substring(0, s + 1) + str.substr(e + 1, length);
|
||||
}
|
||||
return newStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* {0} 字符替换
|
||||
*/
|
||||
public static strReplace(str: string, rStr: string[]): string {
|
||||
let i: number = 0, len: number = rStr.length;
|
||||
for (; i < len; i++) {
|
||||
if (rStr[i] == null || rStr[i] == "") {
|
||||
rStr[i] = "无";
|
||||
}
|
||||
str = str.replace("{" + i + "}", rStr[i]);
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
public static format(str: string, ...args: any[]) {
|
||||
for (let i = 0; i < args.length - 1; i++) {
|
||||
let reg = new RegExp("\\{" + i + "\\}", "gm");
|
||||
str = str.replace(reg, args[i + 1]);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 时间管理器,用于管理游戏中的时间相关属性
|
||||
*/
|
||||
export class Time {
|
||||
/** 游戏运行的总时间,单位为秒 */
|
||||
public static totalTime: number = 0;
|
||||
|
||||
/** deltaTime 的未缩放版本,不受时间尺度的影响 */
|
||||
public static unscaledDeltaTime: number = 0;
|
||||
|
||||
/** 前一帧到当前帧的时间增量,按时间刻度进行缩放 */
|
||||
public static deltaTime: number = 0;
|
||||
|
||||
/** 时间刻度缩放,可以加快或减慢游戏时间 */
|
||||
public static timeScale = 1;
|
||||
|
||||
/** DeltaTime 可以为的最大值,避免游戏出现卡顿情况 */
|
||||
public static maxDeltaTime = Number.MAX_VALUE;
|
||||
|
||||
/** 已传递的帧总数 */
|
||||
public static frameCount = 0;
|
||||
|
||||
/** 自场景加载以来的总时间,单位为秒 */
|
||||
public static timeSinceSceneLoad: number = 0;
|
||||
|
||||
/** 上一次记录的时间,用于计算两次调用 update 之间的时间差 */
|
||||
private static _lastTime = -1;
|
||||
|
||||
/**
|
||||
* 更新时间管理器
|
||||
* @param currentTime 当前时间
|
||||
* @param useEngineTime 是否使用引擎时间
|
||||
*/
|
||||
public static update(currentTime: number, useEngineTime: boolean) {
|
||||
let dt = 0;
|
||||
|
||||
if (useEngineTime) {
|
||||
dt = currentTime;
|
||||
} else {
|
||||
// 如果当前时间为 -1,则表示使用系统时间
|
||||
if (currentTime === -1) {
|
||||
currentTime = Date.now();
|
||||
}
|
||||
|
||||
// 如果上一次记录的时间为 -1,则表示当前为第一次调用 update
|
||||
if (this._lastTime === -1) {
|
||||
this._lastTime = currentTime;
|
||||
}
|
||||
|
||||
// 计算两次调用 update 之间的时间差,并将其转换为秒
|
||||
dt = (currentTime - this._lastTime) / 1000;
|
||||
}
|
||||
|
||||
// 如果计算得到的时间差超过了最大时间步长,则将其限制为最大时间步长
|
||||
if (dt > this.maxDeltaTime) {
|
||||
dt = this.maxDeltaTime;
|
||||
}
|
||||
|
||||
// 更新时间管理器的各个属性
|
||||
this.totalTime += dt;
|
||||
this.deltaTime = dt * this.timeScale;
|
||||
this.unscaledDeltaTime = dt;
|
||||
this.timeSinceSceneLoad += dt;
|
||||
this.frameCount++;
|
||||
|
||||
// 记录当前时间,以备下一次调用 update 使用
|
||||
this._lastTime = currentTime;
|
||||
}
|
||||
|
||||
|
||||
public static sceneChanged() {
|
||||
this.timeSinceSceneLoad = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定时间间隔是否已过去
|
||||
* @param interval 指定时间间隔
|
||||
* @returns 是否已过去指定时间间隔
|
||||
*/
|
||||
public static checkEvery(interval: number): boolean {
|
||||
// 计算当前时刻所经过的完整时间间隔个数(向下取整)
|
||||
const passedIntervals = Math.floor(this.timeSinceSceneLoad / interval);
|
||||
|
||||
// 计算上一帧到当前帧经过的时间所包含的时间间隔个数(向下取整)
|
||||
const deltaIntervals = Math.floor(this.deltaTime / interval);
|
||||
|
||||
// 如果当前时刻所经过的时间间隔数比上一帧所经过的时间间隔数多,则说明时间间隔已过去
|
||||
return passedIntervals > deltaIntervals;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
module es {
|
||||
export class TimeUtils {
|
||||
/**
|
||||
* 获取日期对应的年份和月份的数字组合
|
||||
* @param d 要获取月份的日期对象,不传则默认为当前时间
|
||||
* @returns 返回数字组合的年份和月份
|
||||
*/
|
||||
public static monthId(d: Date = null): number {
|
||||
// 如果传入了时间,则使用传入的时间,否则使用当前时间
|
||||
d = d ? d : new Date();
|
||||
|
||||
// 获取当前年份
|
||||
let y = d.getFullYear();
|
||||
|
||||
// 获取当前月份,并将月份转化为两位数的字符串格式
|
||||
let m = d.getMonth() + 1;
|
||||
let g = m < 10 ? "0" : "";
|
||||
|
||||
// 返回年份和月份的数字组合
|
||||
return parseInt(y + g + m);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日期的数字组合
|
||||
* @param t - 可选参数,传入时间,若不传入则使用当前时间
|
||||
* @returns 数字组合
|
||||
*/
|
||||
public static dateId(t: Date = null): number {
|
||||
// 如果传入了时间,则使用传入的时间,否则使用当前时间
|
||||
t = t ? t : new Date();
|
||||
|
||||
// 获取当前月份,并将月份转化为两位数的字符串格式
|
||||
let m: number = t.getMonth() + 1;
|
||||
let a = m < 10 ? "0" : "";
|
||||
|
||||
// 获取当前日期,并将日期转化为两位数的字符串格式
|
||||
let d: number = t.getDate();
|
||||
let b = d < 10 ? "0" : "";
|
||||
|
||||
// 返回年份、月份和日期的数字组合
|
||||
return parseInt(t.getFullYear() + a + m + b + d);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期所在周的数字组合
|
||||
* @param d - 可选参数,传入日期,若不传入则使用当前日期
|
||||
* @param first - 是否将当前周视为本年度的第1周,默认为true
|
||||
* @returns 数字组合
|
||||
*/
|
||||
public static weekId(d: Date = null, first: boolean = true): number {
|
||||
d = d ? d : new Date();
|
||||
const c: Date = new Date(d.getTime()); // 复制一个新的日期对象,以免改变原始日期对象
|
||||
c.setDate(1);
|
||||
c.setMonth(0); // 将日期设置为当年的第一天
|
||||
|
||||
const year: number = c.getFullYear();
|
||||
let firstDay: number = c.getDay();
|
||||
if (firstDay == 0) {
|
||||
firstDay = 7;
|
||||
}
|
||||
|
||||
let max: boolean = false;
|
||||
if (firstDay <= 4) {
|
||||
max = firstDay > 1;
|
||||
c.setDate(c.getDate() - (firstDay - 1));
|
||||
} else {
|
||||
c.setDate(c.getDate() + 7 - firstDay + 1);
|
||||
}
|
||||
|
||||
let num: number = this.diffDay(d, c, false); // 计算当前日期与本年度的第一个星期一之间的天数
|
||||
if (num < 0) {
|
||||
// 当前日期在本年度第一个星期一之前,则返回上一年度的最后一个星期
|
||||
c.setDate(1);
|
||||
c.setMonth(0);
|
||||
c.setDate(c.getDate() - 1);
|
||||
return this.weekId(c, false);
|
||||
}
|
||||
|
||||
// 计算当前日期在本年度中是第几个星期
|
||||
const week: number = Math.floor(num / 7);
|
||||
const weekIdx: number = Math.floor(week) + 1;
|
||||
if (weekIdx == 53) {
|
||||
c.setTime(d.getTime());
|
||||
c.setDate(c.getDate() - 1);
|
||||
let endDay: number = c.getDay();
|
||||
if (endDay == 0) {
|
||||
endDay = 7;
|
||||
}
|
||||
if (first && (!max || endDay < 4)) {
|
||||
// 如果当前日期在本年度的最后一个星期并且当前年度的星期数不足53或当前日期在本年度第53周的星期4或更早,则返回下一年度的第1周
|
||||
c.setFullYear(c.getFullYear() + 1);
|
||||
c.setDate(1);
|
||||
c.setMonth(0);
|
||||
return this.weekId(c, false);
|
||||
}
|
||||
}
|
||||
const g: string = weekIdx > 9 ? "" : "0";
|
||||
const s: string = year + "00" + g + weekIdx; // 加上00防止和月份ID冲突
|
||||
return parseInt(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个日期之间相差的天数
|
||||
* @param a 第一个日期
|
||||
* @param b 第二个日期
|
||||
* @param fixOne 是否将相差天数四舍五入到整数
|
||||
* @returns 两个日期之间相差的天数
|
||||
*/
|
||||
public static diffDay(a: Date, b: Date, fixOne: boolean = false): number {
|
||||
let x = (a.getTime() - b.getTime()) / 86400000; // 计算两个日期相差的毫秒数,然后除以一天的毫秒数,得到相差的天数
|
||||
return fixOne ? Math.ceil(x) : Math.floor(x); // 如果 fixOne 参数为 true,则将相差天数四舍五入到整数,否则向下取整
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在周的第一天
|
||||
* @param d 指定日期,默认值为今天
|
||||
* @returns 指定日期所在周的第一天
|
||||
*/
|
||||
public static getFirstDayOfWeek(d: Date = new Date()): Date {
|
||||
// 获取当前日期是星期几,如果是0,则设置为7
|
||||
let dayOfWeek = d.getDay() || 7;
|
||||
// 计算出指定日期所在周的第一天,即将指定日期减去星期几再加1
|
||||
// 这里用1减去dayOfWeek是为了保证星期一为一周的第一天
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1 - dayOfWeek, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当日凌晨时间
|
||||
*/
|
||||
public static getFirstOfDay(d?: Date): Date {
|
||||
d = d ? d : new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取次日凌晨时间
|
||||
*/
|
||||
public static getNextFirstOfDay(d?: Date): Date {
|
||||
return new Date(this.getFirstOfDay(d).getTime() + 86400000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期为 "YYYY-MM-DD" 的字符串形式
|
||||
* @param date 要格式化的日期
|
||||
* @returns 格式化后的日期字符串
|
||||
*/
|
||||
public static formatDate(date: Date): string {
|
||||
let y = date.getFullYear();
|
||||
let m: any = date.getMonth() + 1;
|
||||
m = m < 10 ? '0' + m : m;
|
||||
let d: any = date.getDate();
|
||||
d = d < 10 ? ('0' + d) : d;
|
||||
return y + '-' + m + '-' + d;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将日期对象格式化为 "YYYY-MM-DD HH:mm:ss" 的字符串
|
||||
* @param date 日期对象
|
||||
* @returns 格式化后的字符串
|
||||
*/
|
||||
public static formatDateTime(date: Date): string {
|
||||
let y = date.getFullYear();
|
||||
let m: any = date.getMonth() + 1;
|
||||
m = m < 10 ? ('0' + m) : m;
|
||||
let d: any = date.getDate();
|
||||
d = d < 10 ? ('0' + d) : d;
|
||||
let h = date.getHours();
|
||||
let i: any = date.getMinutes();
|
||||
i = i < 10 ? ('0' + i) : i;
|
||||
let s: any = date.getSeconds();
|
||||
s = s < 10 ? ('0' + s) : s;
|
||||
return y + '-' + m + '-' + d + ' ' + h + ':' + i + ":" + s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串解析为Date对象
|
||||
* @param s 要解析的日期字符串,例如:2022-01-01
|
||||
* @returns 返回解析后的Date对象,如果解析失败,则返回当前时间的Date对象
|
||||
*/
|
||||
public static parseDate(s: string): Date {
|
||||
let t = Date.parse(s);
|
||||
if (!isNaN(t)) {
|
||||
// 如果日期字符串中的分隔符为“-”,则需要先将其转换为“/”,否则解析会失败
|
||||
return new Date(Date.parse(s.replace(/-/g, "/")));
|
||||
} else {
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将秒数转换为时分秒的格式
|
||||
* @param time 秒数
|
||||
* @param partition 分隔符
|
||||
* @param showHour 是否显示小时位
|
||||
* @returns 转换后的时间字符串
|
||||
*/
|
||||
public static secondToTime(time: number = 0, partition: string = ":", showHour: boolean = true): string {
|
||||
let hours: number = Math.floor(time / 3600);
|
||||
let minutes: number = Math.floor(time % 3600 / 60);
|
||||
let seconds: number = Math.floor(time % 3600 % 60);
|
||||
|
||||
let h: string = hours.toString();
|
||||
let m: string = minutes.toString();
|
||||
let s: string = seconds.toString();
|
||||
|
||||
if (hours < 10) h = "0" + h;
|
||||
if (minutes < 10) m = "0" + m;
|
||||
if (seconds < 10) s = "0" + s;
|
||||
|
||||
let timeStr: string;
|
||||
if (showHour)
|
||||
timeStr = h + partition + m + partition + s;
|
||||
else
|
||||
timeStr = m + partition + s;
|
||||
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间字符串转换为毫秒数
|
||||
* @param time 时间字符串,如 "01:30:15" 表示 1小时30分钟15秒
|
||||
* @param partition 分隔符,默认为 ":"
|
||||
* @returns 转换后的毫秒数字符串
|
||||
*/
|
||||
public static timeToMillisecond(time: string, partition: string = ":"): string {
|
||||
let _ary: any[] = time.split(partition);
|
||||
let timeNum: number = 0;
|
||||
let len: number = _ary.length;
|
||||
|
||||
// 将时间转换成毫秒数
|
||||
for (let i: number = 0; i < len; i++) {
|
||||
let n: number = <number>_ary[i];
|
||||
timeNum += n * Math.pow(60, (len - 1 - i));
|
||||
}
|
||||
timeNum *= 1000;
|
||||
|
||||
return timeNum.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// ECS工具类导出
|
||||
export { EntityList } from './EntityList';
|
||||
export { EntityProcessorList } from './EntityProcessorList';
|
||||
export { IdentifierPool } from './IdentifierPool';
|
||||
export { Matcher } from './Matcher';
|
||||
export { Bits } from './Bits';
|
||||
export { ComponentTypeManager } from './ComponentTypeManager';
|
||||
@@ -0,0 +1,8 @@
|
||||
// Core目录已删除,直接导出核心类
|
||||
export { Entity } from './Entity';
|
||||
export { Component } from './Component';
|
||||
export { Transform } from './Transform';
|
||||
export { CoreEvents } from './CoreEvents';
|
||||
export * from './Systems';
|
||||
export * from './Utils';
|
||||
export { Scene } from './Scene';
|
||||
@@ -1,126 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 三次方和二次方贝塞尔帮助器(cubic and quadratic bezier helper)
|
||||
*/
|
||||
export class Bezier {
|
||||
/**
|
||||
* 求解二次曲折线
|
||||
* @param p0
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param t
|
||||
*/
|
||||
public static getPoint(p0: Vector2, p1: Vector2, p2: Vector2, t: number): Vector2 {
|
||||
t = MathHelper.clamp01(t);
|
||||
let oneMinusT = 1 - t;
|
||||
return p0.scale(oneMinusT * oneMinusT)
|
||||
.addEqual(p1.scale(2 * oneMinusT * t))
|
||||
.addEqual(p2.scale(t * t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 求解一个立方体曲率
|
||||
* @param start
|
||||
* @param firstControlPoint
|
||||
* @param secondControlPoint
|
||||
* @param end
|
||||
* @param t
|
||||
*/
|
||||
public static getPointThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
|
||||
end: Vector2, t: number): Vector2 {
|
||||
t = MathHelper.clamp01(t);
|
||||
const oneMinusT = 1 - t;
|
||||
return start.scale(oneMinusT * oneMinusT * oneMinusT)
|
||||
.addEqual(firstControlPoint.scale(3 * oneMinusT * oneMinusT * t))
|
||||
.addEqual(secondControlPoint.scale(3 * oneMinusT * t * t))
|
||||
.addEqual(end.scale(t * t * t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到二次贝塞尔函数的一阶导数
|
||||
* @param p0
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param t
|
||||
*/
|
||||
public static getFirstDerivative(p0: Vector2, p1: Vector2, p2: Vector2, t: number) {
|
||||
return p1.sub(p0).scale(2 * (1 - t))
|
||||
.addEqual(p2.sub(p1).scale(2 * t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到一个三次贝塞尔函数的一阶导数
|
||||
* @param start
|
||||
* @param firstControlPoint
|
||||
* @param secondControlPoint
|
||||
* @param end
|
||||
* @param t
|
||||
*/
|
||||
public static getFirstDerivativeThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
|
||||
end: Vector2, t: number) {
|
||||
t = MathHelper.clamp01(t);
|
||||
let oneMunusT = 1 - t;
|
||||
return firstControlPoint.sub(start).scale(3 * oneMunusT * oneMunusT)
|
||||
.addEqual(secondControlPoint.sub(firstControlPoint).scale(6 * oneMunusT * t))
|
||||
.addEqual(end.sub(secondControlPoint).scale(3 * t * t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归地细分bezier曲线,直到满足距离校正
|
||||
* 在这种算法中,平面切片的点要比曲面切片少。返回完成后应返回到ListPool的合并列表。
|
||||
* @param start
|
||||
* @param firstCtrlPoint
|
||||
* @param secondCtrlPoint
|
||||
* @param end
|
||||
* @param distanceTolerance
|
||||
*/
|
||||
public static getOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
|
||||
end: Vector2, distanceTolerance: number = 1) {
|
||||
let points = ListPool.obtain<Vector2>(Vector2);
|
||||
points.push(start);
|
||||
this.recursiveGetOptimizedDrawingPoints(start, firstCtrlPoint, secondCtrlPoint, end, points, distanceTolerance);
|
||||
points.push(end);
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归地细分bezier曲线,直到满足距离校正。在这种算法中,平面切片的点要比曲面切片少。
|
||||
* @param start
|
||||
* @param firstCtrlPoint
|
||||
* @param secondCtrlPoint
|
||||
* @param end
|
||||
* @param points
|
||||
* @param distanceTolerance
|
||||
*/
|
||||
private static recursiveGetOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
|
||||
end: Vector2, points: Vector2[], distanceTolerance: number) {
|
||||
// 计算线段的所有中点
|
||||
let pt12 = Vector2.divideScaler(start.add(firstCtrlPoint), 2);
|
||||
let pt23 = Vector2.divideScaler(firstCtrlPoint.add(secondCtrlPoint), 2);
|
||||
let pt34 = Vector2.divideScaler(secondCtrlPoint.add(end), 2);
|
||||
|
||||
// 计算新半直线的中点
|
||||
let pt123 = Vector2.divideScaler(pt12.add(pt23), 2);
|
||||
let pt234 = Vector2.divideScaler(pt23.add(pt34), 2);
|
||||
|
||||
// 最后再细分最后两个中点。如果我们满足我们的距离公差,这将是我们使用的最后一点。
|
||||
let pt1234 = Vector2.divideScaler(pt123.add(pt234), 2);
|
||||
|
||||
// 试着用一条直线来近似整个三次曲线
|
||||
let deltaLine = end.sub(start);
|
||||
|
||||
let d2 = Math.abs(((firstCtrlPoint.x, end.x) * deltaLine.y - (firstCtrlPoint.y - end.y) * deltaLine.x));
|
||||
let d3 = Math.abs(((secondCtrlPoint.x - end.x) * deltaLine.y - (secondCtrlPoint.y - end.y) * deltaLine.x));
|
||||
|
||||
if ((d2 + d3) * (d2 + d3) < distanceTolerance * (deltaLine.x * deltaLine.x + deltaLine.y * deltaLine.y)) {
|
||||
points.push(pt1234);
|
||||
return;
|
||||
}
|
||||
|
||||
// 继续细分
|
||||
this.recursiveGetOptimizedDrawingPoints(start, pt12, pt123, pt1234, points, distanceTolerance);
|
||||
this.recursiveGetOptimizedDrawingPoints(pt1234, pt234, pt34, end, points, distanceTolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 提供了一系列立方贝塞尔点,并提供了帮助方法来访问贝塞尔
|
||||
*/
|
||||
export class BezierSpline {
|
||||
public _points: Vector2[] = [];
|
||||
public _curveCount: number = 0;
|
||||
|
||||
/**
|
||||
* 在这个过程中,t被修改为在曲线段的范围内。
|
||||
* @param t
|
||||
*/
|
||||
public pointIndexAtTime(t: number): {time: number, range: number} {
|
||||
const res = {time: 0, range: 0};
|
||||
if (t >= 1) {
|
||||
t = 1;
|
||||
res.range = this._points.length - 4;
|
||||
} else {
|
||||
t = MathHelper.clamp01(t) * this._curveCount;
|
||||
res.range = MathHelper.toInt(t);
|
||||
t -= res.range;
|
||||
res.range *= 3;
|
||||
}
|
||||
|
||||
res.time = t;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个控制点,考虑到这是否是一个共享点,如果是,则适当调整
|
||||
* @param index
|
||||
* @param point
|
||||
*/
|
||||
public setControlPoint(index: number, point: Vector2) {
|
||||
if (index % 3 == 0) {
|
||||
const delta = point.sub(this._points[index]);
|
||||
if (index > 0)
|
||||
this._points[index - 1].addEqual(delta);
|
||||
|
||||
if (index + 1 < this._points.length)
|
||||
this._points[index + 1].addEqual(delta);
|
||||
}
|
||||
|
||||
this._points[index] = point;
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到时间t的贝塞尔曲线上的点
|
||||
* @param t
|
||||
*/
|
||||
public getPointAtTime(t: number): Vector2{
|
||||
const res = this.pointIndexAtTime(t);
|
||||
const i = res.range;
|
||||
return Bezier.getPointThree(this._points[i], this._points[i + 1], this._points[i + 2],
|
||||
this._points[i + 3], t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到贝塞尔在时间t的速度(第一导数)
|
||||
* @param t
|
||||
*/
|
||||
public getVelocityAtTime(t: number): Vector2 {
|
||||
const res = this.pointIndexAtTime(t);
|
||||
const i = res.range;
|
||||
return Bezier.getFirstDerivativeThree(this._points[i], this._points[i + 1], this._points[i + 2],
|
||||
this._points[i + 3], t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到时间t时贝塞尔的方向(归一化第一导数)
|
||||
* @param t
|
||||
*/
|
||||
public getDirectionAtTime(t: number) {
|
||||
return this.getVelocityAtTime(t).normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在贝塞尔曲线上添加一条曲线
|
||||
* @param start
|
||||
* @param firstControlPoint
|
||||
* @param secondControlPoint
|
||||
* @param end
|
||||
*/
|
||||
public addCurve(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2, end: Vector2) {
|
||||
// 只有当这是第一条曲线时,我们才会添加起始点。对于其他所有的曲线,前一个曲线的终点应该等于新曲线的起点。
|
||||
if (this._points.length == 0)
|
||||
this._points.push(start);
|
||||
|
||||
this._points.push(firstControlPoint);
|
||||
this._points.push(secondControlPoint);
|
||||
this._points.push(end);
|
||||
|
||||
this._curveCount = (this._points.length - 1) / 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置bezier,移除所有点
|
||||
*/
|
||||
public reset() {
|
||||
this._points.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将splitine分解成totalSegments部分,并返回使用线条绘制所需的所有点
|
||||
* @param totalSegments
|
||||
*/
|
||||
public getDrawingPoints(totalSegments: number): Vector2[] {
|
||||
let points: Vector2[] = [];
|
||||
for (let i = 0; i < totalSegments; i ++) {
|
||||
let t = i / totalSegments;
|
||||
points[i] = this.getPointAtTime(t);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 边缘枚举
|
||||
* 表示矩形的四个边
|
||||
*/
|
||||
export enum Edge {
|
||||
/**
|
||||
* 顶边
|
||||
*/
|
||||
top = 0,
|
||||
|
||||
/**
|
||||
* 底边
|
||||
*/
|
||||
bottom = 1,
|
||||
|
||||
/**
|
||||
* 左边
|
||||
*/
|
||||
left = 2,
|
||||
|
||||
/**
|
||||
* 右边
|
||||
*/
|
||||
right = 3
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 一个用于操作二进制标志(也称为位字段)
|
||||
*/
|
||||
export class Flags {
|
||||
/**
|
||||
* 检查指定二进制数字中是否已设置了指定标志位
|
||||
* @param self 二进制数字
|
||||
* @param flag 标志位,应该为2的幂
|
||||
* @returns 如果设置了指定的标志位,则返回true,否则返回false
|
||||
*/
|
||||
public static isFlagSet(self: number, flag: number): boolean {
|
||||
return (self & flag) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定二进制数字中是否已设置未移位的指定标志位
|
||||
* @param self 二进制数字
|
||||
* @param flag 标志位,不应移位(应为2的幂)
|
||||
* @returns 如果设置了指定的标志位,则返回true,否则返回false
|
||||
*/
|
||||
public static isUnshiftedFlagSet(self: number, flag: number): boolean {
|
||||
flag = 1 << flag;
|
||||
return (self & flag) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的标志位设置为二进制数字的唯一标志
|
||||
* @param self 二进制数字
|
||||
* @param flag 标志位,应该为2的幂
|
||||
*/
|
||||
public static setFlagExclusive(self: Ref<number>, flag: number) {
|
||||
self.value = 1 << flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的标志位设置为二进制数字
|
||||
* @param self 二进制数字的引用
|
||||
* @param flag 标志位,应该为2的幂
|
||||
*/
|
||||
public static setFlag(self: Ref<number>, flag: number) {
|
||||
self.value |= 1 << flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的标志位从二进制数字中取消设置
|
||||
* @param self 二进制数字的引用
|
||||
* @param flag 标志位,应该为2的幂
|
||||
*/
|
||||
public static unsetFlag(self: Ref<number>, flag: number) {
|
||||
flag = 1 << flag;
|
||||
self.value &= ~flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转二进制数字中的所有位(将1变为0,将0变为1)
|
||||
* @param self 二进制数字的引用
|
||||
*/
|
||||
public static invertFlags(self: Ref<number>) {
|
||||
self.value = ~self.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回二进制数字的字符串表示形式(以二进制形式)
|
||||
* @param self 二进制数字
|
||||
* @param leftPadWidth 返回的字符串的最小宽度(在左侧填充0)
|
||||
* @returns 二进制数字的字符串表示形式
|
||||
*/
|
||||
public static binaryStringRepresentation(
|
||||
self: number,
|
||||
leftPadWidth = 10
|
||||
): string {
|
||||
let str = self.toString(2);
|
||||
while (str.length < (leftPadWidth || 2)) {
|
||||
str = "0" + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
-940
File diff suppressed because it is too large
Load Diff
@@ -1,153 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 代表右手4x4浮点矩阵,可以存储平移、比例和旋转信息
|
||||
*/
|
||||
export class Matrix {
|
||||
private static identity = new Matrix(1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1);
|
||||
|
||||
public static get Identity() {
|
||||
return this.identity;
|
||||
}
|
||||
|
||||
public m11: number;
|
||||
public m12: number;
|
||||
public m13: number;
|
||||
public m14: number;
|
||||
public m21: number;
|
||||
public m22: number;
|
||||
public m23: number;
|
||||
public m24: number;
|
||||
public m31: number;
|
||||
public m32: number;
|
||||
public m33: number;
|
||||
public m34: number;
|
||||
public m41: number;
|
||||
public m42: number;
|
||||
public m43: number;
|
||||
public m44: number;
|
||||
|
||||
constructor(m11?, m12?, m13?, m14?, m21?, m22?, m23?, m24?, m31?,
|
||||
m32?, m33?, m34?, m41?, m42?, m43?, m44?) {
|
||||
this.m11 = m11;
|
||||
this.m12 = m12;
|
||||
this.m13 = m13;
|
||||
this.m14 = m14;
|
||||
this.m21 = m21;
|
||||
this.m22 = m22;
|
||||
this.m23 = m23;
|
||||
this.m24 = m24;
|
||||
this.m31 = m31;
|
||||
this.m32 = m32;
|
||||
this.m33 = m33;
|
||||
this.m34 = m34;
|
||||
this.m41 = m41;
|
||||
this.m42 = m42;
|
||||
this.m43 = m43;
|
||||
this.m44 = m44;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为自定义的正交视图创建一个新的投影矩阵
|
||||
* @param left
|
||||
* @param right
|
||||
* @param top
|
||||
* @param zFarPlane
|
||||
* @param result
|
||||
*/
|
||||
public static createOrthographicOffCenter(left: number, right: number, bottom: number, top: number, zNearPlane: number, zFarPlane: number, result: Matrix = new Matrix()) {
|
||||
result.m11 = 2 / (right - left);
|
||||
result.m12 = 0;
|
||||
result.m13 = 0;
|
||||
result.m14 = 0;
|
||||
result.m21 = 0;
|
||||
result.m22 = 2 / (top - bottom);
|
||||
result.m23 = 0;
|
||||
result.m24 = 0;
|
||||
result.m31 = 0;
|
||||
result.m32 = 0;
|
||||
result.m33 = 1 / (zNearPlane - zFarPlane);
|
||||
result.m34 = 0;
|
||||
result.m41 = (left + right) / (left - right);
|
||||
result.m42 = (top + bottom) / (bottom - top);
|
||||
result.m43 = zNearPlane / (zNearPlane - zFarPlane);
|
||||
result.m44 = 1;
|
||||
}
|
||||
|
||||
public static createTranslation(position: Vector2, result: Matrix)
|
||||
{
|
||||
result.m11 = 1;
|
||||
result.m12 = 0;
|
||||
result.m13 = 0;
|
||||
result.m14 = 0;
|
||||
result.m21 = 0;
|
||||
result.m22 = 1;
|
||||
result.m23 = 0;
|
||||
result.m24 = 0;
|
||||
result.m31 = 0;
|
||||
result.m32 = 0;
|
||||
result.m33 = 1;
|
||||
result.m34 = 0;
|
||||
result.m41 = position.x;
|
||||
result.m42 = position.y;
|
||||
result.m43 = 0;
|
||||
result.m44 = 1;
|
||||
}
|
||||
|
||||
public static createRotationZ(radians: number, result: Matrix)
|
||||
{
|
||||
result = Matrix.Identity;
|
||||
|
||||
var val1 = Math.cos(radians);
|
||||
var val2 = Math.sin(radians);
|
||||
|
||||
result.m11 = val1;
|
||||
result.m12 = val2;
|
||||
result.m21 = -val2;
|
||||
result.m22 = val1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的矩阵,其中包含两个矩阵的乘法。
|
||||
* @param matrix1
|
||||
* @param matrix2
|
||||
* @param result
|
||||
*/
|
||||
public static multiply(matrix1: Matrix, matrix2: Matrix, result: Matrix = new Matrix()) {
|
||||
let m11 = (((matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21)) + (matrix1.m13 * matrix2.m31)) + (matrix1.m14 * matrix2.m41);
|
||||
let m12 = (((matrix1.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22)) + (matrix1.m13 * matrix2.m32)) + (matrix1.m14 * matrix2.m42);
|
||||
let m13 = (((matrix1.m11 * matrix2.m13) + (matrix1.m12 * matrix2.m23)) + (matrix1.m13 * matrix2.m33)) + (matrix1.m14 * matrix2.m43);
|
||||
let m14 = (((matrix1.m11 * matrix2.m14) + (matrix1.m12 * matrix2.m24)) + (matrix1.m13 * matrix2.m34)) + (matrix1.m14 * matrix2.m44);
|
||||
let m21 = (((matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21)) + (matrix1.m23 * matrix2.m31)) + (matrix1.m24 * matrix2.m41);
|
||||
let m22 = (((matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22)) + (matrix1.m23 * matrix2.m32)) + (matrix1.m24 * matrix2.m42);
|
||||
let m23 = (((matrix1.m21 * matrix2.m13) + (matrix1.m22 * matrix2.m23)) + (matrix1.m23 * matrix2.m33)) + (matrix1.m24 * matrix2.m43);
|
||||
let m24 = (((matrix1.m21 * matrix2.m14) + (matrix1.m22 * matrix2.m24)) + (matrix1.m23 * matrix2.m34)) + (matrix1.m24 * matrix2.m44);
|
||||
let m31 = (((matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21)) + (matrix1.m33 * matrix2.m31)) + (matrix1.m34 * matrix2.m41);
|
||||
let m32 = (((matrix1.m31 * matrix2.m12) + (matrix1.m32 * matrix2.m22)) + (matrix1.m33 * matrix2.m32)) + (matrix1.m34 * matrix2.m42);
|
||||
let m33 = (((matrix1.m31 * matrix2.m13) + (matrix1.m32 * matrix2.m23)) + (matrix1.m33 * matrix2.m33)) + (matrix1.m34 * matrix2.m43);
|
||||
let m34 = (((matrix1.m31 * matrix2.m14) + (matrix1.m32 * matrix2.m24)) + (matrix1.m33 * matrix2.m34)) + (matrix1.m34 * matrix2.m44);
|
||||
let m41 = (((matrix1.m41 * matrix2.m11) + (matrix1.m42 * matrix2.m21)) + (matrix1.m43 * matrix2.m31)) + (matrix1.m44 * matrix2.m41);
|
||||
let m42 = (((matrix1.m41 * matrix2.m12) + (matrix1.m42 * matrix2.m22)) + (matrix1.m43 * matrix2.m32)) + (matrix1.m44 * matrix2.m42);
|
||||
let m43 = (((matrix1.m41 * matrix2.m13) + (matrix1.m42 * matrix2.m23)) + (matrix1.m43 * matrix2.m33)) + (matrix1.m44 * matrix2.m43);
|
||||
let m44 = (((matrix1.m41 * matrix2.m14) + (matrix1.m42 * matrix2.m24)) + (matrix1.m43 * matrix2.m34)) + (matrix1.m44 * matrix2.m44);
|
||||
result.m11 = m11;
|
||||
result.m12 = m12;
|
||||
result.m13 = m13;
|
||||
result.m14 = m14;
|
||||
result.m21 = m21;
|
||||
result.m22 = m22;
|
||||
result.m23 = m23;
|
||||
result.m24 = m24;
|
||||
result.m31 = m31;
|
||||
result.m32 = m32;
|
||||
result.m33 = m33;
|
||||
result.m34 = m34;
|
||||
result.m41 = m41;
|
||||
result.m42 = m42;
|
||||
result.m43 = m43;
|
||||
result.m44 = m44;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 表示右手3 * 3的浮点矩阵,可以存储平移、缩放和旋转信息。
|
||||
*/
|
||||
export class Matrix2D implements IEquatable<Matrix2D> {
|
||||
public m11: number = 0; // x 缩放
|
||||
public m12: number = 0;
|
||||
|
||||
public m21: number = 0;
|
||||
public m22: number = 0;
|
||||
|
||||
public m31: number = 0;
|
||||
public m32: number = 0;
|
||||
|
||||
/**
|
||||
* 返回标识矩阵
|
||||
*/
|
||||
public static get identity(): Matrix2D {
|
||||
return new Matrix2D().setIdentity();
|
||||
}
|
||||
|
||||
public setIdentity(): Matrix2D {
|
||||
return this.setValues(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
public setValues(
|
||||
m11: number,
|
||||
m12: number,
|
||||
m21: number,
|
||||
m22: number,
|
||||
m31: number,
|
||||
m32: number
|
||||
): Matrix2D {
|
||||
this.m11 = m11;
|
||||
this.m12 = m12;
|
||||
this.m21 = m21;
|
||||
this.m22 = m22;
|
||||
this.m31 = m31;
|
||||
this.m32 = m32;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 储存在该矩阵中的位置
|
||||
*/
|
||||
public get translation(): Vector2 {
|
||||
return new Vector2(this.m31, this.m32);
|
||||
}
|
||||
|
||||
public set translation(value: Vector2) {
|
||||
this.m31 = value.x;
|
||||
this.m32 = value.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以弧度为单位的旋转,存储在这个矩阵中
|
||||
*/
|
||||
public get rotation(): number {
|
||||
return Math.atan2(this.m21, this.m11);
|
||||
}
|
||||
|
||||
public set rotation(value: number) {
|
||||
let val1 = Math.cos(value);
|
||||
let val2 = Math.sin(value);
|
||||
|
||||
this.m11 = val1;
|
||||
this.m12 = val2;
|
||||
this.m21 = -val2;
|
||||
this.m22 = val1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 矩阵中存储的旋转度数
|
||||
*/
|
||||
public get rotationDegrees() {
|
||||
return MathHelper.toDegrees(this.rotation);
|
||||
}
|
||||
|
||||
public set rotationDegrees(value: number) {
|
||||
this.rotation = MathHelper.toRadians(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 储存在这个矩阵中的缩放
|
||||
*/
|
||||
public get scale(): Vector2 {
|
||||
return new Vector2(this.m11, this.m22);
|
||||
}
|
||||
|
||||
public set scale(value: Vector2) {
|
||||
this.m11 = value.x;
|
||||
this.m22 = value.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的围绕Z轴的旋转矩阵2D
|
||||
* @param radians
|
||||
*/
|
||||
public static createRotation(radians: number, result: Matrix2D) {
|
||||
result.setIdentity();
|
||||
const val1 = Math.cos(radians);
|
||||
const val2 = Math.sin(radians);
|
||||
result.m11 = val1;
|
||||
result.m12 = val2;
|
||||
result.m21 = val2 * -1;
|
||||
result.m22 = val1;
|
||||
}
|
||||
|
||||
public static createRotationOut(radians: number, result: Matrix2D) {
|
||||
let val1 = Math.cos(radians);
|
||||
let val2 = Math.sin(radians);
|
||||
|
||||
result.m11 = val1;
|
||||
result.m12 = val2;
|
||||
result.m21 = -val2;
|
||||
result.m22 = val1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的缩放矩阵2D
|
||||
* @param xScale
|
||||
* @param yScale
|
||||
*/
|
||||
public static createScale(xScale: number, yScale: number, result: Matrix2D) {
|
||||
result.m11 = xScale;
|
||||
result.m12 = 0;
|
||||
result.m21 = 0;
|
||||
result.m22 = yScale;
|
||||
result.m31 = 0;
|
||||
result.m32 = 0;
|
||||
}
|
||||
|
||||
public static createScaleOut(xScale: number, yScale: number, result: Matrix2D) {
|
||||
result.m11 = xScale;
|
||||
result.m12 = 0;
|
||||
|
||||
result.m21 = 0;
|
||||
result.m22 = yScale;
|
||||
|
||||
result.m31 = 0;
|
||||
result.m32 = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的平移矩阵2D
|
||||
* @param xPosition
|
||||
* @param yPosition
|
||||
*/
|
||||
public static createTranslation(xPosition: number, yPosition: number, result: Matrix2D) {
|
||||
result.m11 = 1;
|
||||
result.m12 = 0;
|
||||
result.m21 = 0;
|
||||
result.m22 = 1;
|
||||
result.m31 = xPosition;
|
||||
result.m32 = yPosition;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static createTranslationOut(position: Vector2, result: Matrix2D) {
|
||||
result.m11 = 1;
|
||||
result.m12 = 0;
|
||||
|
||||
result.m21 = 0;
|
||||
result.m22 = 1;
|
||||
|
||||
result.m31 = position.x;
|
||||
result.m32 = position.y;
|
||||
}
|
||||
|
||||
public static invert(matrix: Matrix2D) {
|
||||
let det = 1 / matrix.determinant();
|
||||
|
||||
let result = this.identity;
|
||||
result.m11 = matrix.m22 * det;
|
||||
result.m12 = -matrix.m12 * det;
|
||||
|
||||
result.m21 = -matrix.m21 * det;
|
||||
result.m22 = matrix.m11 * det;
|
||||
|
||||
result.m31 = (matrix.m32 * matrix.m21 - matrix.m31 * matrix.m22) * det;
|
||||
result.m32 = -(matrix.m32 * matrix.m11 - matrix.m31 * matrix.m12) * det;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的matrix, 它包含两个矩阵的和。
|
||||
* @param matrix
|
||||
*/
|
||||
public add(matrix: Matrix2D): Matrix2D {
|
||||
this.m11 += matrix.m11;
|
||||
this.m12 += matrix.m12;
|
||||
|
||||
this.m21 += matrix.m21;
|
||||
this.m22 += matrix.m22;
|
||||
|
||||
this.m31 += matrix.m31;
|
||||
this.m32 += matrix.m32;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public substract(matrix: Matrix2D): Matrix2D {
|
||||
this.m11 -= matrix.m11;
|
||||
this.m12 -= matrix.m12;
|
||||
|
||||
this.m21 -= matrix.m21;
|
||||
this.m22 -= matrix.m22;
|
||||
|
||||
this.m31 -= matrix.m31;
|
||||
this.m32 -= matrix.m32;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public divide(matrix: Matrix2D): Matrix2D {
|
||||
this.m11 /= matrix.m11;
|
||||
this.m12 /= matrix.m12;
|
||||
|
||||
this.m21 /= matrix.m21;
|
||||
this.m22 /= matrix.m22;
|
||||
|
||||
this.m31 /= matrix.m31;
|
||||
this.m32 /= matrix.m32;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public multiply(matrix: Matrix2D): Matrix2D {
|
||||
let m11 = (this.m11 * matrix.m11) + (this.m12 * matrix.m21);
|
||||
let m12 = (this.m11 * matrix.m12) + (this.m12 * matrix.m22);
|
||||
|
||||
let m21 = (this.m21 * matrix.m11) + (this.m22 * matrix.m21);
|
||||
let m22 = (this.m21 * matrix.m12) + (this.m22 * matrix.m22);
|
||||
|
||||
let m31 = (this.m31 * matrix.m11) + (this.m32 * matrix.m21) + matrix.m31;
|
||||
let m32 = (this.m31 * matrix.m12) + (this.m32 * matrix.m22) + matrix.m32;
|
||||
|
||||
this.m11 = m11;
|
||||
this.m12 = m12;
|
||||
|
||||
this.m21 = m21;
|
||||
this.m22 = m22;
|
||||
|
||||
this.m31 = m31;
|
||||
this.m32 = m32;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static multiply(matrix1: Matrix2D, matrix2: Matrix2D, result: Matrix2D) {
|
||||
const m11 = (matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21);
|
||||
const m12 = (matrix1.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22);
|
||||
|
||||
const m21 = (matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21);
|
||||
const m22 = (matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22);
|
||||
|
||||
const m31 = (matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21) + matrix2.m31;
|
||||
const m32 = (matrix1.m31 * matrix2.m12) + (matrix1.m32 * matrix2.m22) + matrix2.m32;
|
||||
|
||||
result.m11 = m11;
|
||||
result.m12 = m12;
|
||||
|
||||
result.m21 = m21;
|
||||
result.m22 = m22;
|
||||
|
||||
result.m31 = m31;
|
||||
result.m32 = m32;
|
||||
}
|
||||
|
||||
public determinant() {
|
||||
return this.m11 * this.m22 - this.m12 * this.m21;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Matrix2D,包含指定矩阵中的线性插值。
|
||||
* @param matrix1
|
||||
* @param matrix2
|
||||
* @param amount
|
||||
*/
|
||||
public static lerp(matrix1: Matrix2D, matrix2: Matrix2D, amount: number) {
|
||||
matrix1.m11 = matrix1.m11 + ((matrix2.m11 - matrix1.m11) * amount);
|
||||
matrix1.m12 = matrix1.m12 + ((matrix2.m12 - matrix1.m12) * amount);
|
||||
|
||||
matrix1.m21 = matrix1.m21 + ((matrix2.m21 - matrix1.m21) * amount);
|
||||
matrix1.m22 = matrix1.m22 + ((matrix2.m22 - matrix1.m22) * amount);
|
||||
|
||||
matrix1.m31 = matrix1.m31 + ((matrix2.m31 - matrix1.m31) * amount);
|
||||
matrix1.m32 = matrix1.m32 + ((matrix2.m32 - matrix1.m32) * amount);
|
||||
return matrix1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换矩阵的行和列
|
||||
* @param matrix
|
||||
*/
|
||||
public static transpose(matrix: Matrix2D) {
|
||||
let ret: Matrix2D = this.identity;
|
||||
ret.m11 = matrix.m11;
|
||||
ret.m12 = matrix.m21;
|
||||
|
||||
ret.m21 = matrix.m12;
|
||||
ret.m22 = matrix.m22;
|
||||
|
||||
ret.m31 = 0;
|
||||
ret.m32 = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public mutiplyTranslation(x: number, y: number) {
|
||||
let trans = new Matrix2D();
|
||||
Matrix2D.createTranslation(x, y, trans);
|
||||
return MatrixHelper.multiply(this, trans);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较当前实例是否等于指定的Matrix2D
|
||||
* @param other
|
||||
*/
|
||||
public equals(other: Matrix2D) {
|
||||
return this == other;
|
||||
}
|
||||
|
||||
public static toMatrix(mat: Matrix2D) {
|
||||
let matrix = new Matrix();
|
||||
matrix.m11 = mat.m11;
|
||||
matrix.m12 = mat.m12;
|
||||
matrix.m13 = 0;
|
||||
matrix.m14 = 0;
|
||||
matrix.m21 = mat.m21;
|
||||
matrix.m22 = mat.m22;
|
||||
matrix.m23 = 0;
|
||||
matrix.m24 = 0;
|
||||
matrix.m31 = 0;
|
||||
matrix.m32 = 0;
|
||||
matrix.m33 = 1;
|
||||
matrix.m34 = 0;
|
||||
matrix.m41 = mat.m31;
|
||||
matrix.m42 = mat.m32;
|
||||
matrix.m43 = 0;
|
||||
matrix.m44 = 1;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `{m11:${this.m11} m12:${this.m12} m21:${this.m21} m22:${this.m22} m31:${this.m31} m32:${this.m32}}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
module es {
|
||||
export class MatrixHelper {
|
||||
/**
|
||||
* 该静态方法用于计算两个 Matrix2D 对象的和。
|
||||
* @param {Matrix2D} matrix1 - 加数矩阵。
|
||||
* @param {Matrix2D} matrix2 - 加数矩阵。
|
||||
* @returns {Matrix2D} - 计算结果的 Matrix2D 对象。
|
||||
*/
|
||||
public static add(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
|
||||
// 创建一个新的 Matrix2D 对象以存储计算结果。
|
||||
const result = new Matrix2D();
|
||||
|
||||
// 计算两个矩阵的和。
|
||||
result.m11 = matrix1.m11 + matrix2.m11;
|
||||
result.m12 = matrix1.m12 + matrix2.m12;
|
||||
result.m21 = matrix1.m21 + matrix2.m21;
|
||||
result.m22 = matrix1.m22 + matrix2.m22;
|
||||
result.m31 = matrix1.m31 + matrix2.m31;
|
||||
result.m32 = matrix1.m32 + matrix2.m32;
|
||||
|
||||
// 返回计算结果的 Matrix2D 对象。
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该静态方法用于计算两个 Matrix2D 对象的商。
|
||||
* @param {Matrix2D} matrix1 - 被除数矩阵。
|
||||
* @param {Matrix2D} matrix2 - 除数矩阵。
|
||||
* @returns {Matrix2D} - 计算结果的 Matrix2D 对象。
|
||||
*/
|
||||
public static divide(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
|
||||
// 创建一个新的 Matrix2D 对象以存储计算结果。
|
||||
const result = new Matrix2D();
|
||||
|
||||
// 计算两个矩阵的商。
|
||||
result.m11 = matrix1.m11 / matrix2.m11;
|
||||
result.m12 = matrix1.m12 / matrix2.m12;
|
||||
result.m21 = matrix1.m21 / matrix2.m21;
|
||||
result.m22 = matrix1.m22 / matrix2.m22;
|
||||
result.m31 = matrix1.m31 / matrix2.m31;
|
||||
result.m32 = matrix1.m32 / matrix2.m32;
|
||||
|
||||
// 返回计算结果的 Matrix2D 对象。
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该静态方法用于计算两个 Matrix2D 对象或一个 Matrix2D 对象和一个数字的乘积。
|
||||
* @param {Matrix2D} matrix1 - 第一个矩阵。
|
||||
* @param {Matrix2D | number} matrix2 - 第二个矩阵或一个数字。
|
||||
* @returns {Matrix2D} - 计算结果的 Matrix2D 对象。
|
||||
*/
|
||||
public static multiply(matrix1: Matrix2D, matrix2: Matrix2D | number): Matrix2D {
|
||||
// 创建一个新的 Matrix2D 对象以存储计算结果。
|
||||
const result = new Matrix2D();
|
||||
|
||||
// 根据第二个参数的类型执行不同的计算。
|
||||
if (matrix2 instanceof Matrix2D) {
|
||||
// 执行矩阵乘法。
|
||||
result.m11 = matrix1.m11 * matrix2.m11 + matrix1.m12 * matrix2.m21;
|
||||
result.m12 = matrix1.m11 * matrix2.m12 + matrix1.m12 * matrix2.m22;
|
||||
result.m21 = matrix1.m21 * matrix2.m11 + matrix1.m22 * matrix2.m21;
|
||||
result.m22 = matrix1.m21 * matrix2.m12 + matrix1.m22 * matrix2.m22;
|
||||
result.m31 = matrix1.m31 * matrix2.m11 + matrix1.m32 * matrix2.m21 + matrix2.m31;
|
||||
result.m32 = matrix1.m31 * matrix2.m12 + matrix1.m32 * matrix2.m22 + matrix2.m32;
|
||||
} else {
|
||||
// 执行矩阵和标量的乘法。
|
||||
result.m11 = matrix1.m11 * matrix2;
|
||||
result.m12 = matrix1.m12 * matrix2;
|
||||
result.m21 = matrix1.m21 * matrix2;
|
||||
result.m22 = matrix1.m22 * matrix2;
|
||||
result.m31 = matrix1.m31 * matrix2;
|
||||
result.m32 = matrix1.m32 * matrix2;
|
||||
}
|
||||
|
||||
// 返回计算结果的 Matrix2D 对象。
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该静态方法用于计算两个 Matrix2D 对象的差。
|
||||
* @param {Matrix2D} matrix1 - 第一个矩阵。
|
||||
* @param {Matrix2D} matrix2 - 第二个矩阵。
|
||||
* @returns {Matrix2D} - 计算结果的 Matrix2D 对象。
|
||||
*/
|
||||
public static subtract(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
|
||||
// 创建一个新的 Matrix2D 对象以存储计算结果。
|
||||
const result = new Matrix2D();
|
||||
|
||||
// 计算两个矩阵的差。
|
||||
result.m11 = matrix1.m11 - matrix2.m11;
|
||||
result.m12 = matrix1.m12 - matrix2.m12;
|
||||
result.m21 = matrix1.m21 - matrix2.m21;
|
||||
result.m22 = matrix1.m22 - matrix2.m22;
|
||||
result.m31 = matrix1.m31 - matrix2.m31;
|
||||
result.m32 = matrix1.m32 - matrix2.m32;
|
||||
|
||||
// 返回计算结果的 Matrix2D 对象。
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
-567
@@ -1,570 +1,219 @@
|
||||
module es {
|
||||
export class Rectangle implements IEquatable<Rectangle> {
|
||||
/**
|
||||
* 该矩形的左上角的x坐标
|
||||
*/
|
||||
public x: number = 0;
|
||||
|
||||
/**
|
||||
* 该矩形的左上角的y坐标
|
||||
*/
|
||||
public y: number = 0;
|
||||
|
||||
/**
|
||||
* 该矩形的宽度
|
||||
*/
|
||||
public width: number = 0;
|
||||
|
||||
/**
|
||||
* 该矩形的高度
|
||||
*/
|
||||
public height: number = 0;
|
||||
|
||||
/**
|
||||
* 返回X=0, Y=0, Width=0, Height=0的矩形
|
||||
*/
|
||||
public static get empty(): Rectangle {
|
||||
return new Rectangle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个Number.Min/Max值的矩形
|
||||
*/
|
||||
public static get maxRect(): Rectangle {
|
||||
return new Rectangle(Number.MIN_VALUE / 2, Number.MIN_VALUE / 2, Number.MAX_VALUE, Number.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回此矩形左边缘的X坐标
|
||||
*/
|
||||
public get left(): number {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回此矩形右边缘的X坐标
|
||||
*/
|
||||
public get right(): number {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回此矩形顶边的y坐标
|
||||
*/
|
||||
public get top(): number {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回此矩形底边的y坐标
|
||||
*/
|
||||
public get bottom(): number {
|
||||
return this.y + this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形的最大点,即右下角
|
||||
*/
|
||||
public get max() {
|
||||
return new Vector2(this.right, this.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个矩形的宽和高是否为0,位置是否为(0,0)
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
return ((((this.width == 0) && (this.height == 0)) && (this.x == 0)) && (this.y == 0));
|
||||
}
|
||||
|
||||
/** 这个矩形的左上角坐标 */
|
||||
public get location() {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
public set location(value: Vector2) {
|
||||
this.x = value.x;
|
||||
this.y = value.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个矩形的宽-高坐标
|
||||
*/
|
||||
public get size() {
|
||||
return new Vector2(this.width, this.height);
|
||||
}
|
||||
|
||||
public set size(value: Vector2) {
|
||||
this.width = value.x;
|
||||
this.height = value.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 位于这个矩形中心的一个点
|
||||
* 如果 "宽度 "或 "高度 "是奇数,则中心点将向下舍入
|
||||
*/
|
||||
public get center() {
|
||||
return new Vector2(this.x + (this.width / 2), this.y + (this.height / 2));
|
||||
}
|
||||
|
||||
// temp 用于计算边界的矩阵
|
||||
public _tempMat: Matrix2D = new Matrix2D();
|
||||
public _transformMat: Matrix2D = new Matrix2D();
|
||||
|
||||
/**
|
||||
* 创建一个新的Rectanglestruct实例,指定位置、宽度和高度。
|
||||
* @param x 创建的矩形的左上角的X坐标
|
||||
* @param y 创建的矩形的左上角的y坐标
|
||||
* @param width 创建的矩形的宽度
|
||||
* @param height 创建的矩形的高度
|
||||
*/
|
||||
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个给定最小/最大点(左上角,右下角)的矩形
|
||||
* @param minX
|
||||
* @param minY
|
||||
* @param maxX
|
||||
* @param maxY
|
||||
*/
|
||||
public static fromMinMax(minX: number, minY: number, maxX: number, maxY: number) {
|
||||
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定多边形的点,计算边界
|
||||
* @param points
|
||||
* @returns 来自多边形的点
|
||||
*/
|
||||
public static rectEncompassingPoints(points: Vector2[]) {
|
||||
// 我们需要求出x/y的最小值/最大值
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
let maxX = Number.NEGATIVE_INFINITY;
|
||||
let maxY = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
let pt = points[i];
|
||||
|
||||
if (pt.x < minX) minX = pt.x;
|
||||
if (pt.x > maxX) maxX = pt.x;
|
||||
|
||||
if (pt.y < minY) minY = pt.y;
|
||||
if (pt.y > maxY) maxY = pt.y;
|
||||
}
|
||||
|
||||
return this.fromMinMax(minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定边缘的位置
|
||||
* @param edge
|
||||
*/
|
||||
public getSide(edge: Edge) {
|
||||
switch (edge) {
|
||||
case Edge.top:
|
||||
return this.top;
|
||||
case Edge.bottom:
|
||||
return this.bottom;
|
||||
case Edge.left:
|
||||
return this.left;
|
||||
case Edge.right:
|
||||
return this.right;
|
||||
default:
|
||||
throw new Error("Argument Out Of Range");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所提供的坐标是否在这个矩形的范围内
|
||||
* @param x 检查封堵点的X坐标
|
||||
* @param y 检查封堵点的Y坐标
|
||||
*/
|
||||
public contains(x: number, y: number): boolean {
|
||||
return ((((this.x <= x) && (x < (this.x + this.width))) &&
|
||||
(this.y <= y)) && (y < (this.y + this.height)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按指定的水平和垂直方向调整此矩形的边缘
|
||||
* @param horizontalAmount 调整左、右边缘的值
|
||||
* @param verticalAmount 调整上、下边缘的值
|
||||
*/
|
||||
public inflate(horizontalAmount: number, verticalAmount: number) {
|
||||
this.x -= horizontalAmount;
|
||||
this.y -= verticalAmount;
|
||||
this.width += horizontalAmount * 2;
|
||||
this.height += verticalAmount * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取其他矩形是否与这个矩形相交
|
||||
* @param value 另一个用于测试的矩形
|
||||
*/
|
||||
public intersects(value: Rectangle) {
|
||||
return value.left < this.right &&
|
||||
this.left < value.right &&
|
||||
value.top < this.bottom &&
|
||||
this.top < value.bottom;
|
||||
}
|
||||
|
||||
public rayIntersects(ray: Ray2D): { intersected: boolean; distance: number } {
|
||||
// 存储相交点和相交距离
|
||||
const res = {intersected: false, distance: 0};
|
||||
let maxValue = Infinity;
|
||||
|
||||
// 计算射线与矩形的相交距离
|
||||
if (Math.abs(ray.direction.x) < 1E-06) {
|
||||
// 如果射线方向的x分量很小,说明它是垂直的,那么它就不会相交
|
||||
if (ray.start.x < this.x || ray.start.x > this.x + this.width) {
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
// 计算射线与x边界的交点,以及在矩形上面和下面的交点
|
||||
const num11 = 1 / ray.direction.x;
|
||||
let num8 = (this.x - ray.start.x) * num11;
|
||||
let num7 = (this.x + this.width - ray.start.x) * num11;
|
||||
if (num8 > num7) {
|
||||
[num7, num8] = [num8, num7];
|
||||
}
|
||||
|
||||
// 将最远的相交距离更新为上下两个交点中更远的那个
|
||||
res.distance = Math.max(num8, res.distance);
|
||||
maxValue = Math.min(num7, maxValue);
|
||||
if (res.distance > maxValue) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算射线与y边界的交点,以及在矩形左边和右边的交点
|
||||
if (Math.abs(ray.direction.y) < 1e-06) {
|
||||
if (ray.start.y < this.y || ray.start.y > this.y + this.height) {
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
const num10 = 1 / ray.direction.y;
|
||||
let num6 = (this.y - ray.start.y) * num10;
|
||||
let num5 = (this.y + this.height - ray.start.y) * num10;
|
||||
if (num6 > num5) {
|
||||
[num5, num6] = [num6, num5];
|
||||
}
|
||||
|
||||
// 将最远的相交距离更新为左右两个交点中更远的那个
|
||||
res.distance = Math.max(num6, res.distance);
|
||||
maxValue = Math.min(num5, maxValue);
|
||||
if (res.distance > maxValue) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果相交了,将标志设为真,并返回相交点
|
||||
res.intersected = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所提供的矩形是否在此矩形的边界内
|
||||
* @param value
|
||||
*/
|
||||
public containsRect(value: Rectangle) {
|
||||
return ((((this.x <= value.x) && (value.x < (this.x + this.width))) &&
|
||||
(this.y <= value.y)) &&
|
||||
(value.y < (this.y + this.height)));
|
||||
}
|
||||
|
||||
public getHalfSize() {
|
||||
return new Vector2(this.width * 0.5, this.height * 0.5);
|
||||
}
|
||||
|
||||
public getClosestPointOnBoundsToOrigin() {
|
||||
let max = this.max;
|
||||
let minDist = Math.abs(this.location.x);
|
||||
let boundsPoint = new Vector2(this.location.x, 0);
|
||||
|
||||
if (Math.abs(max.x) < minDist) {
|
||||
minDist = Math.abs(max.x);
|
||||
boundsPoint.x = max.x;
|
||||
boundsPoint.y = 0;
|
||||
}
|
||||
|
||||
if (Math.abs(max.y) < minDist) {
|
||||
minDist = Math.abs(max.y);
|
||||
boundsPoint.x = 0;
|
||||
boundsPoint.y = max.y;
|
||||
}
|
||||
|
||||
if (Math.abs(this.location.y) < minDist) {
|
||||
minDist = Math.abs(this.location.y);
|
||||
boundsPoint.x = 0;
|
||||
boundsPoint.y = this.location.y;
|
||||
}
|
||||
|
||||
return boundsPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回离给定点最近的点
|
||||
* @param point 矩形上离点最近的点
|
||||
*/
|
||||
public getClosestPointOnRectangleToPoint(point: Vector2) {
|
||||
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
|
||||
let res = es.Vector2.zero;
|
||||
res.x = MathHelper.clamp(point.x, this.left, this.right);
|
||||
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形边界上与给定点最近的点
|
||||
* @param point
|
||||
* @param edgeNormal
|
||||
* @returns 矩形边框上离点最近的点
|
||||
*/
|
||||
public getClosestPointOnRectangleBorderToPoint(point: Vector2, edgeNormal: Out<Vector2>): Vector2 {
|
||||
edgeNormal.value = Vector2.zero;
|
||||
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
|
||||
const res = Vector2.zero;
|
||||
res.x = MathHelper.clamp(point.x, this.left, this.right);
|
||||
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
|
||||
|
||||
// 如果点在矩形内,我们需要将res推到边界上,因为它将在矩形内
|
||||
if (this.contains(res.x, res.y)) {
|
||||
let dl = res.x - this.left;
|
||||
let dr = this.right - res.x;
|
||||
let dt = res.y - this.top;
|
||||
let db = this.bottom - res.y;
|
||||
|
||||
let min = Math.min(dl, dr, dt, db);
|
||||
if (min == dt) {
|
||||
res.y = this.top;
|
||||
edgeNormal.value.y = -1;
|
||||
} else if (min == db) {
|
||||
res.y = this.bottom;
|
||||
edgeNormal.value.y = 1;
|
||||
} else if (min == dl) {
|
||||
res.x = this.left;
|
||||
edgeNormal.value.x = -1;
|
||||
} else {
|
||||
res.x = this.right;
|
||||
edgeNormal.value.x = 1;
|
||||
}
|
||||
} else {
|
||||
if (res.x == this.left) edgeNormal.value.x = -1;
|
||||
if (res.x == this.right) edgeNormal.value.x = 1;
|
||||
if (res.y == this.top) edgeNormal.value.y = -1;
|
||||
if (res.y == this.bottom) edgeNormal.value.y = 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的RectangleF,该RectangleF包含两个其他矩形的重叠区域
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @returns 将两个矩形的重叠区域作为输出参数
|
||||
*/
|
||||
public static intersect(value1: Rectangle, value2: Rectangle) {
|
||||
if (value1.intersects(value2)) {
|
||||
let right_side = Math.min(value1.x + value1.width, value2.x + value2.width);
|
||||
let left_side = Math.max(value1.x, value2.x);
|
||||
let top_side = Math.max(value1.y, value2.y);
|
||||
let bottom_side = Math.min(value1.y + value1.height, value2.y + value2.height);
|
||||
return new Rectangle(left_side, top_side, right_side - left_side, bottom_side - top_side);
|
||||
} else {
|
||||
return new Rectangle(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变这个矩形的位置
|
||||
* @param offsetX 要添加到这个矩形的X坐标
|
||||
* @param offsetY 要添加到这个矩形的y坐标
|
||||
*/
|
||||
public offset(offsetX: number, offsetY: number) {
|
||||
this.x += offsetX;
|
||||
this.y += offsetY;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个完全包含两个其他矩形的新矩形
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static union(value1: Rectangle, value2: Rectangle) {
|
||||
let x = Math.min(value1.x, value2.x);
|
||||
let y = Math.min(value1.y, value2.y);
|
||||
return new Rectangle(x, y,
|
||||
Math.max(value1.right, value2.right) - x,
|
||||
Math.max(value1.bottom, value2.bottom) - y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在矩形重叠的地方创建一个新的矩形
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static overlap(value1: Rectangle, value2: Rectangle): Rectangle {
|
||||
let x = Math.max(value1.x, value2.x, 0);
|
||||
let y = Math.max(value1.y, value2.y, 0);
|
||||
return new Rectangle(x, y,
|
||||
Math.max(Math.min(value1.right, value2.right) - x, 0),
|
||||
Math.max(Math.min(value1.bottom, value2.bottom) - y, 0));
|
||||
}
|
||||
|
||||
public calculateBounds(parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
|
||||
rotation: number, width: number, height: number) {
|
||||
if (rotation == 0) {
|
||||
this.x = Math.trunc(parentPosition.x + position.x - origin.x * scale.x);
|
||||
this.y = Math.trunc(parentPosition.y + position.y - origin.y * scale.y);
|
||||
this.width = Math.trunc(width * scale.x);
|
||||
this.height = Math.trunc(height * scale.y);
|
||||
} else {
|
||||
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
|
||||
let worldPosX = parentPosition.x + position.x;
|
||||
let worldPosY = parentPosition.y + position.y;
|
||||
|
||||
// 考虑到原点,将参考点设置为世界参考
|
||||
Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, this._transformMat);
|
||||
Matrix2D.createScale(scale.x, scale.y, this._tempMat);
|
||||
this._transformMat = this._transformMat.multiply(this._tempMat);
|
||||
Matrix2D.createRotation(rotation, this._tempMat);
|
||||
this._transformMat = this._transformMat.multiply(this._tempMat);
|
||||
Matrix2D.createTranslation(worldPosX, worldPosY, this._tempMat);
|
||||
this._transformMat = this._transformMat.multiply(this._tempMat);
|
||||
|
||||
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
|
||||
let topLeft = new Vector2(worldPosX, worldPosY);
|
||||
let topRight = new Vector2(worldPosX + width, worldPosY);
|
||||
let bottomLeft = new Vector2(worldPosX, worldPosY + height);
|
||||
let bottomRight = new Vector2(worldPosX + width, worldPosY + height);
|
||||
|
||||
Vector2Ext.transformR(topLeft, this._transformMat, topLeft);
|
||||
Vector2Ext.transformR(topRight, this._transformMat, topRight);
|
||||
Vector2Ext.transformR(bottomLeft, this._transformMat, bottomLeft);
|
||||
Vector2Ext.transformR(bottomRight, this._transformMat, bottomRight);
|
||||
|
||||
// 找出最小值和最大值,这样我们就可以计算出我们的边界框。
|
||||
let minX = Math.trunc(Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
|
||||
let maxX = Math.trunc(Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
|
||||
let minY = Math.trunc(Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
|
||||
let maxY = Math.trunc(Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
|
||||
|
||||
this.location = new Vector2(minX, minY);
|
||||
this.width = Math.trunc(maxX - minX);
|
||||
this.height = Math.trunc(maxY - minY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个横跨当前矩形和提供的三角形位置的矩形
|
||||
* @param deltaX
|
||||
* @param deltaY
|
||||
*/
|
||||
public getSweptBroadphaseBounds(deltaX: number, deltaY: number) {
|
||||
let broadphasebox = Rectangle.empty;
|
||||
|
||||
broadphasebox.x = deltaX > 0 ? this.x : this.x + deltaX;
|
||||
broadphasebox.y = deltaY > 0 ? this.y : this.y + deltaY;
|
||||
broadphasebox.width = deltaX > 0 ? deltaX + this.width : this.width - deltaX;
|
||||
broadphasebox.height = deltaY > 0 ? deltaY + this.height : this.height - deltaY;
|
||||
|
||||
return broadphasebox;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果发生碰撞,返回true
|
||||
* moveX和moveY将返回b1为避免碰撞而必须移动的移动量
|
||||
* @param other
|
||||
* @param moveX
|
||||
* @param moveY
|
||||
*/
|
||||
public collisionCheck(other: Rectangle, moveX: Ref<number>, moveY: Ref<number>) {
|
||||
moveX.value = moveY.value = 0;
|
||||
|
||||
let l = other.x - (this.x + this.width);
|
||||
let r = (other.x + other.width) - this.x;
|
||||
let t = (other.y - (this.y + this.height));
|
||||
let b = (other.y + other.height) - this.y;
|
||||
|
||||
// 检验是否有碰撞
|
||||
if (l > 0 || r < 0 || t > 0 || b < 0)
|
||||
return false;
|
||||
|
||||
// 求两边的偏移量
|
||||
moveX.value = Math.abs(l) < r ? l : r;
|
||||
moveY.value = Math.abs(t) < b ? t : b;
|
||||
|
||||
// 只使用最小的偏移量
|
||||
if (Math.abs(moveX.value) < Math.abs(moveY.value))
|
||||
moveY.value = 0;
|
||||
else
|
||||
moveX.value = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个矩形之间有符号的交点深度
|
||||
* @param rectA
|
||||
* @param rectB
|
||||
* @returns 两个相交的矩形之间的重叠量。
|
||||
* 这些深度值可以是负值,取决于矩形/相交的哪些边。
|
||||
* 这允许调用者确定正确的推送对象的方向,以解决碰撞问题。
|
||||
* 如果矩形不相交,则返回Vector2.Zero
|
||||
*/
|
||||
public static getIntersectionDepth(rectA: Rectangle, rectB: Rectangle): Vector2 {
|
||||
// 计算半尺寸
|
||||
let halfWidthA = rectA.width / 2;
|
||||
let halfHeightA = rectA.height / 2;
|
||||
let halfWidthB = rectB.width / 2;
|
||||
let halfHeightB = rectB.height / 2;
|
||||
|
||||
// 计算中心
|
||||
let centerA = new Vector2(rectA.left + halfWidthA, rectA.top + halfHeightA);
|
||||
let centerB = new Vector2(rectB.left + halfWidthB, rectB.top + halfHeightB);
|
||||
|
||||
// 计算当前中心间的距离和最小非相交距离
|
||||
let distanceX = centerA.x - centerB.x;
|
||||
let distanceY = centerA.y - centerB.y;
|
||||
let minDistanceX = halfWidthA + halfWidthB;
|
||||
let minDistanceY = halfHeightA + halfHeightB;
|
||||
|
||||
// 如果我们根本不相交,则返回(0,0)
|
||||
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY)
|
||||
return Vector2.zero;
|
||||
|
||||
// 计算并返回交叉点深度
|
||||
let depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
|
||||
let depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
|
||||
|
||||
return new Vector2(depthX, depthY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较当前实例是否等于指定的矩形
|
||||
* @param other
|
||||
*/
|
||||
public equals(other: Rectangle) {
|
||||
return this === other;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取这个矩形的哈希码
|
||||
*/
|
||||
public getHashCode(): number {
|
||||
return (Math.trunc(this.x) ^ Math.trunc(this.y) ^ Math.trunc(this.width) ^ Math.trunc(this.height));
|
||||
}
|
||||
|
||||
public clone(): Rectangle {
|
||||
return new Rectangle(this.x, this.y, this.width, this.height);
|
||||
import { Vector2 } from './Vector2';
|
||||
|
||||
/**
|
||||
* 矩形类
|
||||
* 表示一个二维矩形区域,提供基本的几何操作
|
||||
*/
|
||||
export class Rectangle {
|
||||
/**
|
||||
* 矩形左上角的X坐标
|
||||
*/
|
||||
public x: number = 0;
|
||||
|
||||
/**
|
||||
* 矩形左上角的Y坐标
|
||||
*/
|
||||
public y: number = 0;
|
||||
|
||||
/**
|
||||
* 矩形的宽度
|
||||
*/
|
||||
public width: number = 0;
|
||||
|
||||
/**
|
||||
* 矩形的高度
|
||||
*/
|
||||
public height: number = 0;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param x 左上角X坐标
|
||||
* @param y 左上角Y坐标
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回空矩形实例
|
||||
*/
|
||||
public static get empty(): Rectangle {
|
||||
return new Rectangle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形左边界的X坐标
|
||||
*/
|
||||
public get left(): number {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形右边界的X坐标
|
||||
*/
|
||||
public get right(): number {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形上边界的Y坐标
|
||||
*/
|
||||
public get top(): number {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形下边界的Y坐标
|
||||
*/
|
||||
public get bottom(): number {
|
||||
return this.y + this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形的中心点坐标
|
||||
*/
|
||||
public get center(): Vector2 {
|
||||
return new Vector2(this.x + this.width / 2, this.y + this.height / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或设置矩形的位置(左上角坐标)
|
||||
*/
|
||||
public get location(): Vector2 {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
public set location(value: Vector2) {
|
||||
this.x = value.x;
|
||||
this.y = value.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或设置矩形的尺寸
|
||||
*/
|
||||
public get size(): Vector2 {
|
||||
return new Vector2(this.width, this.height);
|
||||
}
|
||||
|
||||
public set size(value: Vector2) {
|
||||
this.width = value.x;
|
||||
this.height = value.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定点是否在矩形内
|
||||
* @param x 点的X坐标
|
||||
* @param y 点的Y坐标
|
||||
* @returns 如果点在矩形内返回true,否则返回false
|
||||
*/
|
||||
public contains(x: number, y: number): boolean;
|
||||
/**
|
||||
* 检查指定点是否在矩形内
|
||||
* @param point 要检查的点
|
||||
* @returns 如果点在矩形内返回true,否则返回false
|
||||
*/
|
||||
public contains(point: Vector2): boolean;
|
||||
public contains(xOrPoint: number | Vector2, y?: number): boolean {
|
||||
if (typeof xOrPoint === 'number') {
|
||||
return xOrPoint >= this.x && xOrPoint < this.right && y! >= this.y && y! < this.bottom;
|
||||
} else {
|
||||
return this.contains(xOrPoint.x, xOrPoint.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前矩形是否与另一个矩形相交
|
||||
* @param other 要检查的矩形
|
||||
* @returns 如果两个矩形相交返回true,否则返回false
|
||||
*/
|
||||
public intersects(other: Rectangle): boolean {
|
||||
return other.left < this.right && this.left < other.right &&
|
||||
other.top < this.bottom && this.top < other.bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前矩形与另一个矩形的交集
|
||||
* @param other 要计算交集的矩形
|
||||
* @returns 交集矩形,如果不相交则返回空矩形
|
||||
*/
|
||||
public intersection(other: Rectangle): Rectangle {
|
||||
const x1 = Math.max(this.x, other.x);
|
||||
const x2 = Math.min(this.right, other.right);
|
||||
const y1 = Math.max(this.y, other.y);
|
||||
const y2 = Math.min(this.bottom, other.bottom);
|
||||
|
||||
if (x2 >= x1 && y2 >= y1) {
|
||||
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
return Rectangle.empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算包含两个矩形的最小矩形
|
||||
* @param rect1 第一个矩形
|
||||
* @param rect2 第二个矩形
|
||||
* @returns 包含两个矩形的最小矩形
|
||||
*/
|
||||
public static union(rect1: Rectangle, rect2: Rectangle): Rectangle {
|
||||
const x = Math.min(rect1.x, rect2.x);
|
||||
const y = Math.min(rect1.y, rect2.y);
|
||||
const right = Math.max(rect1.right, rect2.right);
|
||||
const bottom = Math.max(rect1.bottom, rect2.bottom);
|
||||
|
||||
return new Rectangle(x, y, right - x, bottom - y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按指定偏移量移动矩形
|
||||
* @param offsetX X轴偏移量
|
||||
* @param offsetY Y轴偏移量
|
||||
*/
|
||||
public offset(offsetX: number, offsetY: number): void {
|
||||
this.x += offsetX;
|
||||
this.y += offsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按指定量扩展矩形
|
||||
* @param horizontalAmount 水平扩展量
|
||||
* @param verticalAmount 垂直扩展量
|
||||
*/
|
||||
public inflate(horizontalAmount: number, verticalAmount: number): void {
|
||||
this.x -= horizontalAmount;
|
||||
this.y -= verticalAmount;
|
||||
this.width += horizontalAmount * 2;
|
||||
this.height += verticalAmount * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查矩形是否为空(所有值都为0)
|
||||
* @returns 如果矩形为空返回true,否则返回false
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
return this.width === 0 && this.height === 0 && this.x === 0 && this.y === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个矩形是否相等
|
||||
* @param other 要比较的矩形
|
||||
* @returns 如果两个矩形相等返回true,否则返回false
|
||||
*/
|
||||
public equals(other: Rectangle): boolean {
|
||||
return this.x === other.x && this.y === other.y &&
|
||||
this.width === other.width && this.height === other.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建当前矩形的副本
|
||||
* @returns 矩形的副本
|
||||
*/
|
||||
public clone(): Rectangle {
|
||||
return new Rectangle(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 该类用于存储具有亚像素分辨率的浮点数。
|
||||
*/
|
||||
export class SubpixelFloat {
|
||||
/**
|
||||
* 存储 SubpixelFloat 值的浮点余数。
|
||||
*/
|
||||
public remainder: number = 0;
|
||||
|
||||
/**
|
||||
* 通过将给定数量的像素添加到余数中来更新 SubpixelFloat 值。
|
||||
* 返回更新后的整数部分,余数表示当前值中包含的亚像素部分。
|
||||
* @param {number} amount - 要添加到余数中的像素数。
|
||||
* @returns {number} 更新后的整数部分。
|
||||
*/
|
||||
public update(amount: number): number {
|
||||
// 将给定的像素数量添加到余数中
|
||||
this.remainder += amount;
|
||||
|
||||
// 检查余数是否超过一个像素的大小
|
||||
let motion = Math.trunc(this.remainder);
|
||||
this.remainder -= motion;
|
||||
|
||||
// 返回整数部分作为更新后的值
|
||||
return motion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SubpixelFloat 值重置为零。
|
||||
*/
|
||||
public reset(): void {
|
||||
this.remainder = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 该类用于存储具有亚像素分辨率的二维向量。
|
||||
*/
|
||||
export class SubpixelVector2 {
|
||||
/**
|
||||
* 用于存储 x 坐标的 SubpixelFloat 对象。
|
||||
*/
|
||||
public _x: SubpixelFloat = new SubpixelFloat();
|
||||
|
||||
/**
|
||||
* 用于存储 y 坐标的 SubpixelFloat 对象。
|
||||
*/
|
||||
public _y: SubpixelFloat = new SubpixelFloat();
|
||||
|
||||
/**
|
||||
* 通过将给定数量的像素添加到余数中来更新 SubpixelVector2 值。
|
||||
* @param {Vector2} amount - 要添加到余数中的像素向量。
|
||||
*/
|
||||
public update(amount: Vector2): void {
|
||||
// 更新 x 和 y 坐标
|
||||
amount.x = this._x.update(amount.x);
|
||||
amount.y = this._y.update(amount.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SubpixelVector2 值的余数重置为零。
|
||||
*/
|
||||
public reset(): void {
|
||||
this._x.reset();
|
||||
this._y.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
+378
-452
@@ -1,471 +1,397 @@
|
||||
module es {
|
||||
/** 2d 向量 */
|
||||
export class Vector2 implements IEquatable<Vector2> {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
/**
|
||||
* 二维向量类
|
||||
* 提供二维向量的基本数学运算
|
||||
*/
|
||||
export class Vector2 {
|
||||
/**
|
||||
* X坐标
|
||||
*/
|
||||
public x: number;
|
||||
|
||||
/**
|
||||
* 从两个值构造一个带有X和Y的二维向量。
|
||||
* @param x 二维空间中的x坐标
|
||||
* @param y 二维空间的y坐标
|
||||
*/
|
||||
constructor(x: number = 0, y: number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
/**
|
||||
* Y坐标
|
||||
*/
|
||||
public y: number;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param x X坐标,默认为0
|
||||
* @param y Y坐标,默认为0
|
||||
*/
|
||||
constructor(x: number = 0, y: number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 零向量
|
||||
*/
|
||||
public static get zero(): Vector2 {
|
||||
return new Vector2(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位向量(1, 1)
|
||||
*/
|
||||
public static get one(): Vector2 {
|
||||
return new Vector2(1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位X向量(1, 0)
|
||||
*/
|
||||
public static get unitX(): Vector2 {
|
||||
return new Vector2(1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位Y向量(0, 1)
|
||||
*/
|
||||
public static get unitY(): Vector2 {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向上向量(0, -1)
|
||||
*/
|
||||
public static get up(): Vector2 {
|
||||
return new Vector2(0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向下向量(0, 1)
|
||||
*/
|
||||
public static get down(): Vector2 {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向左向量(-1, 0)
|
||||
*/
|
||||
public static get left(): Vector2 {
|
||||
return new Vector2(-1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向右向量(1, 0)
|
||||
*/
|
||||
public static get right(): Vector2 {
|
||||
return new Vector2(1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取向量长度
|
||||
*/
|
||||
public get length(): number {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取向量长度的平方
|
||||
*/
|
||||
public get lengthSquared(): number {
|
||||
return this.x * this.x + this.y * this.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置向量的值
|
||||
* @param x X坐标
|
||||
* @param y Y坐标
|
||||
*/
|
||||
public set(x: number, y: number): Vector2 {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制另一个向量的值
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public copyFrom(other: Vector2): Vector2 {
|
||||
this.x = other.x;
|
||||
this.y = other.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆向量
|
||||
*/
|
||||
public clone(): Vector2 {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量加法
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public add(other: Vector2): Vector2 {
|
||||
return new Vector2(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量减法
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public subtract(other: Vector2): Vector2 {
|
||||
return new Vector2(this.x - other.x, this.y - other.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量乘法(标量)
|
||||
* @param scalar 标量
|
||||
*/
|
||||
public multiply(scalar: number): Vector2 {
|
||||
return new Vector2(this.x * scalar, this.y * scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量除法(标量)
|
||||
* @param scalar 标量
|
||||
*/
|
||||
public divide(scalar: number): Vector2 {
|
||||
return new Vector2(this.x / scalar, this.y / scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量点积
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public dot(other: Vector2): number {
|
||||
return this.x * other.x + this.y * other.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量叉积(2D中返回标量)
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public cross(other: Vector2): number {
|
||||
return this.x * other.y - this.y * other.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化向量
|
||||
*/
|
||||
public normalize(): Vector2 {
|
||||
const length = this.length;
|
||||
if (length === 0) {
|
||||
return Vector2.zero;
|
||||
}
|
||||
return new Vector2(this.x / length, this.y / length);
|
||||
}
|
||||
|
||||
public static get zero() {
|
||||
return new Vector2(0, 0);
|
||||
}
|
||||
/**
|
||||
* 获取到另一个向量的距离
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public distanceTo(other: Vector2): number {
|
||||
const dx = this.x - other.x;
|
||||
const dy = this.y - other.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
public static get one() {
|
||||
return new Vector2(1, 1);
|
||||
}
|
||||
/**
|
||||
* 获取到另一个向量的距离的平方
|
||||
* @param other 另一个向量
|
||||
*/
|
||||
public distanceSquaredTo(other: Vector2): number {
|
||||
const dx = this.x - other.x;
|
||||
const dy = this.y - other.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
public static get unitX() {
|
||||
return new Vector2(1, 0);
|
||||
}
|
||||
/**
|
||||
* 检查向量是否相等
|
||||
* @param other 另一个向量
|
||||
* @param tolerance 容差
|
||||
*/
|
||||
public equals(other: Vector2, tolerance: number = 0.0001): boolean {
|
||||
return Math.abs(this.x - other.x) < tolerance && Math.abs(this.y - other.y) < tolerance;
|
||||
}
|
||||
|
||||
public static get unitY() {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
/**
|
||||
* 获取向量的角度(弧度)
|
||||
*/
|
||||
public angle(): number {
|
||||
return Math.atan2(this.y, this.x);
|
||||
}
|
||||
|
||||
public static get up() {
|
||||
return new Vector2(0, -1);
|
||||
}
|
||||
/**
|
||||
* 旋转向量
|
||||
* @param radians 旋转角度(弧度)
|
||||
*/
|
||||
public rotate(radians: number): Vector2 {
|
||||
const cos = Math.cos(radians);
|
||||
const sin = Math.sin(radians);
|
||||
return new Vector2(
|
||||
this.x * cos - this.y * sin,
|
||||
this.x * sin + this.y * cos
|
||||
);
|
||||
}
|
||||
|
||||
public static get down() {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
/**
|
||||
* 线性插值
|
||||
* @param other 目标向量
|
||||
* @param t 插值参数(0-1)
|
||||
*/
|
||||
public lerp(other: Vector2, t: number): Vector2 {
|
||||
return new Vector2(
|
||||
this.x + (other.x - this.x) * t,
|
||||
this.y + (other.y - this.y) * t
|
||||
);
|
||||
}
|
||||
|
||||
public static get left() {
|
||||
return new Vector2(-1, 0);
|
||||
}
|
||||
/**
|
||||
* 四舍五入
|
||||
*/
|
||||
public round(): Vector2 {
|
||||
return new Vector2(Math.round(this.x), Math.round(this.y));
|
||||
}
|
||||
|
||||
public static get right() {
|
||||
return new Vector2(1, 0);
|
||||
}
|
||||
/**
|
||||
* 向下取整
|
||||
*/
|
||||
public floor(): Vector2 {
|
||||
return new Vector2(Math.floor(this.x), Math.floor(this.y));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static add(value1: Vector2, value2: Vector2) {
|
||||
let result: Vector2 = Vector2.zero;
|
||||
result.x = value1.x + value2.x;
|
||||
result.y = value1.y + value2.y;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 向上取整
|
||||
*/
|
||||
public ceil(): Vector2 {
|
||||
return new Vector2(Math.ceil(this.x), Math.ceil(this.y));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static divide(value1: Vector2, value2: Vector2) {
|
||||
let result: Vector2 = Vector2.zero;
|
||||
result.x = value1.x / value2.x;
|
||||
result.y = value1.y / value2.y;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 转换为字符串
|
||||
*/
|
||||
public toString(): string {
|
||||
return `(${this.x.toFixed(2)}, ${this.y.toFixed(2)})`;
|
||||
}
|
||||
|
||||
public static divideScaler(value1: Vector2, value2: number) {
|
||||
let result: Vector2 = Vector2.zero;
|
||||
result.x = value1.x / value2;
|
||||
result.y = value1.y / value2;
|
||||
return result;
|
||||
}
|
||||
// 静态方法
|
||||
|
||||
/**
|
||||
* 返回两个向量之间距离的平方
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static sqrDistance(value1: Vector2, value2: Vector2) {
|
||||
return Math.pow(value1.x - value2.x, 2) + Math.pow(value1.y - value2.y, 2);
|
||||
}
|
||||
/**
|
||||
* 向量加法
|
||||
* @param a 向量A
|
||||
* @param b 向量B
|
||||
*/
|
||||
public static add(a: Vector2, b: Vector2): Vector2 {
|
||||
return new Vector2(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的值限制在一个范围内
|
||||
* @param value1
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
public static clamp(value1: Vector2, min: Vector2, max: Vector2) {
|
||||
return new Vector2(MathHelper.clamp(value1.x, min.x, max.x),
|
||||
MathHelper.clamp(value1.y, min.y, max.y));
|
||||
}
|
||||
/**
|
||||
* 向量减法
|
||||
* @param a 向量A
|
||||
* @param b 向量B
|
||||
*/
|
||||
public static subtract(a: Vector2, b: Vector2): Vector2 {
|
||||
return new Vector2(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含指定向量的线性插值
|
||||
* @param value1 第一个向量
|
||||
* @param value2 第二个向量
|
||||
* @param amount 加权值(0.0-1.0之间)
|
||||
* @returns 指定向量的线性插值结果
|
||||
*/
|
||||
public static lerp(value1: Vector2, value2: Vector2, amount: number) {
|
||||
return new Vector2(MathHelper.lerp(value1.x, value2.x, amount), MathHelper.lerp(value1.y, value2.y, amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含指定矢量的线性插值
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @param amount
|
||||
* @returns
|
||||
*/
|
||||
public static lerpPrecise(value1: Vector2, value2: Vector2, amount: number) {
|
||||
return new Vector2(MathHelper.lerpPrecise(value1.x, value2.x, amount),
|
||||
MathHelper.lerpPrecise(value1.y, value2.y, amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,该Vector2包含了通过指定的Matrix进行的二维向量变换。
|
||||
* @param position
|
||||
* @param matrix
|
||||
*/
|
||||
public static transform(position: Vector2, matrix: Matrix2D) {
|
||||
return new Vector2((position.x * matrix.m11) + (position.y * matrix.m21) + matrix.m31,
|
||||
(position.x * matrix.m12) + (position.y * matrix.m22) + matrix.m32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含由指定的Matrix转换的指定法线
|
||||
* @param normal
|
||||
* @param matrix
|
||||
*/
|
||||
public static transformNormal(normal: Vector2, matrix: Matrix) {
|
||||
return new Vector2((normal.x * matrix.m11) + (normal.y * matrix.m21),
|
||||
(normal.x * matrix.m12) + (normal.y * matrix.m22));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量之间的距离
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @returns 两个向量之间的距离
|
||||
*/
|
||||
public static distance(vec1: Vector2, vec2: Vector2): number {
|
||||
return Math.sqrt(Math.pow(vec1.x - vec2.x, 2) + Math.pow(vec1.y - vec2.y, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量之间的角度,单位是度数
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
public static angle(from: Vector2, to: Vector2): number {
|
||||
from = from.normalize();
|
||||
to = to.normalize();
|
||||
return Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个包含指定向量反转的新Vector2
|
||||
* @param value
|
||||
* @returns 矢量反演的结果
|
||||
*/
|
||||
public static negate(value: Vector2) {
|
||||
value.x = -value.x;
|
||||
value.y = -value.y;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量的反射,输入为两个二维向量vector和normal。函数返回一个新的向量,即vector相对于normal的反射
|
||||
* @param vector
|
||||
* @param normal
|
||||
* @returns
|
||||
*/
|
||||
public static reflect(vector: Vector2, normal: Vector2) {
|
||||
let result: Vector2 = es.Vector2.zero;
|
||||
// 计算向量与法线的点积,并将结果乘2
|
||||
let val = 2 * ((vector.x * normal.x) + (vector.y * normal.y));
|
||||
// 计算反射向量
|
||||
result.x = vector.x - (normal.x * val);
|
||||
result.y = vector.y - (normal.y * val);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含指定矢量的三次插值
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @param amount
|
||||
* @returns
|
||||
*/
|
||||
public static smoothStep(value1: Vector2, value2: Vector2, amount: number) {
|
||||
return new Vector2(MathHelper.smoothStep(value1.x, value2.x, amount),
|
||||
MathHelper.smoothStep(value1.y, value2.y, amount));
|
||||
}
|
||||
|
||||
public setTo(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public negate(): Vector2 {
|
||||
return this.scale(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public add(v: Vector2): Vector2 {
|
||||
return new Vector2(this.x + v.x, this.y + v.y);
|
||||
}
|
||||
|
||||
public addEqual(v: Vector2): Vector2 {
|
||||
this.x += v.x;
|
||||
this.y += v.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public divide(value: Vector2): Vector2 {
|
||||
return new Vector2(this.x / value.x, this.y / value.y);
|
||||
}
|
||||
|
||||
public divideScaler(value: number): Vector2 {
|
||||
return new Vector2(this.x / value, this.y / value);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public multiply(value: Vector2): Vector2 {
|
||||
return new Vector2(value.x * this.x, value.y * this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
public multiplyScaler(value: number): Vector2 {
|
||||
this.x *= value;
|
||||
this.y *= value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前Vector2减去一个Vector2
|
||||
* @param value 要减去的Vector2
|
||||
* @returns 当前Vector2
|
||||
*/
|
||||
public sub(value: Vector2) {
|
||||
return new Vector2(this.x - value.x, this.y - value.y);
|
||||
}
|
||||
|
||||
public subEqual(v: Vector2): Vector2 {
|
||||
this.x -= v.x;
|
||||
this.y -= v.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public dot(v: Vector2): number {
|
||||
return this.x * v.x + this.y * v.y;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param size
|
||||
* @returns
|
||||
*/
|
||||
public scale(size: number): Vector2 {
|
||||
return new Vector2(this.x * size, this.y * size);
|
||||
}
|
||||
|
||||
public scaleEqual(size: number): Vector2 {
|
||||
this.x *= size;
|
||||
this.y *= size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public transform(matrix: Matrix2D): Vector2 {
|
||||
return new Vector2(
|
||||
this.x * matrix.m11 + this.y * matrix.m21 + matrix.m31,
|
||||
this.x * matrix.m12 + this.y * matrix.m22 + matrix.m32
|
||||
);
|
||||
}
|
||||
|
||||
public normalize(): Vector2 {
|
||||
const d = this.distance();
|
||||
if (d > 0) {
|
||||
return new Vector2(this.x / d, this.y / d);
|
||||
} else {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将这个Vector2变成一个方向相同的单位向量
|
||||
*/
|
||||
public normalizeEqual(): Vector2 {
|
||||
const d = this.distance();
|
||||
if (d > 0) {
|
||||
this.setTo(this.x / d, this.y / d);
|
||||
return this;
|
||||
} else {
|
||||
this.setTo(0, 1);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public magnitude(): number {
|
||||
return this.distance();
|
||||
}
|
||||
|
||||
public distance(v?: Vector2): number {
|
||||
if (!v) {
|
||||
v = Vector2.zero;
|
||||
}
|
||||
|
||||
return Math.sqrt(Math.pow(this.x - v.x, 2) + Math.pow(this.y - v.y, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该Vector2的平方长度
|
||||
* @returns 这个Vector2的平方长度
|
||||
*/
|
||||
public lengthSquared(): number {
|
||||
return (this.x * this.x) + (this.y * this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从原点到向量末端的距离
|
||||
* @returns
|
||||
*/
|
||||
public getLength(): number {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 四舍五入X和Y值
|
||||
*/
|
||||
public round(): Vector2 {
|
||||
return new Vector2(Math.round(this.x), Math.round(this.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回以自己为中心点的左右角,单位为度
|
||||
* @param left
|
||||
* @param right
|
||||
*/
|
||||
public angleBetween(left: Vector2, right: Vector2) {
|
||||
let one = left.sub(this);
|
||||
let two = right.sub(this);
|
||||
return Vector2Ext.angle(one, two);
|
||||
}
|
||||
|
||||
public getDistance(other: Vector2): number {
|
||||
return Math.sqrt(this.getDistanceSquared(other));
|
||||
}
|
||||
|
||||
public getDistanceSquared(other: Vector2): number {
|
||||
const dx = other.x - this.x;
|
||||
const dy = other.y - this.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
public isBetween(v1: Vector2, v2: Vector2): boolean {
|
||||
const cross = v2.sub(v1).cross(this.sub(v1));
|
||||
return Math.abs(cross) < Number.EPSILON && this.dot(v2.sub(v1)) >= 0 && this.dot(v1.sub(v2)) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 两个向量的叉积
|
||||
* @param other
|
||||
* @returns
|
||||
*/
|
||||
public cross(other: Vector2): number {
|
||||
return this.x * other.y - this.y * other.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算向量与x轴之间的夹角
|
||||
*/
|
||||
public getAngle(): number {
|
||||
return Math.atan2(this.y, this.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较当前实例是否等于指定的对象
|
||||
* @param other 要比较的对象
|
||||
* @returns 如果实例相同true 否则false
|
||||
*/
|
||||
public equals(other: Vector2, tolerance: number = 0.001): boolean {
|
||||
return Math.abs(this.x - other.x) <= tolerance && Math.abs(this.y - other.y) <= tolerance;
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return MathHelper.isValid(this.x) && MathHelper.isValid(this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含来自两个向量的最小值
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @returns
|
||||
*/
|
||||
public static min(value1: Vector2, value2: Vector2) {
|
||||
return new Vector2(value1.x < value2.x ? value1.x : value2.x,
|
||||
value1.y < value2.y ? value1.y : value2.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含两个向量的最大值
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @returns
|
||||
*/
|
||||
public static max(value1: Vector2, value2: Vector2) {
|
||||
return new Vector2(value1.x > value2.x ? value1.x : value2.x,
|
||||
value1.y > value2.y ? value1.y : value2.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,其中包含Hermite样条插值
|
||||
* @param value1
|
||||
* @param tangent1
|
||||
* @param value2
|
||||
* @param tangent2
|
||||
* @param amount
|
||||
* @returns
|
||||
*/
|
||||
public static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number) {
|
||||
return new Vector2(MathHelper.hermite(value1.x, tangent1.x, value2.x, tangent2.x, amount),
|
||||
MathHelper.hermite(value1.y, tangent1.y, value2.y, tangent2.y, amount));
|
||||
}
|
||||
|
||||
public static unsignedAngle(from: Vector2, to: Vector2, round: boolean = true) {
|
||||
from.normalizeEqual();
|
||||
to.normalizeEqual();
|
||||
const angle =
|
||||
Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
|
||||
return round ? Math.round(angle) : angle;
|
||||
}
|
||||
|
||||
public static fromAngle(angle: number, magnitude: number = 1): Vector2 {
|
||||
return new Vector2(magnitude * Math.cos(angle), magnitude * Math.sin(angle));
|
||||
}
|
||||
|
||||
|
||||
public clone(): Vector2 {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
public copyFrom(source: Vector2): Vector2 {
|
||||
this.x = source.x;
|
||||
this.y = source.y;
|
||||
return this;
|
||||
/**
|
||||
* 向量乘法
|
||||
* @param a 向量A
|
||||
* @param b 向量B或标量
|
||||
*/
|
||||
public static multiply(a: Vector2, b: Vector2 | number): Vector2 {
|
||||
if (typeof b === 'number') {
|
||||
return new Vector2(a.x * b, a.y * b);
|
||||
} else {
|
||||
return new Vector2(a.x * b.x, a.y * b.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量除法
|
||||
* @param a 向量A
|
||||
* @param b 向量B或标量
|
||||
*/
|
||||
public static divide(a: Vector2, b: Vector2 | number): Vector2 {
|
||||
if (typeof b === 'number') {
|
||||
return new Vector2(a.x / b, a.y / b);
|
||||
} else {
|
||||
return new Vector2(a.x / b.x, a.y / b.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量点积
|
||||
* @param a 向量A
|
||||
* @param b 向量B
|
||||
*/
|
||||
public static dot(a: Vector2, b: Vector2): number {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量叉积
|
||||
* @param a 向量A
|
||||
* @param b 向量B
|
||||
*/
|
||||
public static cross(a: Vector2, b: Vector2): number {
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量距离
|
||||
* @param a 向量A
|
||||
* @param b 向量B
|
||||
*/
|
||||
public static distance(a: Vector2, b: Vector2): number {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量距离的平方
|
||||
* @param a 向量A
|
||||
* @param b 向量B
|
||||
*/
|
||||
public static distanceSquared(a: Vector2, b: Vector2): number {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线性插值
|
||||
* @param a 起始向量
|
||||
* @param b 目标向量
|
||||
* @param t 插值参数(0-1)
|
||||
*/
|
||||
public static lerp(a: Vector2, b: Vector2, t: number): Vector2 {
|
||||
return new Vector2(
|
||||
a.x + (b.x - a.x) * t,
|
||||
a.y + (b.y - a.y) * t
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 四舍五入
|
||||
* @param vector 向量
|
||||
*/
|
||||
public static round(vector: Vector2): Vector2 {
|
||||
return new Vector2(Math.round(vector.x), Math.round(vector.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从角度创建向量
|
||||
* @param radians 角度(弧度)
|
||||
* @param length 长度,默认为1
|
||||
*/
|
||||
public static fromAngle(radians: number, length: number = 1): Vector2 {
|
||||
return new Vector2(Math.cos(radians) * length, Math.sin(radians) * length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射向量
|
||||
* @param vector 入射向量
|
||||
* @param normal 法向量
|
||||
*/
|
||||
public static reflect(vector: Vector2, normal: Vector2): Vector2 {
|
||||
const dot = Vector2.dot(vector, normal);
|
||||
return Vector2.subtract(vector, Vector2.multiply(normal, 2 * dot));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// 数学库导出
|
||||
export { Vector2 } from './Vector2';
|
||||
export { MathHelper } from './MathHelper';
|
||||
export { Rectangle } from './Rectangle';
|
||||
export { Edge } from './Edge';
|
||||
@@ -1,129 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 移动器使用的帮助器类,用于管理触发器碰撞器交互并调用itriggerlistener
|
||||
*/
|
||||
export class ColliderTriggerHelper {
|
||||
private _entity: Entity;
|
||||
/** 存储当前帧中发生的所有活动交点对 */
|
||||
private _activeTriggerIntersections: PairSet<Collider> = new PairSet<Collider>();
|
||||
/** 存储前一帧的交点对,这样我们就可以在移动这一帧后检测到退出 */
|
||||
private _previousTriggerIntersections: PairSet<Collider> = new PairSet<Collider>();
|
||||
private _tempTriggerList: ITriggerListener[] = [];
|
||||
|
||||
constructor(entity: Entity) {
|
||||
this._entity = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* update应该在实体被移动后被调用,它将处理任何与Colllider重叠的ITriggerListeners。
|
||||
* 它将处理任何与Collider重叠的ITriggerListeners。
|
||||
*/
|
||||
public update() {
|
||||
const lateColliders = [];
|
||||
// 对所有实体.colliders进行重叠检查,这些实体.colliders是触发器,与所有宽相碰撞器,无论是否触发器。
|
||||
// 任何重叠都会导致触发事件
|
||||
let colliders: Collider[] = this.getColliders();
|
||||
if (colliders.length > 0) {
|
||||
for (let i = 0; i < colliders.length; i++) {
|
||||
let collider = colliders[i];
|
||||
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, collider.bounds, collider.collidesWithLayers.value);
|
||||
for (let j = 0; j < neighbors.length; j++) {
|
||||
let neighbor = neighbors[j];
|
||||
// 我们至少需要一个碰撞器作为触发器
|
||||
if (!collider.isTrigger && !neighbor.isTrigger)
|
||||
continue;
|
||||
|
||||
if (collider.overlaps(neighbor)) {
|
||||
const pair = new Pair<Collider>(collider, neighbor);
|
||||
|
||||
// 如果我们的某一个集合中已经有了这个对子(前一个或当前的触发交叉点),就不要调用输入事件了
|
||||
const shouldReportTriggerEvent = !this._activeTriggerIntersections.has(pair) &&
|
||||
!this._previousTriggerIntersections.has(pair);
|
||||
|
||||
if (shouldReportTriggerEvent) {
|
||||
if (neighbor.castSortOrder >= Collider.lateSortOrder) {
|
||||
lateColliders.push(pair);
|
||||
} else {
|
||||
this.notifyTriggerListeners(pair, true);
|
||||
}
|
||||
}
|
||||
|
||||
this._activeTriggerIntersections.add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (lateColliders.length > 0) {
|
||||
for (let i = 0; i < lateColliders.length; i ++) {
|
||||
const pair = lateColliders[i];
|
||||
this.notifyTriggerListeners(pair, true);
|
||||
}
|
||||
}
|
||||
|
||||
this.checkForExitedColliders();
|
||||
}
|
||||
|
||||
private getColliders() {
|
||||
const colliders: Collider[] = [];
|
||||
if (this._entity.components.buffer.length > 0)
|
||||
for (let i = 0; i < this._entity.components.buffer.length; i ++) {
|
||||
const component = this._entity.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
colliders.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
return colliders;
|
||||
}
|
||||
|
||||
private checkForExitedColliders() {
|
||||
// 删除所有与此帧交互的触发器,留下我们退出的触发器
|
||||
this._previousTriggerIntersections.except(this._activeTriggerIntersections);
|
||||
const all = this._previousTriggerIntersections.all;
|
||||
all.forEach(pair => {
|
||||
this.notifyTriggerListeners(pair, false);
|
||||
});
|
||||
|
||||
this._previousTriggerIntersections.clear();
|
||||
|
||||
// 添加所有当前激活的触发器
|
||||
this._previousTriggerIntersections.union(this._activeTriggerIntersections);
|
||||
|
||||
// 清空活动集,为下一帧做准备
|
||||
this._activeTriggerIntersections.clear();
|
||||
}
|
||||
|
||||
private notifyTriggerListeners(collisionPair: Pair<Collider>, isEntering: boolean) {
|
||||
TriggerListenerHelper.getITriggerListener(collisionPair.first.entity, this._tempTriggerList);
|
||||
if (this._tempTriggerList.length > 0)
|
||||
for (let i = 0; i < this._tempTriggerList.length; i++) {
|
||||
const trigger = this._tempTriggerList[i];
|
||||
if (isEntering) {
|
||||
trigger.onTriggerEnter(collisionPair.second, collisionPair.first);
|
||||
} else {
|
||||
trigger.onTriggerExit(collisionPair.second, collisionPair.first);
|
||||
}
|
||||
|
||||
this._tempTriggerList.length = 0;
|
||||
|
||||
if (collisionPair.second.entity) {
|
||||
TriggerListenerHelper.getITriggerListener(collisionPair.second.entity, this._tempTriggerList);
|
||||
if (this._tempTriggerList.length > 0)
|
||||
for (let i = 0; i < this._tempTriggerList.length; i++) {
|
||||
const trigger = this._tempTriggerList[i];
|
||||
if (isEntering) {
|
||||
trigger.onTriggerEnter(collisionPair.first, collisionPair.second);
|
||||
} else {
|
||||
trigger.onTriggerExit(collisionPair.first, collisionPair.second);
|
||||
}
|
||||
}
|
||||
|
||||
this._tempTriggerList.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
module es {
|
||||
export enum PointSectors {
|
||||
center = 0,
|
||||
top = 1,
|
||||
bottom = 2,
|
||||
topLeft = 9,
|
||||
topRight = 5,
|
||||
left = 8,
|
||||
right = 4,
|
||||
bottomLeft = 10,
|
||||
bottomRight = 6
|
||||
}
|
||||
|
||||
export class Collisions {
|
||||
public static lineToLine(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2): boolean {
|
||||
const b = a2.sub(a1);
|
||||
const d = b2.sub(b1);
|
||||
const bDotDPerp = b.x * d.y - b.y * d.x;
|
||||
|
||||
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
|
||||
if (bDotDPerp == 0)
|
||||
return false;
|
||||
|
||||
const c = b1.sub(a1);
|
||||
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
|
||||
if (t < 0 || t > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
|
||||
if (u < 0 || u > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static lineToLineIntersection(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2, intersection: Vector2 = es.Vector2.zero): boolean {
|
||||
intersection.x = 0;
|
||||
intersection.y = 0;
|
||||
|
||||
const b = a2.sub(a1);
|
||||
const d = b2.sub(b1);
|
||||
const bDotDPerp = b.x * d.y - b.y * d.x;
|
||||
|
||||
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
|
||||
if (bDotDPerp == 0)
|
||||
return false;
|
||||
|
||||
const c = b1.sub(a1);
|
||||
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
|
||||
if (t < 0 || t > 1)
|
||||
return false;
|
||||
|
||||
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
|
||||
if (u < 0 || u > 1)
|
||||
return false;
|
||||
|
||||
const temp = a1.add(b.scale(t));
|
||||
intersection.x = temp.x;
|
||||
intersection.y = temp.y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static closestPointOnLine(lineA: Vector2, lineB: Vector2, closestTo: Vector2) {
|
||||
const v = lineB.sub(lineA);
|
||||
const w = closestTo.sub(lineA);
|
||||
let t = w.dot(v) / v.dot(v);
|
||||
t = MathHelper.clamp(t, 0, 1);
|
||||
|
||||
return lineA.add(v.scale(t));
|
||||
}
|
||||
|
||||
public static circleToCircle(circleCenter1: Vector2, circleRadius1: number, circleCenter2: Vector2, circleRadius2: number): boolean {
|
||||
return Vector2.sqrDistance(circleCenter1, circleCenter2) < (circleRadius1 + circleRadius2) * (circleRadius1 + circleRadius2);
|
||||
}
|
||||
|
||||
public static circleToLine(circleCenter: Vector2, radius: number, lineFrom: Vector2, lineTo: Vector2): boolean {
|
||||
return Vector2.sqrDistance(circleCenter, this.closestPointOnLine(lineFrom, lineTo, circleCenter)) < radius * radius;
|
||||
}
|
||||
|
||||
public static circleToPoint(circleCenter: Vector2, radius: number, point: Vector2): boolean {
|
||||
return Vector2.sqrDistance(circleCenter, point) < radius * radius;
|
||||
}
|
||||
|
||||
public static rectToCircle(rect: Rectangle, cPosition: Vector2, cRadius: number): boolean {
|
||||
// 检查矩形是否包含圆的中心点
|
||||
if (this.rectToPoint(rect.x, rect.y, rect.width, rect.height, cPosition))
|
||||
return true;
|
||||
|
||||
// 对照相关边缘检查圆圈
|
||||
let edgeFrom: Vector2;
|
||||
let edgeTo: Vector2;
|
||||
const sector = this.getSector(rect.x, rect.y, rect.width, rect.height, cPosition);
|
||||
|
||||
if ((sector & PointSectors.top) !== 0) {
|
||||
edgeFrom = new Vector2(rect.x, rect.y);
|
||||
edgeTo = new Vector2(rect.x + rect.width, rect.y);
|
||||
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((sector & PointSectors.bottom) !== 0) {
|
||||
edgeFrom = new Vector2(rect.x, rect.y + rect.width);
|
||||
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
|
||||
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((sector & PointSectors.left) !== 0) {
|
||||
edgeFrom = new Vector2(rect.x, rect.y);
|
||||
edgeTo = new Vector2(rect.x, rect.y + rect.height);
|
||||
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((sector & PointSectors.right) !== 0) {
|
||||
edgeFrom = new Vector2(rect.x + rect.width, rect.y);
|
||||
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
|
||||
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查矩形和线段之间是否相交
|
||||
* @param rect - 要检查的矩形
|
||||
* @param lineFrom - 线段起点
|
||||
* @param lineTo - 线段终点
|
||||
* @returns 如果相交返回 true,否则返回 false
|
||||
*/
|
||||
public static rectToLine(rect: Rectangle, lineFrom: Vector2, lineTo: Vector2): boolean {
|
||||
// 获取起点和终点所在矩形的位置
|
||||
const fromSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineFrom);
|
||||
const toSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineTo);
|
||||
|
||||
// 起点或终点位于矩形内部
|
||||
if (fromSector == PointSectors.center || toSector == PointSectors.center) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 起点和终点都在矩形外部的同一区域
|
||||
if ((fromSector & toSector) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 到这里说明起点和终点分别在矩形的两个不同区域,需要检查线段是否与矩形的边相交
|
||||
|
||||
// 枚举起点和终点所在区域
|
||||
const both = fromSector | toSector;
|
||||
|
||||
// 逐条检查矩形的四条边是否与线段相交
|
||||
if ((both & PointSectors.top) != 0) {
|
||||
if (this.lineToLine(
|
||||
new Vector2(rect.x, rect.y),
|
||||
new Vector2(rect.x + rect.width, rect.y),
|
||||
lineFrom,
|
||||
lineTo
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((both & PointSectors.bottom) != 0) {
|
||||
if (this.lineToLine(
|
||||
new Vector2(rect.x, rect.y + rect.height),
|
||||
new Vector2(rect.x + rect.width, rect.y + rect.height),
|
||||
lineFrom,
|
||||
lineTo
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((both & PointSectors.left) != 0) {
|
||||
if (this.lineToLine(
|
||||
new Vector2(rect.x, rect.y),
|
||||
new Vector2(rect.x, rect.y + rect.height),
|
||||
lineFrom,
|
||||
lineTo
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((both & PointSectors.right) != 0) {
|
||||
if (this.lineToLine(
|
||||
new Vector2(rect.x + rect.width, rect.y),
|
||||
new Vector2(rect.x + rect.width, rect.y + rect.height),
|
||||
lineFrom,
|
||||
lineTo
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static rectToPoint(rX: number, rY: number, rW: number, rH: number, point: Vector2) {
|
||||
return point.x >= rX && point.y >= rY && point.x < rX + rW && point.y < rY + rH;
|
||||
}
|
||||
|
||||
/**
|
||||
* 位标志和帮助使用Cohen–Sutherland算法
|
||||
*
|
||||
* 位标志:
|
||||
* 1001 1000 1010
|
||||
* 0001 0000 0010
|
||||
* 0101 0100 0110
|
||||
* @param rX
|
||||
* @param rY
|
||||
* @param rW
|
||||
* @param rH
|
||||
* @param point
|
||||
*/
|
||||
public static getSector(rX: number, rY: number, rW: number, rH: number, point: Vector2): PointSectors {
|
||||
let sector = PointSectors.center;
|
||||
|
||||
if (point.x < rX)
|
||||
sector |= PointSectors.left;
|
||||
else if (point.x >= rX + rW)
|
||||
sector |= PointSectors.right;
|
||||
|
||||
if (point.y < rY)
|
||||
sector |= PointSectors.top;
|
||||
else if (point.y >= rY + rH)
|
||||
sector |= PointSectors.bottom;
|
||||
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
///<reference path="./RaycastHit.ts" />
|
||||
module es {
|
||||
export class Physics {
|
||||
public static _spatialHash: SpatialHash;
|
||||
/** 用于在全局范围内存储重力值的方便字段 */
|
||||
public static gravity = new Vector2(0, -300);
|
||||
/** 调用reset并创建一个新的SpatialHash时使用的单元格大小 */
|
||||
public static spatialHashCellSize = 100;
|
||||
/** 接受layerMask的所有方法的默认值 */
|
||||
public static readonly allLayers: number = -1;
|
||||
/**
|
||||
* raycast是否检测配置为触发器的碰撞器
|
||||
*/
|
||||
public static raycastsHitTriggers: boolean = false;
|
||||
/**
|
||||
* 在碰撞器中开始的射线/直线是否强制转换检测到那些碰撞器
|
||||
*/
|
||||
public static raycastsStartInColliders = false;
|
||||
public static debugRender: boolean = false;
|
||||
/**
|
||||
* 我们保留它以避免在每次raycast发生时分配它
|
||||
*/
|
||||
public static _hitArray: RaycastHit[] = [
|
||||
new RaycastHit()
|
||||
];
|
||||
/**
|
||||
* 避免重叠检查和形状投射的分配
|
||||
*/
|
||||
public static _colliderArray: Collider[] = [
|
||||
null
|
||||
];
|
||||
|
||||
public static reset() {
|
||||
this._spatialHash = new SpatialHash(this.spatialHashCellSize);
|
||||
this._hitArray[0].reset();
|
||||
this._colliderArray[0] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SpatialHash中移除所有碰撞器
|
||||
*/
|
||||
public static clear() {
|
||||
this._spatialHash.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有对撞机落在一个圆形区域内。返回遇到的第一个对撞机
|
||||
* @param center
|
||||
* @param radius
|
||||
* @param layerMask
|
||||
*/
|
||||
public static overlapCircle(center: Vector2, radius: number, layerMask: number = Physics.allLayers) {
|
||||
this._colliderArray[0] = null;
|
||||
this._spatialHash.overlapCircle(center, radius, this._colliderArray, layerMask);
|
||||
return this._colliderArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有落在指定圆圈内的碰撞器
|
||||
* @param center
|
||||
* @param randius
|
||||
* @param results
|
||||
* @param layerMask
|
||||
*/
|
||||
public static overlapCircleAll(center: Vector2, radius: number, results: Collider[], layerMask: number = this.allLayers) {
|
||||
return this._spatialHash.overlapCircle(
|
||||
center,
|
||||
radius,
|
||||
results,
|
||||
layerMask
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有碰撞器与边界相交的碰撞器。bounds。请注意,这是一个broadphase检查,所以它只检查边界,不做单个碰撞到碰撞器的检查!
|
||||
* @param rect
|
||||
* @param layerMask
|
||||
*/
|
||||
public static boxcastBroadphase(rect: Rectangle, layerMask: number = this.allLayers) {
|
||||
return this._spatialHash.aabbBroadphase(rect, null, layerMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有被边界交错的碰撞器,但不包括传入的碰撞器(self)。
|
||||
* 如果你想为其他查询自己创建扫描边界,这个方法很有用
|
||||
* @param collider
|
||||
* @param rect
|
||||
* @param layerMask
|
||||
*/
|
||||
public static boxcastBroadphaseExcludingSelf(collider: Collider, rect: Rectangle, layerMask = this.allLayers) {
|
||||
return this._spatialHash.aabbBroadphase(rect, collider, layerMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有边界与 collider.bounds 相交的碰撞器,但不包括传入的碰撞器(self)
|
||||
* @param collider
|
||||
* @param layerMask
|
||||
*/
|
||||
public static boxcastBroadphaseExcludingSelfNonRect(collider: Collider, layerMask = this.allLayers) {
|
||||
let bounds = collider.bounds;
|
||||
return this._spatialHash.aabbBroadphase(bounds, collider, layerMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有被 collider.bounds 扩展为包含 deltaX/deltaY 的碰撞器,但不包括传入的碰撞器(self)
|
||||
* @param collider
|
||||
* @param deltaX
|
||||
* @param deltaY
|
||||
* @param layerMask
|
||||
*/
|
||||
public static boxcastBroadphaseExcludingSelfDelta(collider: Collider, deltaX: number, deltaY: number, layerMask: number = Physics.allLayers) {
|
||||
let colliderBounds = collider.bounds;
|
||||
let sweptBounds = colliderBounds.getSweptBroadphaseBounds(deltaX, deltaY);
|
||||
return this._spatialHash.aabbBroadphase(sweptBounds, collider, layerMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对撞机添加到物理系统中
|
||||
* @param collider
|
||||
*/
|
||||
public static addCollider(collider: Collider) {
|
||||
Physics._spatialHash.register(collider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从物理系统中移除对撞机
|
||||
* @param collider
|
||||
*/
|
||||
public static removeCollider(collider: Collider) {
|
||||
Physics._spatialHash.remove(collider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新物理系统中对撞机的位置。这实际上只是移除然后重新添加带有新边界的碰撞器
|
||||
* @param collider
|
||||
*/
|
||||
public static updateCollider(collider: Collider) {
|
||||
this._spatialHash.remove(collider);
|
||||
this._spatialHash.register(collider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回与layerMask匹配的碰撞器的第一次命中
|
||||
* @param start
|
||||
* @param end
|
||||
* @param layerMask
|
||||
*/
|
||||
public static linecast(start: Vector2, end: Vector2, layerMask: number = this.allLayers, ignoredColliders: Set<Collider> = null): RaycastHit {
|
||||
this._hitArray[0].reset();
|
||||
Physics.linecastAll(
|
||||
start,
|
||||
end,
|
||||
this._hitArray,
|
||||
layerMask,
|
||||
ignoredColliders
|
||||
);
|
||||
return this._hitArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过空间散列强制执行一行,并用该行命中的任何碰撞器填充hits数组
|
||||
* @param start
|
||||
* @param end
|
||||
* @param hits
|
||||
* @param layerMask
|
||||
*/
|
||||
public static linecastAll(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number = this.allLayers, ignoredColliders: Set<Collider> = null) {
|
||||
return this._spatialHash.linecast(
|
||||
start,
|
||||
end,
|
||||
hits,
|
||||
layerMask,
|
||||
ignoredColliders
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有对撞机落在一个矩形区域中
|
||||
* @param rect
|
||||
* @param layerMask
|
||||
*/
|
||||
public static overlapRectangle(rect: Rectangle, layerMask: number = Physics.allLayers) {
|
||||
this._colliderArray[0] = null;
|
||||
this._spatialHash.overlapRectangle(rect, this._colliderArray, layerMask);
|
||||
return this._colliderArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有在指定矩形范围内的碰撞器
|
||||
* @param rect
|
||||
* @param results
|
||||
* @param layerMask
|
||||
*/
|
||||
public static overlapRectangleAll(rect: Rectangle, results: Collider[], layerMask: number = Physics.allLayers) {
|
||||
if (results.length == 0) {
|
||||
console.warn("传入了一个空的结果数组。不会返回任何结果");
|
||||
return 0;
|
||||
}
|
||||
return this._spatialHash.overlapRectangle(rect, results, layerMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 不是真正的射线(射线只有开始和方向),作为一条线和射线。
|
||||
*/
|
||||
export class Ray2D {
|
||||
public get start(): Vector2 {
|
||||
return this._start;
|
||||
}
|
||||
|
||||
public get direction(): Vector2 {
|
||||
return this._direction;
|
||||
}
|
||||
|
||||
public get end(): Vector2 {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
constructor(pos: Vector2, end: Vector2) {
|
||||
this._start = pos.clone();
|
||||
this._end = end.clone();
|
||||
this._direction = this._end.sub(this._start);
|
||||
}
|
||||
|
||||
private _start: Vector2;
|
||||
private _direction: Vector2;
|
||||
private _end: Vector2;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
module es {
|
||||
export class RaycastHit {
|
||||
/**
|
||||
* 对撞机被射线击中
|
||||
*/
|
||||
public collider: Collider;
|
||||
|
||||
/**
|
||||
* 撞击发生时沿射线的距离。
|
||||
*/
|
||||
public fraction: number = 0;
|
||||
|
||||
/**
|
||||
* 从射线原点到碰撞点的距离
|
||||
*/
|
||||
public distance: number = 0;
|
||||
|
||||
/**
|
||||
* 世界空间中光线击中对撞机表面的点
|
||||
*/
|
||||
public point: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 被射线击中的表面的法向量
|
||||
*/
|
||||
public normal: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 用于执行转换的质心。使其接触的形状的位置。
|
||||
*/
|
||||
public centroid: Vector2;
|
||||
|
||||
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2) {
|
||||
this.collider = collider;
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
this.centroid = Vector2.zero;
|
||||
}
|
||||
|
||||
public setAllValues(collider: Collider, fraction: number, distance: number, point: Vector2, normal: Vector2) {
|
||||
this.collider = collider;
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
public setValues(fraction: number, distance: number, point: Vector2, normal: Vector2) {
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.collider = null;
|
||||
this.fraction = this.distance = 0;
|
||||
}
|
||||
|
||||
public clone(): RaycastHit {
|
||||
const hit = new RaycastHit();
|
||||
hit.setAllValues(
|
||||
this.collider,
|
||||
this.fraction,
|
||||
this.distance,
|
||||
this.point,
|
||||
this.normal
|
||||
);
|
||||
return hit;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[RaycastHit] fraction: ${this.fraction}, distance: ${this.distance}, normal: ${this.normal}, centroid: ${this.centroid}, point: ${this.point}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
///<reference path="./Polygon.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 多边形的特殊情况。在进行SAT碰撞检查时,我们只需要检查2个轴而不是8个轴
|
||||
*/
|
||||
export class Box extends Polygon {
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
constructor(width: number, height: number) {
|
||||
super(Box.buildBox(width, height), true);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一个盒子的形状中建立多边形需要的点的帮助方法
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
private static buildBox(width: number, height: number): Vector2[] {
|
||||
// 我们在(0,0)的中心周围创建点
|
||||
let halfWidth = width / 2;
|
||||
let halfHeight = height / 2;
|
||||
let verts = new Array(4);
|
||||
verts[0] = new Vector2(-halfWidth, -halfHeight);
|
||||
verts[1] = new Vector2(halfWidth, -halfHeight);
|
||||
verts[2] = new Vector2(halfWidth, halfHeight);
|
||||
verts[3] = new Vector2(-halfWidth, halfHeight);
|
||||
|
||||
return verts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新框点,重新计算中心,设置宽度/高度
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public updateBox(width: number, height: number) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
// 我们在(0,0)的中心周围创建点
|
||||
let halfWidth = width / 2;
|
||||
let halfHeight = height / 2;
|
||||
|
||||
this.points[0] = new Vector2(-halfWidth, -halfHeight);
|
||||
this.points[1] = new Vector2(halfWidth, -halfHeight);
|
||||
this.points[2] = new Vector2(halfWidth, halfHeight);
|
||||
this.points[3] = new Vector2(-halfWidth, halfHeight);
|
||||
|
||||
for (let i = 0; i < this.points.length; i++)
|
||||
this._originalPoints[i] = this.points[i];
|
||||
}
|
||||
|
||||
public getEdges(): Array<Line> {
|
||||
const edges = [];
|
||||
|
||||
for (let i = 0; i < this.points.length; i++) {
|
||||
const j = (i + 1) % this.points.length;
|
||||
edges.push(new Line(this.points[i], this.points[j]));
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
public overlaps(other: Shape) {
|
||||
// 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测
|
||||
if (this.isUnrotated) {
|
||||
if (other instanceof Box && other.isUnrotated)
|
||||
return this.bounds.intersects(other.bounds);
|
||||
|
||||
if (other instanceof Circle)
|
||||
return Collisions.rectToCircle(this.bounds, other.position, other.radius);
|
||||
}
|
||||
|
||||
return super.overlaps(other);
|
||||
}
|
||||
|
||||
public collidesWithShape(other: Shape, result: Out<CollisionResult>): boolean {
|
||||
// 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测
|
||||
if (other instanceof Box && (other as Box).isUnrotated) {
|
||||
return ShapeCollisionsBox.boxToBox(this, other, result);
|
||||
}
|
||||
|
||||
// TODO: 让 minkowski 运行于 cricleToBox
|
||||
|
||||
return super.collidesWithShape(other, result);
|
||||
}
|
||||
|
||||
public containsPoint(point: Vector2) {
|
||||
if (this.isUnrotated)
|
||||
return this.bounds.contains(point.x, point.y);
|
||||
|
||||
return super.containsPoint(point);
|
||||
}
|
||||
|
||||
public pointCollidesWithShape(point: Vector2, result: Out<CollisionResult>): boolean {
|
||||
if (this.isUnrotated)
|
||||
return ShapeCollisionsPoint.pointToBox(point, this, result);
|
||||
|
||||
return super.pointCollidesWithShape(point, result);
|
||||
}
|
||||
|
||||
public getFurthestPoint(normal: Vector2): Vector2 {
|
||||
const halfWidth = this.width / 2;
|
||||
const halfHeight = this.height / 2;
|
||||
|
||||
let furthestPoint = new Vector2(halfWidth, halfHeight);
|
||||
let dotProduct = furthestPoint.dot(normal);
|
||||
|
||||
let tempPoint = new Vector2(-halfWidth, halfHeight);
|
||||
let tempDotProduct = tempPoint.dot(normal);
|
||||
if (tempDotProduct > dotProduct) {
|
||||
furthestPoint.copyFrom(tempPoint);
|
||||
dotProduct = tempDotProduct;
|
||||
}
|
||||
|
||||
tempPoint.setTo(-halfWidth, -halfHeight);
|
||||
tempDotProduct = tempPoint.dot(normal);
|
||||
if (tempDotProduct > dotProduct) {
|
||||
furthestPoint.copyFrom(tempPoint);
|
||||
dotProduct = tempDotProduct;
|
||||
}
|
||||
|
||||
tempPoint.setTo(halfWidth, -halfHeight);
|
||||
tempDotProduct = tempPoint.dot(normal);
|
||||
if (tempDotProduct > dotProduct) {
|
||||
furthestPoint.copyFrom(tempPoint);
|
||||
}
|
||||
|
||||
return furthestPoint;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
///<reference path="./Shape.ts" />
|
||||
module es {
|
||||
export class Circle extends Shape {
|
||||
public radius: number;
|
||||
public _originalRadius: number;
|
||||
|
||||
constructor(radius: number) {
|
||||
super();
|
||||
this.radius = radius;
|
||||
this._originalRadius = radius;
|
||||
}
|
||||
|
||||
public recalculateBounds(collider: Collider) {
|
||||
// 如果我们没有旋转或不关心TRS我们使用localOffset作为中心
|
||||
this.center = collider.localOffset;
|
||||
|
||||
if (collider.shouldColliderScaleAndRotateWithTransform) {
|
||||
// 我们只将直线缩放为一个圆,所以我们将使用最大值
|
||||
const scale = collider.entity.transform.scale;
|
||||
const hasUnitScale = scale.x === 1 && scale.y === 1;
|
||||
const maxScale = Math.max(scale.x, scale.y);
|
||||
this.radius = this._originalRadius * maxScale;
|
||||
|
||||
if (collider.entity.transform.rotation !== 0) {
|
||||
// 为了处理偏移原点的旋转,我们只需要将圆心围绕(0,0)在一个圆上移动,我们的偏移量就是0角
|
||||
const offsetAngle = Math.atan2(collider.localOffset.y, collider.localOffset.x) * MathHelper.Rad2Deg;
|
||||
const offsetLength = hasUnitScale ? collider._localOffsetLength : collider.localOffset.multiply(collider.entity.transform.scale).magnitude();
|
||||
this.center = MathHelper.pointOnCircle(Vector2.zero, offsetLength, collider.entity.transform.rotation + offsetAngle);
|
||||
}
|
||||
}
|
||||
|
||||
this.position = collider.transform.position.add(this.center);
|
||||
this.bounds = new Rectangle(this.position.x - this.radius, this.position.y - this.radius, this.radius * 2, this.radius * 2);
|
||||
}
|
||||
|
||||
public overlaps(other: Shape) {
|
||||
const result = new Out<CollisionResult>();
|
||||
if (other instanceof Box && (other as Box).isUnrotated)
|
||||
return Collisions.rectToCircle(other.bounds, this.position, this.radius);
|
||||
|
||||
if (other instanceof Circle)
|
||||
return Collisions.circleToCircle(this.position, this.radius, other.position, (other as Circle).radius);
|
||||
|
||||
if (other instanceof Polygon)
|
||||
return ShapeCollisionsCircle.circleToPolygon(this, other, result);
|
||||
|
||||
throw new Error(`overlaps of circle to ${other} are not supported`);
|
||||
}
|
||||
|
||||
public collidesWithShape(other: Shape, result: Out<CollisionResult>): boolean {
|
||||
if (other instanceof Box && (other as Box).isUnrotated) {
|
||||
return ShapeCollisionsCircle.circleToBox(this, other, result);
|
||||
}
|
||||
|
||||
if (other instanceof Circle) {
|
||||
return ShapeCollisionsCircle.circleToCircle(this, other, result);
|
||||
}
|
||||
|
||||
if (other instanceof Polygon) {
|
||||
return ShapeCollisionsCircle.circleToPolygon(this, other, result);
|
||||
}
|
||||
|
||||
throw new Error(`Collisions of Circle to ${other} are not supported`);
|
||||
}
|
||||
|
||||
public collidesWithLine(start: Vector2, end: Vector2, hit: Out<RaycastHit>): boolean {
|
||||
return ShapeCollisionsLine.lineToCircle(start, end, this, hit);
|
||||
}
|
||||
|
||||
public getPointAlongEdge(angle: number): Vector2 {
|
||||
return new Vector2(
|
||||
this.position.x + this.radius * Math.cos(angle),
|
||||
this.position.y + this.radius * Math.sin(angle)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所提供的点是否在此范围内
|
||||
* @param point
|
||||
*/
|
||||
public containsPoint(point: Vector2) {
|
||||
return (point.sub(this.position)).lengthSquared() <= this.radius * this.radius;
|
||||
}
|
||||
|
||||
public pointCollidesWithShape(point: Vector2, result: Out<CollisionResult>): boolean {
|
||||
return ShapeCollisionsPoint.pointToCircle(point, this, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
module es {
|
||||
export class CollisionResult {
|
||||
/**
|
||||
* 与之相撞的对撞机
|
||||
*/
|
||||
public collider: Collider;
|
||||
/**
|
||||
* 被形状击中的表面的法向量
|
||||
*/
|
||||
public normal: Vector2 = Vector2.zero;
|
||||
/**
|
||||
* 应用于第一个形状以推入形状的转换
|
||||
*/
|
||||
public minimumTranslationVector: Vector2 = Vector2.zero;
|
||||
/**
|
||||
* 不是所有冲突类型都使用!在依赖这个字段之前,请检查ShapeCollisions切割类!
|
||||
*/
|
||||
public point: Vector2 = Vector2.zero;
|
||||
|
||||
public reset() {
|
||||
this.collider = null;
|
||||
this.normal.setTo(0, 0);
|
||||
this.minimumTranslationVector.setTo(0, 0);
|
||||
if (this.point) {
|
||||
this.point.setTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public cloneTo(cr: CollisionResult) {
|
||||
cr.collider = this.collider;
|
||||
cr.normal.setTo(this.normal.x, this.normal.y);
|
||||
cr.minimumTranslationVector.setTo(
|
||||
this.minimumTranslationVector.x,
|
||||
this.minimumTranslationVector.y
|
||||
);
|
||||
if (this.point) {
|
||||
if (!cr.point) {
|
||||
cr.point = new Vector2(0, 0);
|
||||
}
|
||||
cr.point.setTo(this.point.x, this.point.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从移动向量中移除水平方向的位移,以确保形状只沿垂直方向运动。如果移动向量包含水平移动,则通过计算垂直位移来修复响应距离。
|
||||
* @param deltaMovement - 移动向量
|
||||
*/
|
||||
public removeHorizontalTranslation(deltaMovement: Vector2) {
|
||||
// 如果运动方向和法线方向不在同一方向或者移动量为0且法线方向不为0,则需要修复
|
||||
if (Math.sign(this.normal.x) !== Math.sign(deltaMovement.x) || (deltaMovement.x === 0 && this.normal.x !== 0)) {
|
||||
// 获取响应距离
|
||||
const responseDistance = this.minimumTranslationVector.magnitude();
|
||||
// 计算需要修复的位移
|
||||
const fix = responseDistance / this.normal.y;
|
||||
|
||||
// 如果法线方向不是完全水平或垂直,并且修复距离小于移动向量的3倍,则修复距离
|
||||
if (Math.abs(this.normal.x) != 1 && Math.abs(fix) < Math.abs(deltaMovement.y * 3)) {
|
||||
this.minimumTranslationVector = new Vector2(0, -fix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public invertResult() {
|
||||
this.minimumTranslationVector = this.minimumTranslationVector.negate();
|
||||
this.normal = this.normal.negate();
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[CollisionResult] normal: ${this.normal}, minimumTranslationVector: ${this.minimumTranslationVector}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
module es {
|
||||
export class Line {
|
||||
public start: Vector2;
|
||||
public end: Vector2;
|
||||
|
||||
constructor(start: Vector2, end: Vector2) {
|
||||
this.start = start.clone();
|
||||
this.end = end.clone();
|
||||
}
|
||||
|
||||
public get direction(): Vector2 {
|
||||
return this.end.sub(this.start).normalize();
|
||||
}
|
||||
|
||||
public getNormal(): Vector2 {
|
||||
const angle = this.direction.getAngle() - Math.PI / 2;
|
||||
return new Vector2(Math.cos(angle), Math.sin(angle));
|
||||
}
|
||||
|
||||
public getDirection(out: Vector2) {
|
||||
return out.copyFrom(this.end).sub(this.start).normalize();
|
||||
}
|
||||
|
||||
public getLength() {
|
||||
return this.start.getDistance(this.end);
|
||||
}
|
||||
|
||||
public getLengthSquared() {
|
||||
return this.start.getDistanceSquared(this.end);
|
||||
}
|
||||
|
||||
public distanceToPoint(normal: Vector2, center: Vector2): number {
|
||||
return Math.abs((this.end.y - this.start.y) * normal.x - (this.end.x - this.start.x) * normal.y + this.end.x * this.start.y - this.end.y * this.start.x) / (2 * normal.magnitude());
|
||||
}
|
||||
|
||||
public getFurthestPoint(direction: Vector2): Vector2 {
|
||||
const d1 = this.start.dot(direction);
|
||||
const d2 = this.end.dot(direction);
|
||||
return d1 > d2 ? this.start : this.end;
|
||||
}
|
||||
|
||||
public getClosestPoint(point: Vector2, out: Vector2) {
|
||||
const delta = out.copyFrom(this.end).sub(this.start);
|
||||
const t = (point.sub(this.start)).dot(delta) / delta.lengthSquared();
|
||||
|
||||
if (t < 0) {
|
||||
return out.copyFrom(this.start);
|
||||
} else if (t > 1) {
|
||||
return out.copyFrom(this.end);
|
||||
}
|
||||
|
||||
return out.copyFrom(delta).multiplyScaler(t).add(this.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
///<reference path="./Shape.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 多边形
|
||||
*/
|
||||
export class Polygon extends Shape {
|
||||
/**
|
||||
* 组成多边形的点
|
||||
* 保持顺时针与凸边形
|
||||
*/
|
||||
public points: Vector2[];
|
||||
public _areEdgeNormalsDirty = true;
|
||||
/**
|
||||
* 多边形的原始数据
|
||||
*/
|
||||
public _originalPoints: Vector2[];
|
||||
public _polygonCenter: Vector2;
|
||||
/**
|
||||
* 用于优化未旋转box碰撞
|
||||
*/
|
||||
public isBox: boolean;
|
||||
public isUnrotated: boolean = true;
|
||||
|
||||
/**
|
||||
* 从点构造一个多边形
|
||||
* 多边形应该以顺时针方式指定 不能重复第一个/最后一个点,它们以0 0为中心
|
||||
* @param points
|
||||
* @param isBox
|
||||
*/
|
||||
constructor(points: Vector2[], isBox?: boolean) {
|
||||
super();
|
||||
|
||||
this.setPoints(points);
|
||||
this.isBox = isBox;
|
||||
}
|
||||
|
||||
public create(vertCount: number, radius: number) {
|
||||
Polygon.buildSymmetricalPolygon(vertCount, radius);
|
||||
}
|
||||
|
||||
public _edgeNormals: Vector2[];
|
||||
|
||||
/**
|
||||
* 边缘法线用于SAT碰撞检测。缓存它们用于避免squareRoots
|
||||
* box只有两个边缘 因为其他两边是平行的
|
||||
*/
|
||||
public get edgeNormals() {
|
||||
if (this._areEdgeNormalsDirty)
|
||||
this.buildEdgeNormals();
|
||||
return this._edgeNormals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置点并重新计算中心和边缘法线
|
||||
* @param points
|
||||
*/
|
||||
public setPoints(points: Vector2[]) {
|
||||
this.points = points;
|
||||
this.recalculateCenterAndEdgeNormals();
|
||||
|
||||
this._originalPoints = [];
|
||||
this.points.forEach(p => {
|
||||
this._originalPoints.push(p.clone());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算多边形中心
|
||||
* 如果点数改变必须调用该方法
|
||||
*/
|
||||
public recalculateCenterAndEdgeNormals() {
|
||||
this._polygonCenter = Polygon.findPolygonCenter(this.points);
|
||||
this._areEdgeNormalsDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立多边形边缘法线
|
||||
* 它们仅由edgeNormals getter惰性创建和更新
|
||||
*/
|
||||
public buildEdgeNormals() {
|
||||
// 对于box 我们只需要两条边,因为另外两条边是平行的
|
||||
let totalEdges = this.isBox ? 2 : this.points.length;
|
||||
if (this._edgeNormals == undefined || this._edgeNormals.length != totalEdges)
|
||||
this._edgeNormals = new Array(totalEdges);
|
||||
|
||||
let p2: Vector2;
|
||||
for (let i = 0; i < totalEdges; i++) {
|
||||
let p1 = this.points[i];
|
||||
if (i + 1 >= this.points.length)
|
||||
p2 = this.points[0];
|
||||
else
|
||||
p2 = this.points[i + 1];
|
||||
|
||||
let perp = Vector2Ext.perpendicular(p1, p2);
|
||||
Vector2Ext.normalize(perp);
|
||||
this._edgeNormals[i] = perp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立一个对称的多边形(六边形,八角形,n角形)并返回点
|
||||
* @param vertCount
|
||||
* @param radius
|
||||
*/
|
||||
public static buildSymmetricalPolygon(vertCount: number, radius: number) {
|
||||
const verts = new Array(vertCount);
|
||||
|
||||
for (let i = 0; i < vertCount; i++) {
|
||||
const a = 2 * Math.PI * (i / vertCount);
|
||||
verts[i] = new Vector2(Math.cos(a) * radius, Math.sin(a) * radius);
|
||||
}
|
||||
|
||||
return verts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定位多边形的点
|
||||
* @param points
|
||||
*/
|
||||
public static recenterPolygonVerts(points: Vector2[]) {
|
||||
const center = this.findPolygonCenter(points);
|
||||
for (let i = 0; i < points.length; i++)
|
||||
points[i] = points[i].sub(center);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到多边形的中心。注意,这对于正则多边形是准确的。不规则多边形没有中心。
|
||||
* @param points
|
||||
*/
|
||||
public static findPolygonCenter(points: Vector2[]) {
|
||||
let x = 0, y = 0;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
x += points[i].x;
|
||||
y += points[i].y;
|
||||
}
|
||||
|
||||
return new Vector2(x / points.length, y / points.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不知道辅助顶点,所以取每个顶点,如果你知道辅助顶点,执行climbing算法
|
||||
* @param points
|
||||
* @param direction
|
||||
*/
|
||||
public static getFarthestPointInDirection(points: Vector2[], direction: Vector2): Vector2 {
|
||||
let index = 0;
|
||||
let maxDot = points[index].dot(direction);
|
||||
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
let dot = points[i].dot(direction);
|
||||
if (dot > maxDot) {
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return points[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* 迭代多边形的所有边,并得到任意边上离点最近的点。
|
||||
* 通过最近点的平方距离和它所在的边的法线返回。
|
||||
* 点应该在多边形的空间中(点-多边形.位置)
|
||||
* @param points
|
||||
* @param point
|
||||
* @param distanceSquared
|
||||
* @param edgeNormal
|
||||
*/
|
||||
public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2): {
|
||||
distanceSquared: number;
|
||||
edgeNormal: Vector2;
|
||||
closestPoint: Vector2
|
||||
} {
|
||||
const res = {
|
||||
distanceSquared: Number.MAX_VALUE,
|
||||
edgeNormal: Vector2.zero,
|
||||
closestPoint: Vector2.zero,
|
||||
};
|
||||
|
||||
let tempDistanceSquared = 0;
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
let j = i + 1;
|
||||
if (j === points.length)
|
||||
j = 0;
|
||||
|
||||
const closest = ShapeCollisionsCircle.closestPointOnLine(points[i], points[j], point);
|
||||
tempDistanceSquared = Vector2.sqrDistance(point, closest);
|
||||
|
||||
if (tempDistanceSquared < res.distanceSquared) {
|
||||
res.distanceSquared = tempDistanceSquared;
|
||||
res.closestPoint = closest;
|
||||
|
||||
// 求直线的法线
|
||||
const line = points[j].sub(points[i]);
|
||||
res.edgeNormal.x = line.y;
|
||||
res.edgeNormal.y = -line.x;
|
||||
}
|
||||
}
|
||||
|
||||
res.edgeNormal = res.edgeNormal.normalize();
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转原始点并复制旋转的值到旋转的点
|
||||
* @param radians
|
||||
* @param originalPoints
|
||||
* @param rotatedPoints
|
||||
*/
|
||||
public static rotatePolygonVerts(radians: number, originalPoints: Vector2[], rotatedPoints: Vector2[]) {
|
||||
let cos = Math.cos(radians);
|
||||
let sin = Math.sin(radians);
|
||||
|
||||
for (let i = 0; i < originalPoints.length; i++) {
|
||||
let position = originalPoints[i];
|
||||
rotatedPoints[i] = new Vector2(position.x * cos + position.y * -sin, position.x * sin + position.y * cos);
|
||||
}
|
||||
}
|
||||
|
||||
public recalculateBounds(collider: Collider) {
|
||||
this.center = collider.localOffset;
|
||||
|
||||
if (collider.shouldColliderScaleAndRotateWithTransform) {
|
||||
let hasUnitScale = true;
|
||||
const tempMat = new Matrix2D();
|
||||
const combinedMatrix = new Matrix2D();
|
||||
|
||||
Matrix2D.createTranslation(-this._polygonCenter.x, -this._polygonCenter.y, combinedMatrix);
|
||||
|
||||
const scale = collider.entity.transform.scale;
|
||||
if (!scale.equals(Vector2.one)) {
|
||||
Matrix2D.createScale(scale.x, scale.y, tempMat);
|
||||
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
|
||||
hasUnitScale = false;
|
||||
|
||||
const scaledOffset = collider.localOffset.multiply(scale);
|
||||
this.center = scaledOffset;
|
||||
}
|
||||
|
||||
const rotation = collider.entity.transform.rotationDegrees;
|
||||
if (rotation !== 0) {
|
||||
const offsetLength = hasUnitScale ? collider._localOffsetLength : collider.localOffset.multiply(scale).magnitude();
|
||||
const offsetAngle = Math.atan2(collider.localOffset.y * scale.y, collider.localOffset.x * scale.x) * MathHelper.Rad2Deg;
|
||||
this.center = MathHelper.pointOnCircle(Vector2.zero, offsetLength, rotation + offsetAngle);
|
||||
Matrix2D.createRotation(MathHelper.Deg2Rad * rotation, tempMat);
|
||||
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
|
||||
}
|
||||
|
||||
Matrix2D.createTranslation(this._polygonCenter.x + collider.transform.position.x + this.center.x, this._polygonCenter.y + collider.transform.position.y + this.center.y, tempMat);
|
||||
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
|
||||
|
||||
this.points = this._originalPoints.map(p => p.transform(combinedMatrix));
|
||||
this.isUnrotated = rotation === 0;
|
||||
if (collider._isRotationDirty) this._areEdgeNormalsDirty = true;
|
||||
}
|
||||
|
||||
this.position = collider.transform.position.add(this.center);
|
||||
this.bounds = Rectangle.rectEncompassingPoints(this.points).offset(this.position.x, this.position.y);
|
||||
}
|
||||
|
||||
public overlaps(other: Shape) {
|
||||
let result = new Out<CollisionResult>();
|
||||
if (other instanceof Polygon)
|
||||
return ShapeCollisionsPolygon.polygonToPolygon(this, other, result);
|
||||
|
||||
if (other instanceof Circle) {
|
||||
if (ShapeCollisionsCircle.circleToPolygon(other, this, result)) {
|
||||
result.value.invertResult();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new Error(`overlaps of Pologon to ${other} are not supported`);
|
||||
}
|
||||
|
||||
public collidesWithShape(other: Shape, result: Out<CollisionResult>): boolean {
|
||||
if (other instanceof Polygon) {
|
||||
return ShapeCollisionsPolygon.polygonToPolygon(this, other, result);
|
||||
}
|
||||
|
||||
if (other instanceof Circle) {
|
||||
if (ShapeCollisionsCircle.circleToPolygon(other, this, result)) {
|
||||
result.value.invertResult();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new Error(`overlaps of Polygon to ${other} are not supported`);
|
||||
}
|
||||
|
||||
public collidesWithLine(start: Vector2, end: Vector2, hit: Out<RaycastHit>): boolean {
|
||||
return ShapeCollisionsLine.lineToPoly(start, end, this, hit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本质上,这个算法所做的就是从一个点发射一条射线。
|
||||
* 如果它与奇数条多边形边相交,我们就知道它在多边形内部。
|
||||
* @param point
|
||||
*/
|
||||
public containsPoint(point: Vector2) {
|
||||
// 将点归一化到多边形坐标空间中
|
||||
point = point.sub(this.position);
|
||||
|
||||
let isInside = false;
|
||||
for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) {
|
||||
if (((this.points[i].y > point.y) !== (this.points[j].y > point.y)) &&
|
||||
(point.x < (this.points[j].x - this.points[i].x) * (point.y - this.points[i].y) / (this.points[j].y - this.points[i].y) +
|
||||
this.points[i].x)) {
|
||||
isInside = !isInside;
|
||||
}
|
||||
}
|
||||
|
||||
return isInside;
|
||||
}
|
||||
|
||||
public pointCollidesWithShape(point: Vector2, result: Out<CollisionResult>): boolean {
|
||||
return ShapeCollisionsPoint.pointToPoly(point, this, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user