Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee7cf4278 | ||
|
|
b9db6f0b40 | ||
|
|
8967cba3c7 | ||
|
|
d04ad2eea9 | ||
|
|
f2d3880a06 | ||
|
|
ec5f70ecfc | ||
|
|
40b3fe7165 | ||
|
|
4095f1e946 | ||
|
|
e219fc47ba | ||
|
|
6e2e7a4af5 | ||
|
|
2e7f764d6c | ||
|
|
ce64de5b3d | ||
|
|
35ca1dd7ea | ||
|
|
8d0ad6b871 | ||
|
|
0aa4791cf7 | ||
|
|
082c2b46d0 | ||
|
|
50420f9052 | ||
|
|
499cbf8a60 | ||
|
|
2e38284d6e | ||
|
|
00cc3a11c6 | ||
|
|
9909a7f7b0 | ||
|
|
3363fca160 | ||
|
|
78e0b09c7a | ||
|
|
caa3ffc8f5 | ||
|
|
323fb6a5fe | ||
|
|
1adc5f1729 | ||
|
|
78079252c9 | ||
|
|
72fdabd099 | ||
|
|
64bd6aa055 | ||
|
|
6329200b84 | ||
|
|
3f3fd16110 | ||
|
|
34f0c4ac2d | ||
|
|
dde04d514e | ||
|
|
ffddadd798 | ||
|
|
5dca337b92 | ||
|
|
131df181e6 | ||
|
|
e207952786 | ||
|
|
3f7ef284fc | ||
|
|
1a41533d15 | ||
|
|
85f7bbbf1a | ||
|
|
ccc603b59f | ||
|
|
8135f99616 | ||
|
|
713f4ae18b | ||
|
|
f3f5d0bbd1 | ||
|
|
4a9e11c480 | ||
|
|
cd94326aad | ||
|
|
a4b971bba0 | ||
|
|
5d1609111c | ||
|
|
d54ccaf629 | ||
|
|
6adea240e2 | ||
|
|
3486d403d2 | ||
|
|
f533186c8d | ||
|
|
0beadf8e5a | ||
|
|
8bc06f0476 | ||
|
|
8fd8f74b27 | ||
|
|
9fc9b60de5 | ||
|
|
2a026726db | ||
|
|
f9a99f8b09 | ||
|
|
95b8dc765d | ||
|
|
052d6e25e7 | ||
|
|
fd82486bbc | ||
|
|
ef07f7555f | ||
|
|
67859b7be0 | ||
|
|
1e5ddadd00 | ||
|
|
7bab76d765 | ||
|
|
416f243bda | ||
|
|
77ad112f67 | ||
|
|
dc3d639824 | ||
|
|
13a001c258 | ||
|
|
85bdd97d48 | ||
|
|
3d9c8699e7 | ||
|
|
ea482dab48 | ||
|
|
96a41eb2cc | ||
|
|
e5dfb20aa2 | ||
|
|
fe308d35f2 | ||
|
|
219b90fc5d | ||
|
|
44e2ca07e5 | ||
|
|
79d684caae | ||
|
|
4db8734a34 | ||
|
|
8b7baf7f86 | ||
|
|
28145e876f | ||
|
|
53625cf87b | ||
|
|
88f9779dd0 | ||
|
|
561a44b26b | ||
|
|
26068aaf6f | ||
|
|
fb0f9dc608 | ||
|
|
6c44d38c10 | ||
|
|
da3ab02a8d | ||
|
|
9c7703eb2d | ||
|
|
84922794fb | ||
|
|
1dcce99d87 | ||
|
|
fd62aa469a | ||
|
|
63d307b95a | ||
|
|
9f12971eda | ||
|
|
47edd4f583 | ||
|
|
e94b7b23e4 | ||
|
|
5c77e6c56d | ||
|
|
522cbe0e6e | ||
|
|
f7061f360d | ||
|
|
16cdfa0426 | ||
|
|
74bd0c161f | ||
|
|
da595e1eb9 | ||
|
|
5277c0de7d | ||
|
|
b3c85e00f9 | ||
|
|
0748652a8d | ||
|
|
9fa0442b20 |
4
.fleet/settings.json
Normal file
4
.fleet/settings.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"toolchains": [],
|
||||
"backend.maxHeapSizeMb": 896
|
||||
}
|
||||
34
.github/workflows/node.js.yml
vendored
34
.github/workflows/node.js.yml
vendored
@@ -1,34 +0,0 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [8.x]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: cmd
|
||||
working-directory: ./source
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
- run: npm run build --if-present
|
||||
- run: gulp build
|
||||
85
.gitignore
vendored
85
.gitignore
vendored
@@ -1,10 +1,75 @@
|
||||
/source/node_modules
|
||||
/demo/bin-debug
|
||||
/demo/bin-release
|
||||
/.idea
|
||||
/.vscode
|
||||
/demo_wxgame
|
||||
/demo/.wing
|
||||
/demo/.idea
|
||||
/demo/.vscode
|
||||
/source/docs
|
||||
# 依赖目录
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# 构建输出
|
||||
bin/
|
||||
dist/
|
||||
wasm/
|
||||
*.tgz
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# IDE 配置
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# 环境配置
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# 测试覆盖率
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# Rust 构建文件
|
||||
src/wasm/*/target/
|
||||
src/wasm/*/pkg/
|
||||
Cargo.lock
|
||||
|
||||
# 包管理器锁文件(保留npm的,忽略其他的)
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# 文档生成
|
||||
docs/api/
|
||||
docs/build/
|
||||
|
||||
# 备份文件
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# 演示项目构建产物
|
||||
/demo/bin-debug/
|
||||
/demo/bin-release/
|
||||
/demo/.wing/
|
||||
/demo/.idea/
|
||||
/demo/.vscode/
|
||||
/demo_wxgame/
|
||||
|
||||
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -1,15 +0,0 @@
|
||||
[submodule "demo/egret_demo"]
|
||||
path = demo/egret_demo
|
||||
url = https://github.com/esengine/ecs-egret-demo
|
||||
[submodule "demo/laya_demo"]
|
||||
path = demo/laya_demo
|
||||
url = https://github.com/esengine/ecs-laya-demo.git
|
||||
[submodule "extensions/ecs-star"]
|
||||
path = extensions/ecs-star
|
||||
url = https://github.com/esengine/ecs-astar
|
||||
[submodule "extensions/behaviourTree-ai"]
|
||||
path = extensions/behaviourTree-ai
|
||||
url = https://github.com/esengine/BehaviourTree-ai
|
||||
[submodule "extensions/ecs-tween"]
|
||||
path = extensions/ecs-tween
|
||||
url = https://github.com/esengine/ecs-tween
|
||||
46
.npmignore
Normal file
46
.npmignore
Normal file
@@ -0,0 +1,46 @@
|
||||
# 源代码文件
|
||||
src/
|
||||
tsconfig*.json
|
||||
*.ts
|
||||
!bin/**/*.d.ts
|
||||
|
||||
# 开发文件
|
||||
dev-bin/
|
||||
scripts/
|
||||
.vscode/
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# 测试文件
|
||||
**/*.test.*
|
||||
**/*.spec.*
|
||||
**/test/
|
||||
**/tests/
|
||||
|
||||
# 构建缓存
|
||||
node_modules/
|
||||
*.log
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Rust 构建文件(保留编译后的WASM)
|
||||
src/wasm/rust-ecs-core/target/
|
||||
src/wasm/rust-ecs-core/Cargo.lock
|
||||
src/wasm/rust-ecs-core/pkg/
|
||||
!bin/wasm/
|
||||
|
||||
# 文档草稿
|
||||
docs/draft/
|
||||
*.draft.md
|
||||
|
||||
# 编辑器文件
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 环境文件
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
485
README.md
485
README.md
@@ -1,231 +1,326 @@
|
||||
这是一套ecs游戏框架,里面包含ECS框架用于管理场景实体,一些常用2D碰撞检测及游戏中常用的工具
|
||||
# ECS Framework
|
||||
|
||||
# 项目规划及讨论
|
||||
- [点击参与及查看](https://github.com/esengine/ecs-framework/discussions/36)
|
||||
[](https://badge.fury.io/js/%40esengine%2Fecs-framework)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## 交流群
|
||||
点击链接加入群聊【ecs游戏框架交流】:https://jq.qq.com/?_wv=1027&k=29w1Nud6
|
||||
一个专业级的 TypeScript ECS(Entity-Component-System)框架,采用现代化架构设计,专为高性能游戏开发打造。
|
||||
|
||||
## 快速开始
|
||||
- 初始化框架
|
||||
```typescript
|
||||
es.Core.create();
|
||||
```
|
||||
- 派发帧事件
|
||||
```typescript
|
||||
es.Core.emitter.emit(es.CoreEvents.frameUpdated);
|
||||
```
|
||||
完成以上对框架的初始化,您还需要一个默认场景并激活它,新建一个场景类并继承`es.Scene`
|
||||
## ✨ 核心特性
|
||||
|
||||
```typescript
|
||||
class MainScene extends es.Scene { }
|
||||
- 🏗️ **现代化 ECS 架构** - 完整的实体组件系统,提供清晰的代码结构
|
||||
- 📡 **类型安全事件系统** - 增强的事件总线,支持异步事件、优先级、批处理和装饰器
|
||||
- ⏰ **定时器管理系统** - 完整的定时器管理,支持延迟和重复任务
|
||||
- 🔍 **智能查询系统** - 支持复杂的实体查询,流式API设计
|
||||
- ⚡ **高性能优化** - 组件索引、Archetype系统、脏标记机制三重优化
|
||||
- 🛠️ **开发者友好** - 完整的TypeScript支持,丰富的调试工具
|
||||
- 📦 **轻量级设计** - 最小化依赖,适用于各种游戏引擎
|
||||
|
||||
## 📦 安装
|
||||
|
||||
```bash
|
||||
npm install @esengine/ecs-framework
|
||||
```
|
||||
|
||||
- 激活场景
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 基础设置
|
||||
|
||||
```typescript
|
||||
es.Core.scene = new MainScene();
|
||||
import { Core, CoreEvents, Scene } from '@esengine/ecs-framework';
|
||||
|
||||
// 创建 Core 实例
|
||||
const core = Core.create(true); // 开启调试模式
|
||||
|
||||
// 创建场景
|
||||
class GameScene extends Scene {
|
||||
public initialize() {
|
||||
// 场景初始化逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 在游戏循环中更新框架
|
||||
function gameLoop() {
|
||||
Core.emitter.emit(CoreEvents.frameUpdated);
|
||||
}
|
||||
```
|
||||
|
||||
至此框架已完全开始运作,您可以使用框架内所有提供的方法。
|
||||
|
||||
## ECS框架的基本使用
|
||||
|
||||
#### 创建实体
|
||||
|
||||
创建实体一般由场景控制。您需要在场景内进行创建或添加实体(`createEntity` / `addEntity`)
|
||||
|
||||
> 注意:创建实体需要在场景`onStart`方法进行创建,而不是直接在`initialize`方法内创建。`initialize` 只是在构造函数完成后进行调用,此时该场景并未被激活。
|
||||
### 2. 创建实体和组件
|
||||
|
||||
```typescript
|
||||
class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
// 创建一个名为player的实体
|
||||
const player = this.createEntity("player");
|
||||
import { Component, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
// 定义组件
|
||||
class PositionComponent extends Component {
|
||||
constructor(public x: number = 0, public y: number = 0) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
constructor(public dx: number = 0, public dy: number = 0) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建实体
|
||||
const entity = scene.createEntity("Player");
|
||||
entity.addComponent(new PositionComponent(100, 100));
|
||||
entity.addComponent(new VelocityComponent(10, 0));
|
||||
```
|
||||
|
||||
### 3. 创建处理系统
|
||||
|
||||
```typescript
|
||||
import { EntitySystem } from '@esengine/ecs-framework';
|
||||
|
||||
class MovementSystem extends EntitySystem {
|
||||
public process(entities: Entity[]) {
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
const velocity = entity.getComponent(VelocityComponent);
|
||||
|
||||
if (position && velocity) {
|
||||
position.x += velocity.dx;
|
||||
position.y += velocity.dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加系统到场景
|
||||
scene.addEntityProcessor(new MovementSystem());
|
||||
```
|
||||
|
||||
### 4. 实体查询和管理
|
||||
|
||||
```typescript
|
||||
import { EntityManager } from '@esengine/ecs-framework';
|
||||
|
||||
// 使用EntityManager进行高级查询
|
||||
const entityManager = new EntityManager();
|
||||
|
||||
// 查询具有特定组件的实体
|
||||
const movingEntities = entityManager
|
||||
.query()
|
||||
.withAll(PositionComponent, VelocityComponent)
|
||||
.execute();
|
||||
|
||||
// 查询带标签的实体
|
||||
const enemies = entityManager.getEntitiesByTag(1);
|
||||
|
||||
// 批量创建实体
|
||||
const bullets = entityManager.createEntities(100, "bullet");
|
||||
```
|
||||
|
||||
### 5. 事件系统
|
||||
|
||||
```typescript
|
||||
import { EventBus, ECSEventType, EventHandler } from '@esengine/ecs-framework';
|
||||
|
||||
// 获取事件总线
|
||||
const eventBus = entityManager.eventBus;
|
||||
|
||||
// 监听实体创建事件
|
||||
eventBus.onEntityCreated((data) => {
|
||||
console.log(`Entity created: ${data.entityName}`);
|
||||
});
|
||||
|
||||
// 监听组件添加事件
|
||||
eventBus.onComponentAdded((data) => {
|
||||
console.log(`Component ${data.componentType} added to entity ${data.entityId}`);
|
||||
});
|
||||
|
||||
// 使用装饰器自动注册事件监听器
|
||||
class GameManager {
|
||||
@EventHandler(ECSEventType.ENTITY_DESTROYED)
|
||||
onEntityDestroyed(data) {
|
||||
console.log('Entity destroyed:', data.entityName);
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义事件
|
||||
eventBus.emit('player:levelup', { playerId: 123, newLevel: 5 });
|
||||
```
|
||||
|
||||
## 🆚 框架对比
|
||||
|
||||
与其他 TypeScript ECS 框架相比,我们的优势:
|
||||
|
||||
| 特性 | @esengine/ecs-framework | bitecs | ecsy | Miniplex |
|
||||
|------|-------------------------|-------|------|----------|
|
||||
| **TypeScript 支持** | ✅ 原生支持 | ✅ 完整支持 | ⚠️ 部分支持 | ✅ 原生支持 |
|
||||
| **事件系统** | ✅ 类型安全+装饰器 | ❌ 无内置事件系统 | ⚠️ 基础事件 | ✅ 响应式事件 |
|
||||
| **查询系统** | ✅ 智能查询+流式API | ✅ 高性能 | ✅ 基础查询 | ✅ 响应式查询 |
|
||||
| **性能优化** | ✅ 多层优化系统 | ✅ WASM优化 | ⚠️ 基础优化 | ✅ React集成优化 |
|
||||
| **实体管理器** | ✅ 统一管理接口 | ❌ 无统一接口 | ✅ 基础管理 | ✅ 响应式管理 |
|
||||
| **组件索引** | ✅ 哈希+位图索引 | ✅ 原生支持 | ❌ 无索引系统 | ✅ 自动索引 |
|
||||
| **Archetype系统** | ✅ 内置支持 | ✅ 内置支持 | ❌ 无Archetype | ❌ 无Archetype |
|
||||
| **脏标记系统** | ✅ 细粒度追踪 | ⚠️ 基础支持 | ❌ 无脏标记 | ✅ React级追踪 |
|
||||
| **批量操作** | ✅ 全面的批量API | ✅ 批量支持 | ⚠️ 有限支持 | ⚠️ 有限支持 |
|
||||
| **游戏引擎集成** | ✅ 通用设计 | ✅ 通用设计 | ✅ 通用设计 | ⚠️ 主要针对React |
|
||||
| **学习曲线** | 🟢 中等 | 🟡 较陡峭 | 🟢 简单 | 🟡 需要React知识 |
|
||||
| **社区生态** | 🟡 成长中 | 🟢 活跃 | 🟡 稳定 | 🟡 小众但精品 |
|
||||
|
||||
### 为什么选择我们?
|
||||
|
||||
**相比 bitecs**:
|
||||
- 更友好的 TypeScript API,无需手动管理内存
|
||||
- 完整的实体管理器,开发体验更佳
|
||||
- 内置类型安全事件系统,bitecs需要自己实现
|
||||
- 多种索引系统可选,适应不同场景
|
||||
|
||||
**相比 ecsy**:
|
||||
- 现代化的性能优化系统(组件索引、Archetype、脏标记)
|
||||
- 更完整的 TypeScript 类型定义
|
||||
- 增强的事件系统,支持装饰器和异步事件
|
||||
- 活跃的维护和功能更新
|
||||
|
||||
**相比 Miniplex**:
|
||||
- 不依赖 React 生态,可用于任何游戏引擎
|
||||
- 专门针对游戏开发优化
|
||||
- 更轻量级的核心设计
|
||||
- 传统事件模式,更适合游戏开发习惯
|
||||
|
||||
## 📚 核心概念
|
||||
|
||||
### Entity(实体)
|
||||
实体是游戏世界中的基本对象,可以挂载组件和运行系统。
|
||||
|
||||
```typescript
|
||||
// 创建实体
|
||||
const entity = scene.createEntity("Player");
|
||||
|
||||
// 设置实体属性
|
||||
entity.tag = 1;
|
||||
entity.updateOrder = 0;
|
||||
entity.enabled = true;
|
||||
|
||||
// 批量创建实体
|
||||
const entities = scene.createEntities(100, "Enemy");
|
||||
```
|
||||
|
||||
### Component(组件)
|
||||
组件存储数据,定义实体的属性和状态。
|
||||
|
||||
```typescript
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public maxHealth: number = 100;
|
||||
public currentHealth: number = 100;
|
||||
|
||||
public takeDamage(damage: number) {
|
||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
||||
if (this.currentHealth <= 0) {
|
||||
this.entity.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加组件到实体
|
||||
entity.addComponent(new HealthComponent());
|
||||
```
|
||||
|
||||
### 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 添加组件
|
||||
## 🧪 测试
|
||||
|
||||
每个实体都会有多个组件,如果我们需要实体能够拥有一个新的能力,则需要给它添加相对应的组件,组件必须继承 `es.Component` 来标志为组件。
|
||||
```bash
|
||||
# 运行所有测试
|
||||
npm run test
|
||||
|
||||
> 注意:组件一般没有具体逻辑,一般只存放一些属性,逻辑应该由系统进行调用
|
||||
|
||||
```typescript
|
||||
class MovementComponent extends es.Component {
|
||||
// 定义该实体的移动速度
|
||||
public moveSpeed: number = 100;
|
||||
}
|
||||
# 性能基准测试
|
||||
npm run benchmark
|
||||
```
|
||||
|
||||
- 将组件添加至Player实体上(`addComponent`)
|
||||
## 📖 文档
|
||||
|
||||
```typescript
|
||||
class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
// 创建一个名为player的实体,createEntity方法会将创建的实体返回给你
|
||||
const player = this.createEntity("player");
|
||||
// 为实体添加一个移动组件,addComponent会将新建的移动组件返回给你
|
||||
const moveComponent = player.addComponent(new MovementComponent())
|
||||
}
|
||||
}
|
||||
- [快速入门](docs/getting-started.md) - 从零开始学习框架使用
|
||||
- [EntityManager 使用指南](docs/entity-manager-example.md) - 详细了解实体管理器的高级功能
|
||||
- [事件系统使用指南](docs/event-system-example.md) - 学习类型安全事件系统的完整用法
|
||||
- [性能优化指南](docs/performance-optimization.md) - 深入了解三大性能优化系统
|
||||
- [核心概念](docs/core-concepts.md) - 深入了解 ECS 架构和设计原理
|
||||
- [查询系统使用指南](docs/query-system-usage.md) - 学习高性能查询系统的详细用法
|
||||
|
||||
## 🔗 扩展库
|
||||
|
||||
- [路径寻找库](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
|
||||
|
||||
# 运行基准测试
|
||||
node benchmark.js
|
||||
|
||||
# 开发构建 (在source目录)
|
||||
cd source && npm install && npm run build
|
||||
```
|
||||
|
||||
#### 添加系统
|
||||
### 构建要求
|
||||
|
||||
系统里会使用`Matcher`类帮助过滤它所感兴趣的实体并对其进行更新。我们一般定义系统会与对应的组件相匹配,刚我们创建了一个移动组件,则相对应的就会有一个移动系统来处理该类型的组件。
|
||||
- Node.js >= 14.0.0
|
||||
- TypeScript >= 4.0.0
|
||||
|
||||
这里我们使用`es.EntityProcessingSystem`,因为我们要对带有移动组件的实体进行更新。
|
||||
## 📄 许可证
|
||||
|
||||
> 系统也分多种类型,分为管理实体的系统(`es.EntityProcessingSystem`)/管理系统的系统(`es.ProcessingSystem`)
|
||||
本项目采用 [MIT](LICENSE) 许可证。
|
||||
|
||||
```typescript
|
||||
class MoveSystem extends es.EntityProcessingSystem {
|
||||
// 必须实现
|
||||
processEntity(entity: Entity) {
|
||||
// 这里会传入带有MoveComponent的实体
|
||||
// 为什么会传入只带有MoveComponent的实体?
|
||||
// 因为在构造函数中有一个Matcher匹配器,在你初始化MoveSystem的时候需要你传入匹配的对象. 见下方如何定义匹配带MoveComponent的匹配器
|
||||
## 💬 交流群
|
||||
|
||||
// 该方法每帧都会执行,请确保您的操作尽可能的小或者在使用大数据时采用缓存的方法进行获取操作
|
||||
const moveComponent = entity.getComponent(MovementComponent);
|
||||
if (!moveComponent.enabled)
|
||||
return;
|
||||
加入 QQ 群讨论:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
|
||||
|
||||
// 根据moveComponent的数据执行移动的逻辑
|
||||
}
|
||||
}
|
||||
### 🚀 核心性能指标
|
||||
|
||||
```bash
|
||||
实体创建: 640,000+ 个/秒
|
||||
组件查询: O(1) 复杂度(使用索引)
|
||||
内存优化: 30-50% 减少分配
|
||||
批量操作: 显著提升处理效率
|
||||
```
|
||||
|
||||
##### 定义Matcher匹配器匹配所有带MoveComponent的实体
|
||||
### 🎯 性能优化技术
|
||||
|
||||
```typescript
|
||||
// 这里你可以传入多个组件类型
|
||||
const matcher = Matcher.empty().all(MovementComponent);
|
||||
```
|
||||
- **组件索引系统**: 哈希和位图双重索引,支持 O(1) 查询
|
||||
- **Archetype 系统**: 按组件组合分组,减少查询开销
|
||||
- **脏标记机制**: 细粒度变更追踪,避免不必要的计算
|
||||
- **批量操作 API**: 减少函数调用开销,提升大规模操作效率
|
||||
- **智能缓存**: 查询结果缓存和延迟清理机制
|
||||
|
||||
#### 激活系统
|
||||
### 🔧 性能建议
|
||||
|
||||
使用`addEntityProcessor`方法进行添加系统
|
||||
1. **大规模场景**: 使用批量API和组件索引
|
||||
2. **频繁查询**: 启用Archetype系统进行快速筛选
|
||||
3. **实时游戏**: 利用脏标记减少无效更新
|
||||
4. **移动端**: 建议实体数量控制在20,000以内
|
||||
|
||||
```typescript
|
||||
class MainScene extends es.Scene {
|
||||
onStart() {
|
||||
const matcher = Matcher.empty().all(MovementComponent);
|
||||
// 将所有带MoveComponent的实体送到该系统处理
|
||||
this.addEntityProcessor(new MoveSystem(matcher));
|
||||
运行 `npm run benchmark` 查看在您的环境中的具体性能表现。
|
||||
|
||||
// 创建一个名为player的实体,createEntity方法会将创建的实体返回给你
|
||||
const player = this.createEntity("player");
|
||||
// 为实体添加一个移动组件,addComponent会将新建的移动组件返回给你
|
||||
const moveComponent = player.addComponent(new MovementComponent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
至此所有的快速开始的学习到此为止,如果上述运行过程您已看懂,则框架的基本知识你已经掌握,框架内还含有部分内置组件和更高级的用法,您刚所学习的只是框架的冰山一角,如果您需要学习更多的话可以查看源码或查阅更多资料进行学习。
|
||||
|
||||
## 关于 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 = 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<Collider>().collidesWithAny( deltaMovement, collisionResult ) )
|
||||
{
|
||||
// 记录CollisionResult。 您可能需要使用它来添加一些粒子效果或与您的游戏相关的任何其他内容。
|
||||
console.log( `collision result: ${collisionResult}` );
|
||||
}
|
||||
|
||||
// 将实体移到新位置。 已经调整了deltaMovement为我们解决冲突。
|
||||
entity.position = Vector2.add(entity.position, deltaMovement);
|
||||
```
|
||||
|
||||
如果您需要对碰撞发生时的情况进行更多控制,则也可以手动检查是否与其他collider发生碰撞。 请注意,执行此操作时,deltaMovement不会为您调整。 解决冲突时,您需要考虑最小平移矢量。
|
||||
|
||||
```ts
|
||||
let collisionResult = null;
|
||||
|
||||
// 进行检查以查看entity.getComponent<Collider>是否与一些其他Collider发生碰撞
|
||||
if( entity.getComponent<Collider>().collidesWith( someOtherCollider, deltaMovement, collisionResult ) )
|
||||
{
|
||||
// 将实体移动到与命中Collider相邻的位置,然后记录CollisionResult
|
||||
entity.position = Vector2.add(entity.position, Vector2.substract(deltaMovement, collisionResult.minimumTranslationVector));
|
||||
console.log( `collision result: ${collisionResult}` );
|
||||
}
|
||||
```
|
||||
我们可以使用前面提到的Physics.boxcastBroadphase方法或更具体地讲,将自身排除在查询之外的版本,使上述示例更进一步。 该方法将为我们提供场景中所有在我们附近的collider,然后我们可以使用这些对撞机进行实际的碰撞检查。
|
||||
|
||||
```ts
|
||||
// 在我们自身以外的位置获取可能与之重叠的任何东西
|
||||
let neighborColliders = Physics.boxcastBroadphaseExcludingSelf( entity.getComponent<Collider>() );
|
||||
|
||||
// 遍历并检查每个对撞机是否重叠
|
||||
for( let collider of neighborColliders )
|
||||
{
|
||||
if( entity.getComponent<Collider>().overlaps( collider ) )
|
||||
console.log( `我们正在重叠一个collider : ${collider}` );
|
||||
}
|
||||
```
|
||||
|
||||
### 示例地址
|
||||
|
||||
#### [laya-demo](https://github.com/esengine/ecs-laya-demo)
|
||||
#### [egret-demo](https://github.com/esengine/ecs-egret-demo)
|
||||
|
||||
## 扩展库
|
||||
|
||||
#### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar)
|
||||
#### [基于ecs-framework开发的AI(BehaviourTree、UtilityAI)系统](https://github.com/esengine/BehaviourTree-ai))
|
||||
|
||||
## 关于用ecs框架(typescript/javascript)
|
||||
ecs 是功能强大的实体组件系统。typescript与其他语言不同,因此我对ecs的设计尽可能的支持typescript特性。虽然ecs拥有标准实体组件系统,但在细节上有很大不同。创建标准ecs通常处于原始速度、缓存位置和其他性能原因。使用typescript,我们没有struct,因为没有必要匹配标准实体组件系统的设计方式,因为我们对内存布局没有那种控制。
|
||||
|
||||
ecs更灵活,可以更好的集中、组织、排序和过滤游戏中的对象。ecs让您拥有轻量级实体和组件,这些组件可以由系统批量处理。
|
||||
例如,您在制作一个射手,您可能会有几十到几百个子弹,这些作为轻量级实体由系统批量处理。
|
||||
|
||||
所以ecs在设计当中拥有四种重要类型:世界(Scene),过滤器(Matcher),系统(System)和实体(Entity)
|
||||
|
||||
## [世界(Scene)](https://github.com/esengine/egret-framework/wiki/%E5%9C%BA%E6%99%AF-Scene)
|
||||
Scene是ecs包含系统和实体最外面的容器。
|
||||
|
||||
## 实体(Entity)
|
||||
实体只由系统处理。
|
||||
|
||||
## 组件(Component)
|
||||
组件应该只包含数据而没有逻辑代码。对数据进行逻辑是系统的工作。
|
||||
|
||||
## 系统(System)
|
||||
ecs中的系统会不断的更新实体。系统使用过滤器选择某些实体,然后仅更新那些选择的实体。
|
||||
---
|
||||
|
||||
**ECS Framework** - 让游戏开发更简单、更高效!
|
||||
60
SECURITY.md
60
SECURITY.md
@@ -1,21 +1,53 @@
|
||||
# Security Policy
|
||||
# 安全政策
|
||||
|
||||
## Supported Versions
|
||||
## 支持的版本
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
我们为以下版本提供安全更新:
|
||||
|
||||
| Version | Supported |
|
||||
| 版本 | 支持状态 |
|
||||
| ------- | ------------------ |
|
||||
| 5.1.x | :white_check_mark: |
|
||||
| 5.0.x | :x: |
|
||||
| 4.0.x | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
| 2.0.x | :white_check_mark: |
|
||||
| 1.0.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
## 报告漏洞
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
如果您发现了安全漏洞,请通过以下方式报告:
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
### 报告渠道
|
||||
|
||||
- **邮箱**: [安全邮箱将在实际部署时提供]
|
||||
- **GitHub**: 创建私有安全报告(推荐)
|
||||
|
||||
### 报告流程
|
||||
|
||||
1. **不要**在公开的 issue 中报告安全漏洞
|
||||
2. 提供详细的漏洞描述,包括:
|
||||
- 受影响的版本
|
||||
- 复现步骤
|
||||
- 潜在的影响范围
|
||||
- 如果可能,提供修复建议
|
||||
|
||||
### 响应时间
|
||||
|
||||
- **确认收到**: 72小时内
|
||||
- **初步评估**: 1周内
|
||||
- **修复发布**: 根据严重程度,通常在2-4周内
|
||||
|
||||
### 处理流程
|
||||
|
||||
1. 我们会确认漏洞的存在和严重程度
|
||||
2. 开发修复方案并进行测试
|
||||
3. 发布安全更新
|
||||
4. 在修复发布后,会在相关渠道公布漏洞详情
|
||||
|
||||
### 安全最佳实践
|
||||
|
||||
使用 ECS Framework 时,请遵循以下安全建议:
|
||||
|
||||
- 始终使用最新的稳定版本
|
||||
- 定期更新依赖项
|
||||
- 在生产环境中禁用调试模式
|
||||
- 验证所有外部输入数据
|
||||
- 不要在客户端存储敏感信息
|
||||
|
||||
感谢您帮助保持 ECS Framework 的安全性!
|
||||
|
||||
Submodule demo/egret_demo deleted from 7fd9460053
Submodule demo/laya_demo deleted from e4273ff0f5
622
docs/core-concepts.md
Normal file
622
docs/core-concepts.md
Normal file
@@ -0,0 +1,622 @@
|
||||
# 核心概念
|
||||
|
||||
ECS Framework 基于 Entity-Component-System 架构模式,这是一种高度模块化和可扩展的游戏开发架构。本文档将详细介绍框架的核心概念。
|
||||
|
||||
## ECS 架构概述
|
||||
|
||||
ECS 架构将传统的面向对象设计分解为三个核心部分:
|
||||
|
||||
- **Entity(实体)** - 游戏世界中的对象,包含基本属性如位置、旋转、缩放
|
||||
- **Component(组件)** - 包含数据和行为的功能模块
|
||||
- **System(系统)** - 处理实体集合的逻辑处理单元
|
||||
|
||||
## Core(核心)
|
||||
|
||||
Core 是框架的核心管理类,负责游戏的生命周期管理。
|
||||
|
||||
### 创建和配置
|
||||
|
||||
```typescript
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
|
||||
// 创建核心实例(调试模式)
|
||||
const core = Core.create(true);
|
||||
|
||||
// 创建核心实例(发布模式)
|
||||
const core = Core.create(false);
|
||||
```
|
||||
|
||||
### 事件系统
|
||||
|
||||
```typescript
|
||||
import { CoreEvents } from '@esengine/ecs-framework';
|
||||
|
||||
// 监听核心事件
|
||||
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 '@esengine/ecs-framework';
|
||||
|
||||
// 创建场景
|
||||
const scene = new Scene();
|
||||
scene.name = "GameScene";
|
||||
|
||||
// 设置为当前场景
|
||||
Core.scene = scene;
|
||||
|
||||
// 场景生命周期
|
||||
scene.begin(); // 开始场景
|
||||
scene.update(); // 更新场景
|
||||
scene.end(); // 结束场景
|
||||
```
|
||||
|
||||
### 批量实体管理
|
||||
|
||||
```typescript
|
||||
// 批量创建实体 - 高性能
|
||||
const entities = scene.createEntities(1000, "Enemy");
|
||||
|
||||
// 批量添加实体(延迟缓存清理)
|
||||
entities.forEach(entity => {
|
||||
scene.addEntity(entity, false); // 延迟清理
|
||||
});
|
||||
scene.querySystem.clearCache(); // 手动清理缓存
|
||||
|
||||
// 获取性能统计
|
||||
const stats = scene.getPerformanceStats();
|
||||
console.log(`实体数量: ${stats.entityCount}`);
|
||||
```
|
||||
|
||||
## Entity(实体)
|
||||
|
||||
实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性。
|
||||
|
||||
### 实体的基本属性
|
||||
|
||||
```typescript
|
||||
import { Vector2 } from '@esengine/ecs-framework';
|
||||
|
||||
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 '@esengine/ecs-framework';
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public maxHealth: number = 100;
|
||||
public currentHealth: number = 100;
|
||||
|
||||
public takeDamage(damage: number) {
|
||||
this.currentHealth -= damage;
|
||||
if (this.currentHealth <= 0) {
|
||||
this.entity.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public heal(amount: number) {
|
||||
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 组件生命周期
|
||||
|
||||
```typescript
|
||||
class MyComponent extends Component {
|
||||
public onAddedToEntity() {
|
||||
// 组件被添加到实体时调用
|
||||
console.log("组件已添加到实体:", this.entity.name);
|
||||
}
|
||||
|
||||
public onRemovedFromEntity() {
|
||||
// 组件从实体移除时调用
|
||||
console.log("组件已从实体移除");
|
||||
}
|
||||
|
||||
public onEnabled() {
|
||||
// 组件启用时调用
|
||||
console.log("组件已启用");
|
||||
}
|
||||
|
||||
public onDisabled() {
|
||||
// 组件禁用时调用
|
||||
console.log("组件已禁用");
|
||||
}
|
||||
|
||||
public update() {
|
||||
// 每帧更新(如果组件启用)
|
||||
console.log("组件更新");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 组件管理
|
||||
|
||||
```typescript
|
||||
// 添加组件
|
||||
const health = entity.addComponent(new HealthComponent());
|
||||
|
||||
// 创建并添加组件
|
||||
const movement = entity.createComponent(MovementComponent, 200); // 传递构造参数
|
||||
|
||||
// 获取组件
|
||||
const healthComp = entity.getComponent(HealthComponent);
|
||||
|
||||
// 检查组件是否存在
|
||||
if (entity.hasComponent(HealthComponent)) {
|
||||
// 处理逻辑
|
||||
}
|
||||
|
||||
// 获取或创建组件
|
||||
const weapon = entity.getOrCreateComponent(WeaponComponent);
|
||||
|
||||
// 获取多个同类型组件
|
||||
const allHealthComps = entity.getComponents(HealthComponent);
|
||||
|
||||
// 移除组件
|
||||
entity.removeComponent(healthComp);
|
||||
|
||||
// 按类型移除组件
|
||||
entity.removeComponentByType(HealthComponent);
|
||||
|
||||
// 移除所有组件
|
||||
entity.removeAllComponents();
|
||||
```
|
||||
|
||||
### 组件对象池优化
|
||||
|
||||
```typescript
|
||||
import { Component, ComponentPoolManager } from '@esengine/ecs-framework';
|
||||
|
||||
class BulletComponent extends Component {
|
||||
public damage: number = 10;
|
||||
public speed: number = 300;
|
||||
|
||||
// 对象池重置方法
|
||||
public reset() {
|
||||
this.damage = 10;
|
||||
this.speed = 300;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册组件池
|
||||
ComponentPoolManager.getInstance().registerPool(BulletComponent, 1000);
|
||||
|
||||
// 使用对象池获取组件
|
||||
const bullet = ComponentPoolManager.getInstance().getComponent(BulletComponent);
|
||||
entity.addComponent(bullet);
|
||||
|
||||
// 释放组件回对象池
|
||||
ComponentPoolManager.getInstance().releaseComponent(bullet);
|
||||
|
||||
// 预热组件池
|
||||
ComponentPoolManager.getInstance().preWarmPools({
|
||||
BulletComponent: 1000,
|
||||
EffectComponent: 500
|
||||
});
|
||||
|
||||
// 获取池统计
|
||||
const stats = ComponentPoolManager.getInstance().getPoolStats();
|
||||
console.log('组件池统计:', stats);
|
||||
```
|
||||
|
||||
## 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. 性能优化
|
||||
|
||||
- 使用对象池减少内存分配
|
||||
- 监控性能数据
|
||||
- 合理使用时间缩放
|
||||
|
||||
## 高级性能优化功能
|
||||
|
||||
### 位掩码优化器
|
||||
|
||||
位掩码优化器可以预计算和缓存常用的组件掩码,提升查询性能。
|
||||
|
||||
```typescript
|
||||
import { BitMaskOptimizer } from '@esengine/ecs-framework';
|
||||
|
||||
const optimizer = BitMaskOptimizer.getInstance();
|
||||
|
||||
// 注册组件类型
|
||||
optimizer.registerComponentType(PositionComponent);
|
||||
optimizer.registerComponentType(VelocityComponent);
|
||||
optimizer.registerComponentType(RenderComponent);
|
||||
|
||||
// 预计算常用掩码组合
|
||||
optimizer.precomputeCommonMasks();
|
||||
|
||||
// 获取优化的掩码
|
||||
const positionMask = optimizer.getComponentMask(PositionComponent);
|
||||
const movementMask = optimizer.getCombinedMask([PositionComponent, VelocityComponent]);
|
||||
|
||||
// 掩码操作
|
||||
const hasBothComponents = optimizer.hasAllComponents(entityMask, movementMask);
|
||||
const hasAnyComponent = optimizer.hasAnyComponent(entityMask, movementMask);
|
||||
|
||||
// 获取掩码分析
|
||||
const analysis = optimizer.analyzeMask(entityMask);
|
||||
console.log('掩码包含的组件类型:', analysis.componentTypes);
|
||||
```
|
||||
|
||||
### 延迟索引更新器
|
||||
|
||||
批量更新索引可以显著提升大规模实体操作的性能。
|
||||
|
||||
```typescript
|
||||
import { IndexUpdateBatcher } from '@esengine/ecs-framework';
|
||||
|
||||
const batcher = new IndexUpdateBatcher((updates) => {
|
||||
// 处理批量更新
|
||||
console.log(`批量处理 ${updates.length} 个索引更新`);
|
||||
});
|
||||
|
||||
// 配置批量大小和延迟
|
||||
batcher.configure(100, 16); // 批量大小100,延迟16ms
|
||||
|
||||
// 添加更新任务
|
||||
batcher.addUpdate("add", entity, componentMask);
|
||||
batcher.addUpdate("remove", entity, componentMask);
|
||||
|
||||
// 强制刷新
|
||||
batcher.flush();
|
||||
```
|
||||
|
||||
### 批量操作API
|
||||
|
||||
```typescript
|
||||
// 批量创建实体 - 最高性能
|
||||
const entities = scene.createEntities(10000, "Bullets");
|
||||
|
||||
// 延迟缓存清理
|
||||
entities.forEach(entity => {
|
||||
scene.addEntity(entity, false); // 延迟清理
|
||||
});
|
||||
scene.querySystem.clearCache(); // 手动清理
|
||||
|
||||
// 批量查询优化
|
||||
const movingEntities = scene.getEntitiesWithComponents([PositionComponent, VelocityComponent]);
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
ECS Framework 提供了完整的实体组件系统架构:
|
||||
|
||||
- **Core** 管理游戏生命周期和全局功能
|
||||
- **Entity** 作为游戏对象的基础容器
|
||||
- **Component** 实现具体的功能模块,支持对象池优化
|
||||
- **System** 处理游戏逻辑
|
||||
- **Scene** 管理游戏世界状态,支持批量操作
|
||||
- **高级优化** 位掩码优化器、组件对象池、批量操作等
|
||||
|
||||
通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。
|
||||
482
docs/entity-guide.md
Normal file
482
docs/entity-guide.md
Normal file
@@ -0,0 +1,482 @@
|
||||
# 实体使用指南
|
||||
|
||||
本指南详细介绍 ECS Framework 中实体(Entity)的所有功能和使用方法。
|
||||
|
||||
## 实体概述
|
||||
|
||||
实体(Entity)是 ECS 架构中的核心概念之一,它作为组件的容器存在。实体本身不包含游戏逻辑,所有功能都通过添加不同的组件来实现。
|
||||
|
||||
### 实体的特点
|
||||
|
||||
- **轻量级容器**:实体只是组件的载体,不包含具体的游戏逻辑
|
||||
- **唯一标识**:每个实体都有唯一的ID和名称
|
||||
- **层次结构**:支持父子关系,可以构建复杂的实体层次
|
||||
- **高性能查询**:基于位掩码的组件查询系统
|
||||
- **生命周期管理**:完整的创建、更新、销毁流程
|
||||
|
||||
## 创建实体
|
||||
|
||||
### 基本创建方式
|
||||
|
||||
```typescript
|
||||
import { Scene } from '@esengine/ecs-framework';
|
||||
|
||||
// 通过场景创建实体
|
||||
const scene = new Scene();
|
||||
const entity = scene.createEntity("Player");
|
||||
|
||||
console.log(entity.name); // "Player"
|
||||
console.log(entity.id); // 唯一的数字ID
|
||||
```
|
||||
|
||||
### 批量创建实体(推荐)
|
||||
|
||||
```typescript
|
||||
import { Scene } from '@esengine/ecs-framework';
|
||||
|
||||
const scene = new Scene();
|
||||
|
||||
// 批量创建1000个实体 - 高性能
|
||||
const entities = scene.createEntities(1000, "Enemy");
|
||||
|
||||
// 批量配置
|
||||
entities.forEach((entity, index) => {
|
||||
entity.tag = 2; // 敌人标签
|
||||
// 添加组件...
|
||||
});
|
||||
```
|
||||
|
||||
### 使用流式API创建
|
||||
|
||||
```typescript
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
|
||||
// 使用ECS流式API
|
||||
const entity = Core.ecsAPI
|
||||
?.entity("Enemy")
|
||||
.withComponent(new PositionComponent(100, 200))
|
||||
.withComponent(new HealthComponent(50))
|
||||
.withTag(2)
|
||||
.build();
|
||||
```
|
||||
|
||||
## 实体属性
|
||||
|
||||
### 基本属性
|
||||
|
||||
```typescript
|
||||
// 实体名称 - 用于调试和标识
|
||||
entity.name = "Player";
|
||||
|
||||
// 实体ID - 只读,场景内唯一
|
||||
console.log(entity.id); // 例如: 1
|
||||
|
||||
// 标签 - 用于分类和快速查询
|
||||
entity.tag = 1; // 玩家标签
|
||||
entity.tag = 2; // 敌人标签
|
||||
|
||||
// 更新顺序 - 控制实体在系统中的处理优先级
|
||||
entity.updateOrder = 0; // 数值越小优先级越高
|
||||
```
|
||||
|
||||
### 状态控制
|
||||
|
||||
```typescript
|
||||
// 启用状态 - 控制实体是否参与更新和处理
|
||||
entity.enabled = true; // 启用实体
|
||||
entity.enabled = false; // 禁用实体
|
||||
|
||||
// 激活状态 - 控制实体及其子实体的活跃状态
|
||||
entity.active = true; // 激活实体
|
||||
entity.active = false; // 停用实体
|
||||
|
||||
// 检查层次结构中的激活状态
|
||||
if (entity.activeInHierarchy) {
|
||||
// 实体在整个层次结构中都是激活的
|
||||
}
|
||||
|
||||
// 检查销毁状态
|
||||
if (entity.isDestroyed) {
|
||||
// 实体已被销毁
|
||||
}
|
||||
```
|
||||
|
||||
### 更新间隔
|
||||
|
||||
```typescript
|
||||
// 控制实体更新频率
|
||||
entity.updateInterval = 1; // 每帧更新
|
||||
entity.updateInterval = 2; // 每2帧更新一次
|
||||
entity.updateInterval = 5; // 每5帧更新一次
|
||||
```
|
||||
|
||||
## 组件管理
|
||||
|
||||
### 添加组件
|
||||
|
||||
```typescript
|
||||
// 创建并添加组件
|
||||
const healthComponent = entity.addComponent(new HealthComponent(100));
|
||||
|
||||
// 使用工厂方法创建组件
|
||||
const positionComponent = entity.createComponent(PositionComponent, 100, 200);
|
||||
|
||||
// 批量添加组件
|
||||
const components = entity.addComponents([
|
||||
new PositionComponent(0, 0),
|
||||
new VelocityComponent(50, 0),
|
||||
new HealthComponent(100)
|
||||
]);
|
||||
```
|
||||
|
||||
### 获取组件
|
||||
|
||||
```typescript
|
||||
// 获取单个组件
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
if (health) {
|
||||
console.log(`当前生命值: ${health.currentHealth}`);
|
||||
}
|
||||
|
||||
// 获取或创建组件(如果不存在则创建)
|
||||
const position = entity.getOrCreateComponent(PositionComponent, 0, 0);
|
||||
|
||||
// 获取多个同类型组件(如果组件可以重复添加)
|
||||
const allHealthComponents = entity.getComponents(HealthComponent);
|
||||
```
|
||||
|
||||
### 检查组件
|
||||
|
||||
```typescript
|
||||
// 检查是否拥有指定组件
|
||||
if (entity.hasComponent(HealthComponent)) {
|
||||
// 实体拥有生命值组件
|
||||
}
|
||||
|
||||
// 检查组件掩码(高性能)
|
||||
const mask = entity.componentMask;
|
||||
console.log(`组件掩码: ${mask.toString(2)}`);
|
||||
```
|
||||
|
||||
### 移除组件
|
||||
|
||||
```typescript
|
||||
// 移除指定组件实例
|
||||
const healthComponent = entity.getComponent(HealthComponent);
|
||||
if (healthComponent) {
|
||||
entity.removeComponent(healthComponent);
|
||||
}
|
||||
|
||||
// 按类型移除组件
|
||||
const removedHealth = entity.removeComponentByType(HealthComponent);
|
||||
|
||||
// 批量移除组件
|
||||
const removedComponents = entity.removeComponentsByTypes([
|
||||
HealthComponent,
|
||||
VelocityComponent
|
||||
]);
|
||||
|
||||
// 移除所有组件
|
||||
entity.removeAllComponents();
|
||||
```
|
||||
|
||||
## 层次结构管理
|
||||
|
||||
### 父子关系
|
||||
|
||||
```typescript
|
||||
// 创建父子实体
|
||||
const player = scene.createEntity("Player");
|
||||
const weapon = scene.createEntity("Weapon");
|
||||
const shield = scene.createEntity("Shield");
|
||||
|
||||
// 添加子实体
|
||||
player.addChild(weapon);
|
||||
player.addChild(shield);
|
||||
|
||||
// 获取父实体
|
||||
console.log(weapon.parent === player); // true
|
||||
|
||||
// 获取所有子实体
|
||||
const children = player.children;
|
||||
console.log(children.length); // 2
|
||||
|
||||
// 获取子实体数量
|
||||
console.log(player.childCount); // 2
|
||||
```
|
||||
|
||||
### 查找子实体
|
||||
|
||||
```typescript
|
||||
// 按名称查找子实体
|
||||
const weapon = player.findChild("Weapon");
|
||||
|
||||
// 递归查找子实体
|
||||
const deepChild = player.findChild("DeepChild", true);
|
||||
|
||||
// 按标签查找子实体
|
||||
const enemies = player.findChildrenByTag(2); // 查找所有敌人标签的子实体
|
||||
|
||||
// 递归按标签查找
|
||||
const allEnemies = player.findChildrenByTag(2, true);
|
||||
```
|
||||
|
||||
### 层次结构操作
|
||||
|
||||
```typescript
|
||||
// 移除子实体
|
||||
const removed = player.removeChild(weapon);
|
||||
|
||||
// 移除所有子实体
|
||||
player.removeAllChildren();
|
||||
|
||||
// 获取根实体
|
||||
const root = weapon.getRoot();
|
||||
|
||||
// 检查祖先关系
|
||||
if (player.isAncestorOf(weapon)) {
|
||||
// player 是 weapon 的祖先
|
||||
}
|
||||
|
||||
// 检查后代关系
|
||||
if (weapon.isDescendantOf(player)) {
|
||||
// weapon 是 player 的后代
|
||||
}
|
||||
|
||||
// 获取实体在层次结构中的深度
|
||||
const depth = weapon.getDepth(); // 从根实体开始计算的深度
|
||||
```
|
||||
|
||||
### 遍历子实体
|
||||
|
||||
```typescript
|
||||
// 遍历直接子实体
|
||||
player.forEachChild((child, index) => {
|
||||
console.log(`子实体 ${index}: ${child.name}`);
|
||||
});
|
||||
|
||||
// 递归遍历所有子实体
|
||||
player.forEachChild((child, index) => {
|
||||
console.log(`子实体 ${index}: ${child.name} (深度: ${child.getDepth()})`);
|
||||
}, true);
|
||||
```
|
||||
|
||||
## 实体生命周期
|
||||
|
||||
### 更新循环
|
||||
|
||||
```typescript
|
||||
// 手动更新实体(通常由场景自动调用)
|
||||
entity.update();
|
||||
|
||||
// 实体会自动调用所有组件的update方法
|
||||
class MyComponent extends Component {
|
||||
public update(): void {
|
||||
// 组件更新逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 销毁实体
|
||||
|
||||
```typescript
|
||||
// 销毁实体
|
||||
entity.destroy();
|
||||
|
||||
// 检查是否已销毁
|
||||
if (entity.isDestroyed) {
|
||||
console.log("实体已被销毁");
|
||||
}
|
||||
|
||||
// 销毁实体时会自动:
|
||||
// 1. 移除所有组件
|
||||
// 2. 从父实体中移除
|
||||
// 3. 销毁所有子实体
|
||||
// 4. 从场景中移除
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 组件缓存
|
||||
|
||||
```typescript
|
||||
// 预热组件缓存(提高后续访问性能)
|
||||
entity.warmUpComponentCache();
|
||||
|
||||
// 清理组件缓存
|
||||
entity.cleanupComponentCache();
|
||||
|
||||
// 获取缓存统计信息
|
||||
const cacheStats = entity.getComponentCacheStats();
|
||||
console.log(`缓存命中率: ${cacheStats.cacheStats.hitRate}`);
|
||||
console.log(`组件访问统计:`, cacheStats.accessStats);
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
```typescript
|
||||
// 批量添加组件(比单个添加更高效)
|
||||
const components = entity.addComponents([
|
||||
new PositionComponent(0, 0),
|
||||
new VelocityComponent(50, 0),
|
||||
new HealthComponent(100)
|
||||
]);
|
||||
|
||||
// 批量移除组件
|
||||
const removed = entity.removeComponentsByTypes([
|
||||
HealthComponent,
|
||||
VelocityComponent
|
||||
]);
|
||||
```
|
||||
|
||||
## 调试和监控
|
||||
|
||||
### 调试信息
|
||||
|
||||
```typescript
|
||||
// 获取详细的调试信息
|
||||
const debugInfo = entity.getDebugInfo();
|
||||
console.log("实体调试信息:", debugInfo);
|
||||
|
||||
// 调试信息包含:
|
||||
// - 基本属性(名称、ID、状态等)
|
||||
// - 组件信息(数量、类型、掩码等)
|
||||
// - 层次结构信息(父子关系、深度等)
|
||||
// - 性能统计(缓存命中率、访问统计等)
|
||||
```
|
||||
|
||||
### 实体比较
|
||||
|
||||
```typescript
|
||||
// 比较两个实体的优先级
|
||||
const result = entity1.compareTo(entity2);
|
||||
if (result < 0) {
|
||||
// entity1 优先级更高
|
||||
} else if (result > 0) {
|
||||
// entity2 优先级更高
|
||||
} else {
|
||||
// 优先级相同
|
||||
}
|
||||
|
||||
// 实体的字符串表示
|
||||
console.log(entity.toString()); // "Entity[Player:1]"
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 合理使用标签
|
||||
|
||||
```typescript
|
||||
// 定义标签常量
|
||||
const Tags = {
|
||||
PLAYER: 1,
|
||||
ENEMY: 2,
|
||||
PROJECTILE: 3,
|
||||
PICKUP: 4
|
||||
} as const;
|
||||
|
||||
// 使用标签进行分类
|
||||
player.tag = Tags.PLAYER;
|
||||
enemy.tag = Tags.ENEMY;
|
||||
```
|
||||
|
||||
### 2. 优化更新顺序
|
||||
|
||||
```typescript
|
||||
// 设置合理的更新顺序
|
||||
player.updateOrder = 0; // 玩家最先更新
|
||||
enemy.updateOrder = 1; // 敌人其次
|
||||
projectile.updateOrder = 2; // 投射物最后
|
||||
```
|
||||
|
||||
### 3. 合理使用层次结构
|
||||
|
||||
```typescript
|
||||
// 创建复合实体
|
||||
const tank = scene.createEntity("Tank");
|
||||
const turret = scene.createEntity("Turret");
|
||||
const barrel = scene.createEntity("Barrel");
|
||||
|
||||
// 建立层次关系
|
||||
tank.addChild(turret);
|
||||
turret.addChild(barrel);
|
||||
|
||||
// 这样可以通过控制父实体来影响整个层次结构
|
||||
tank.active = false; // 整个坦克都会被停用
|
||||
```
|
||||
|
||||
### 4. 组件缓存优化
|
||||
|
||||
```typescript
|
||||
// 对于频繁访问的组件,预热缓存
|
||||
entity.warmUpComponentCache();
|
||||
|
||||
// 定期清理不常用的缓存
|
||||
setInterval(() => {
|
||||
entity.cleanupComponentCache();
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
### 5. 避免内存泄漏
|
||||
|
||||
```typescript
|
||||
// 确保正确销毁实体
|
||||
if (entity.isDestroyed) {
|
||||
return; // 避免操作已销毁的实体
|
||||
}
|
||||
|
||||
// 在适当的时候销毁不需要的实体
|
||||
if (enemy.getComponent(HealthComponent)?.isDead()) {
|
||||
enemy.destroy();
|
||||
}
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 实体可以在不同场景间移动吗?
|
||||
|
||||
A: 不可以。实体与场景紧密绑定,如果需要在场景间传递数据,应该序列化实体的组件数据,然后在新场景中重新创建。
|
||||
|
||||
### Q: 如何实现实体的位置、旋转、缩放?
|
||||
|
||||
A: 框架本身不提供这些属性,需要通过组件来实现:
|
||||
|
||||
```typescript
|
||||
class TransformComponent extends Component {
|
||||
public position = { x: 0, y: 0 };
|
||||
public rotation = 0;
|
||||
public scale = { x: 1, y: 1 };
|
||||
}
|
||||
|
||||
const transform = entity.addComponent(new TransformComponent());
|
||||
transform.position.x = 100;
|
||||
transform.rotation = Math.PI / 4;
|
||||
```
|
||||
|
||||
### Q: 实体的更新顺序如何影响性能?
|
||||
|
||||
A: 更新顺序主要影响游戏逻辑的执行顺序,对性能影响较小。但合理的更新顺序可以避免一些逻辑问题,比如确保输入处理在移动之前执行。
|
||||
|
||||
### Q: 如何处理大量实体的性能问题?
|
||||
|
||||
A:
|
||||
1. 使用对象池重用实体
|
||||
2. 合理使用组件缓存
|
||||
3. 避免不必要的组件查询
|
||||
4. 使用批量操作
|
||||
5. 定期清理销毁的实体
|
||||
|
||||
```typescript
|
||||
// 使用对象池
|
||||
class EntityPool extends Pool<Entity> {
|
||||
protected createObject(): Entity {
|
||||
return scene.createEntity("PooledEntity");
|
||||
}
|
||||
|
||||
protected resetObject(entity: Entity): void {
|
||||
entity.removeAllComponents();
|
||||
entity.active = true;
|
||||
entity.enabled = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
421
docs/entity-manager-example.md
Normal file
421
docs/entity-manager-example.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# EntityManager 使用指南
|
||||
|
||||
EntityManager 是 ECS Framework 的核心管理系统,提供统一的实体管理、高性能查询和自动优化功能。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 创建实体管理器
|
||||
|
||||
```typescript
|
||||
import { EntityManager, Scene } from '@esengine/ecs-framework';
|
||||
|
||||
// 通常在游戏管理器中创建
|
||||
const scene = new Scene();
|
||||
const entityManager = new EntityManager(scene);
|
||||
```
|
||||
|
||||
### 基础实体操作
|
||||
|
||||
```typescript
|
||||
// 创建单个实体
|
||||
const player = entityManager.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(100, 100));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
player.tag = "player";
|
||||
|
||||
// 批量创建实体
|
||||
const enemies = entityManager.createEntities(50, "Enemy");
|
||||
enemies.forEach((enemy, index) => {
|
||||
enemy.addComponent(new PositionComponent(
|
||||
Math.random() * 800,
|
||||
Math.random() * 600
|
||||
));
|
||||
enemy.addComponent(new HealthComponent(30));
|
||||
enemy.tag = "enemy";
|
||||
});
|
||||
|
||||
// 销毁实体
|
||||
entityManager.destroyEntity(player);
|
||||
```
|
||||
|
||||
## 高性能查询系统
|
||||
|
||||
EntityManager 提供多种查询方式,自动选择最优的查询策略。
|
||||
|
||||
### 基础查询
|
||||
|
||||
```typescript
|
||||
// 通过ID查询(O(1))
|
||||
const entity = entityManager.getEntity(123);
|
||||
|
||||
// 通过名称查询(O(1) 哈希查找)
|
||||
const player = entityManager.getEntityByName("Player");
|
||||
|
||||
// 通过标签查询(O(1) 索引查找)
|
||||
const enemies = entityManager.getEntitiesByTag("enemy");
|
||||
|
||||
// 组件查询(使用O(1)组件索引)
|
||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||
|
||||
// 多组件查询(使用Archetype优化)
|
||||
const movingEntities = entityManager.getEntitiesWithComponents([
|
||||
PositionComponent,
|
||||
VelocityComponent
|
||||
]);
|
||||
```
|
||||
|
||||
### 流式查询API
|
||||
|
||||
EntityManager 提供强大的流式查询构建器:
|
||||
|
||||
```typescript
|
||||
// 基础查询构建
|
||||
const results = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, HealthComponent]) // 必须包含这些组件
|
||||
.withoutTag("dead") // 不能有死亡标签
|
||||
.active(true) // 必须激活
|
||||
.execute();
|
||||
|
||||
// 复杂条件查询
|
||||
const livingEnemies = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, HealthComponent])
|
||||
.withTag("enemy")
|
||||
.withoutTag("dead")
|
||||
.where(entity => {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
return health && health.currentHealth > 0;
|
||||
})
|
||||
.execute();
|
||||
|
||||
// 查询变体
|
||||
const firstEnemy = entityManager
|
||||
.query()
|
||||
.withTag("enemy")
|
||||
.first(); // 获取第一个匹配
|
||||
|
||||
const enemyCount = entityManager
|
||||
.query()
|
||||
.withTag("enemy")
|
||||
.count(); // 获取数量
|
||||
|
||||
// 直接处理查询结果
|
||||
entityManager
|
||||
.query()
|
||||
.withAll([HealthComponent])
|
||||
.forEach(entity => {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
if (health.currentHealth <= 0) {
|
||||
entity.addTag("dead");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 高级查询选项
|
||||
|
||||
```typescript
|
||||
// 组合条件查询
|
||||
const combatUnits = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, HealthComponent]) // AND条件
|
||||
.withAny([WeaponComponent, MagicComponent]) // OR条件
|
||||
.without([DeadComponent]) // NOT条件
|
||||
.withTag("combatant")
|
||||
.withoutTag("peaceful")
|
||||
.active(true)
|
||||
.enabled(true)
|
||||
.execute();
|
||||
|
||||
// 使用自定义过滤器
|
||||
const nearbyEnemies = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent])
|
||||
.withTag("enemy")
|
||||
.where(entity => {
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(pos.x - playerPos.x, 2) +
|
||||
Math.pow(pos.y - playerPos.y, 2)
|
||||
);
|
||||
return distance < 100; // 距离玩家100像素内
|
||||
})
|
||||
.execute();
|
||||
```
|
||||
|
||||
## 批量操作
|
||||
|
||||
EntityManager 提供高效的批量操作方法:
|
||||
|
||||
```typescript
|
||||
// 遍历所有实体
|
||||
entityManager.forEachEntity(entity => {
|
||||
// 处理每个实体
|
||||
if (entity.position.x < 0) {
|
||||
entity.position.x = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// 遍历特定组件的实体
|
||||
entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => {
|
||||
if (health.currentHealth <= 0) {
|
||||
entity.addTag("dead");
|
||||
entity.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 批量创建并配置实体
|
||||
const bullets = entityManager.createEntities(100, "Bullet", (bullet, index) => {
|
||||
bullet.addComponent(new PositionComponent(
|
||||
100 + index * 10,
|
||||
100
|
||||
));
|
||||
bullet.addComponent(new VelocityComponent(0, -200));
|
||||
bullet.tag = "projectile";
|
||||
});
|
||||
```
|
||||
|
||||
## 性能优化系统
|
||||
|
||||
EntityManager 内置了三个性能优化系统:
|
||||
|
||||
### 1. 组件索引系统
|
||||
|
||||
自动为组件查询提供O(1)性能:
|
||||
|
||||
```typescript
|
||||
// 获取组件索引统计
|
||||
const componentIndex = entityManager.getComponentIndex();
|
||||
const stats = componentIndex.getPerformanceStats();
|
||||
|
||||
console.log('组件索引统计:', {
|
||||
totalQueries: stats.totalQueries,
|
||||
indexHits: stats.indexHits,
|
||||
hitRate: (stats.indexHits / stats.totalQueries * 100).toFixed(2) + '%'
|
||||
});
|
||||
|
||||
// 手动优化(通常自动进行)
|
||||
componentIndex.optimize();
|
||||
```
|
||||
|
||||
### 2. Archetype系统
|
||||
|
||||
按组件组合分组实体,优化批量查询:
|
||||
|
||||
```typescript
|
||||
// 获取Archetype统计
|
||||
const archetypeSystem = entityManager.getArchetypeSystem();
|
||||
const archetypeStats = archetypeSystem.getStatistics();
|
||||
|
||||
console.log('Archetype统计:', {
|
||||
totalArchetypes: archetypeStats.totalArchetypes,
|
||||
totalEntities: archetypeStats.totalEntities,
|
||||
queryCacheSize: archetypeStats.queryCacheSize
|
||||
});
|
||||
|
||||
// 查看所有原型
|
||||
console.log('当前原型:', archetypeSystem.getAllArchetypes());
|
||||
```
|
||||
|
||||
### 3. 脏标记系统
|
||||
|
||||
追踪实体变更,避免不必要的更新:
|
||||
|
||||
```typescript
|
||||
// 获取脏标记统计
|
||||
const dirtyTracking = entityManager.getDirtyTrackingSystem();
|
||||
const dirtyStats = dirtyTracking.getPerformanceStats();
|
||||
|
||||
console.log('脏标记统计:', {
|
||||
totalMarks: dirtyStats.totalMarks,
|
||||
batchesProcessed: dirtyStats.batchesProcessed,
|
||||
listenersNotified: dirtyStats.listenersNotified
|
||||
});
|
||||
|
||||
// 手动处理脏标记
|
||||
dirtyTracking.processDirtyMarks();
|
||||
```
|
||||
|
||||
## 实体管理器统计
|
||||
|
||||
获取EntityManager的综合性能数据:
|
||||
|
||||
```typescript
|
||||
const stats = entityManager.getStatistics();
|
||||
|
||||
console.log('EntityManager统计:', {
|
||||
// 基础统计
|
||||
entityCount: stats.entityCount,
|
||||
activeEntityCount: stats.activeEntityCount,
|
||||
|
||||
// 查询统计
|
||||
totalQueries: stats.totalQueries,
|
||||
indexHits: stats.indexHits,
|
||||
archetypeHits: stats.archetypeHits,
|
||||
|
||||
// 性能指标
|
||||
averageQueryTime: stats.averageQueryTime,
|
||||
hitRate: (stats.indexHits / stats.totalQueries * 100).toFixed(2) + '%'
|
||||
});
|
||||
```
|
||||
|
||||
## 系统优化和清理
|
||||
|
||||
```typescript
|
||||
// 手动触发优化
|
||||
entityManager.optimize();
|
||||
|
||||
// 内存清理
|
||||
entityManager.cleanup();
|
||||
|
||||
// 压缩数据结构
|
||||
entityManager.compact();
|
||||
|
||||
// 获取内存使用情况
|
||||
const memoryStats = entityManager.getMemoryUsage();
|
||||
console.log('内存使用:', {
|
||||
entityIndexSize: memoryStats.entityIndex,
|
||||
componentIndexSize: memoryStats.componentIndex,
|
||||
archetypeSize: memoryStats.archetype
|
||||
});
|
||||
```
|
||||
|
||||
## 实际使用案例
|
||||
|
||||
### 游戏系统集成
|
||||
|
||||
```typescript
|
||||
class MovementSystem extends EntitySystem {
|
||||
private entityManager: EntityManager;
|
||||
|
||||
constructor(scene: Scene) {
|
||||
super();
|
||||
this.entityManager = new EntityManager(scene);
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]): void {
|
||||
// 使用高效查询获取移动实体
|
||||
const movingEntities = this.entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, VelocityComponent])
|
||||
.active(true)
|
||||
.execute();
|
||||
|
||||
// 批量处理
|
||||
movingEntities.forEach(entity => {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
const velocity = entity.getComponent(VelocityComponent);
|
||||
|
||||
position.x += velocity.dx * Time.deltaTime;
|
||||
position.y += velocity.dy * Time.deltaTime;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂查询示例
|
||||
|
||||
```typescript
|
||||
// 战斗系统:查找攻击范围内的敌人
|
||||
class CombatSystem {
|
||||
private entityManager: EntityManager;
|
||||
|
||||
findTargetsInRange(attacker: Entity, range: number): Entity[] {
|
||||
const attackerPos = attacker.getComponent(PositionComponent);
|
||||
if (!attackerPos) return [];
|
||||
|
||||
return this.entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, HealthComponent])
|
||||
.withTag("enemy")
|
||||
.withoutTag("dead")
|
||||
.where(entity => {
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(pos.x - attackerPos.x, 2) +
|
||||
Math.pow(pos.y - attackerPos.y, 2)
|
||||
);
|
||||
return distance <= range;
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
// 优化版本:使用空间分区(如果实现了的话)
|
||||
findTargetsInRangeOptimized(attacker: Entity, range: number): Entity[] {
|
||||
// 首先通过空间查询缩小范围
|
||||
const nearbyEntities = this.spatialIndex.queryRange(
|
||||
attackerPos.x - range,
|
||||
attackerPos.y - range,
|
||||
attackerPos.x + range,
|
||||
attackerPos.y + range
|
||||
);
|
||||
|
||||
// 然后使用EntityManager进行精确过滤
|
||||
return this.entityManager
|
||||
.query()
|
||||
.withAll([HealthComponent])
|
||||
.withTag("enemy")
|
||||
.withoutTag("dead")
|
||||
.where(entity => nearbyEntities.includes(entity))
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能建议
|
||||
|
||||
### 查询优化
|
||||
|
||||
1. **利用索引**: 优先使用组件查询和标签查询,它们具有O(1)性能
|
||||
2. **减少自定义过滤**: `where()`条件虽然灵活,但会降低性能
|
||||
3. **缓存查询结果**: 对于不经常变化的查询结果,考虑缓存
|
||||
|
||||
```typescript
|
||||
// ✅ 推荐:使用索引查询
|
||||
const enemies = entityManager.getEntitiesByTag("enemy");
|
||||
|
||||
// ⚠️ 谨慎:自定义过滤
|
||||
const enemies = entityManager
|
||||
.query()
|
||||
.where(entity => entity.name.includes("Enemy"))
|
||||
.execute();
|
||||
```
|
||||
|
||||
### 批量操作优化
|
||||
|
||||
```typescript
|
||||
// ✅ 推荐:批量创建
|
||||
const bullets = entityManager.createEntities(100, "Bullet");
|
||||
|
||||
// ❌ 避免:循环单独创建
|
||||
for (let i = 0; i < 100; i++) {
|
||||
entityManager.createEntity("Bullet");
|
||||
}
|
||||
```
|
||||
|
||||
### 内存管理
|
||||
|
||||
```typescript
|
||||
// 定期清理
|
||||
setInterval(() => {
|
||||
entityManager.cleanup();
|
||||
}, 30000); // 每30秒清理一次
|
||||
|
||||
// 监控性能
|
||||
const stats = entityManager.getStatistics();
|
||||
if (stats.indexHits / stats.totalQueries < 0.8) {
|
||||
console.warn('查询命中率较低,考虑优化查询策略');
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
EntityManager 提供了:
|
||||
|
||||
- **统一接口**: 所有实体操作通过一个管理器完成
|
||||
- **自动优化**: 内置三个性能优化系统
|
||||
- **灵活查询**: 从简单的ID查找到复杂的条件查询
|
||||
- **性能监控**: 完整的统计和诊断信息
|
||||
- **批量操作**: 高效的批量处理能力
|
||||
|
||||
通过合理使用EntityManager,您可以构建高性能、可维护的ECS游戏系统。
|
||||
496
docs/event-system-example.md
Normal file
496
docs/event-system-example.md
Normal file
@@ -0,0 +1,496 @@
|
||||
# ECS事件系统使用指南
|
||||
|
||||
本文档介绍如何使用ECS框架的增强事件系统,包括类型安全的事件发布订阅、预定义的ECS事件类型和高级功能。
|
||||
|
||||
## 目录
|
||||
|
||||
1. [基础用法](#基础用法)
|
||||
2. [预定义ECS事件](#预定义ecs事件)
|
||||
3. [事件装饰器](#事件装饰器)
|
||||
4. [高级功能](#高级功能)
|
||||
5. [性能优化](#性能优化)
|
||||
6. [最佳实践](#最佳实践)
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 创建事件总线
|
||||
|
||||
```typescript
|
||||
import { EventBus, GlobalEventBus } from './ECS';
|
||||
|
||||
// 方式1:创建独立的事件总线
|
||||
const eventBus = new EventBus(true); // true启用调试模式
|
||||
|
||||
// 方式2:使用全局事件总线
|
||||
const globalEventBus = GlobalEventBus.getInstance(true);
|
||||
```
|
||||
|
||||
### 基本事件发布订阅
|
||||
|
||||
```typescript
|
||||
// 定义事件数据类型
|
||||
interface PlayerDiedEvent {
|
||||
playerId: number;
|
||||
cause: string;
|
||||
position: { x: number; y: number };
|
||||
}
|
||||
|
||||
// 监听事件
|
||||
const listenerId = eventBus.on<PlayerDiedEvent>('player:died', (data) => {
|
||||
console.log(`Player ${data.playerId} died at (${data.position.x}, ${data.position.y})`);
|
||||
console.log(`Cause: ${data.cause}`);
|
||||
});
|
||||
|
||||
// 发射事件
|
||||
eventBus.emit('player:died', {
|
||||
playerId: 123,
|
||||
cause: 'enemy_attack',
|
||||
position: { x: 100, y: 200 }
|
||||
});
|
||||
|
||||
// 移除监听器
|
||||
eventBus.off('player:died', listenerId);
|
||||
```
|
||||
|
||||
### 一次性事件监听
|
||||
|
||||
```typescript
|
||||
// 只监听一次
|
||||
eventBus.once<PlayerDiedEvent>('player:died', (data) => {
|
||||
console.log('This will only be called once');
|
||||
});
|
||||
```
|
||||
|
||||
### 异步事件处理
|
||||
|
||||
```typescript
|
||||
// 异步事件监听
|
||||
eventBus.onAsync<PlayerDiedEvent>('player:died', async (data) => {
|
||||
await savePlayerDeathToDatabase(data);
|
||||
await updateLeaderboard(data.playerId);
|
||||
});
|
||||
|
||||
// 异步事件发射
|
||||
await eventBus.emitAsync('player:died', playerData);
|
||||
```
|
||||
|
||||
## 预定义ECS事件
|
||||
|
||||
框架提供了完整的ECS事件类型定义,支持实体、组件、系统等核心概念的事件。
|
||||
|
||||
### 实体事件
|
||||
|
||||
```typescript
|
||||
import { ECSEventType, IEntityEventData } from './ECS';
|
||||
|
||||
// 监听实体创建事件
|
||||
eventBus.onEntityCreated((data: IEntityEventData) => {
|
||||
console.log(`Entity created: ${data.entityName} (ID: ${data.entityId})`);
|
||||
});
|
||||
|
||||
// 监听实体销毁事件
|
||||
eventBus.on<IEntityEventData>(ECSEventType.ENTITY_DESTROYED, (data) => {
|
||||
console.log(`Entity destroyed: ${data.entityName}`);
|
||||
});
|
||||
|
||||
// 手动发射实体事件
|
||||
eventBus.emitEntityCreated({
|
||||
timestamp: Date.now(),
|
||||
source: 'GameManager',
|
||||
entityId: 123,
|
||||
entityName: 'Player',
|
||||
entityTag: 'player'
|
||||
});
|
||||
```
|
||||
|
||||
### 组件事件
|
||||
|
||||
```typescript
|
||||
import { IComponentEventData } from './ECS';
|
||||
|
||||
// 监听组件添加事件
|
||||
eventBus.onComponentAdded((data: IComponentEventData) => {
|
||||
console.log(`Component ${data.componentType} added to entity ${data.entityId}`);
|
||||
});
|
||||
|
||||
// 监听组件移除事件
|
||||
eventBus.on<IComponentEventData>(ECSEventType.COMPONENT_REMOVED, (data) => {
|
||||
console.log(`Component ${data.componentType} removed from entity ${data.entityId}`);
|
||||
});
|
||||
```
|
||||
|
||||
### 系统事件
|
||||
|
||||
```typescript
|
||||
import { ISystemEventData } from './ECS';
|
||||
|
||||
// 监听系统错误
|
||||
eventBus.onSystemError((data: ISystemEventData) => {
|
||||
console.error(`System error in ${data.systemName}: ${data.systemType}`);
|
||||
});
|
||||
|
||||
// 监听系统处理开始/结束
|
||||
eventBus.on<ISystemEventData>(ECSEventType.SYSTEM_PROCESSING_START, (data) => {
|
||||
console.log(`System ${data.systemName} started processing`);
|
||||
});
|
||||
```
|
||||
|
||||
### 性能事件
|
||||
|
||||
```typescript
|
||||
import { IPerformanceEventData } from './ECS';
|
||||
|
||||
// 监听性能警告
|
||||
eventBus.onPerformanceWarning((data: IPerformanceEventData) => {
|
||||
console.warn(`Performance warning: ${data.operation} took ${data.executionTime}ms`);
|
||||
});
|
||||
|
||||
// 监听内存使用过高
|
||||
eventBus.on<IPerformanceEventData>(ECSEventType.MEMORY_USAGE_HIGH, (data) => {
|
||||
console.warn(`High memory usage: ${data.memoryUsage}MB`);
|
||||
});
|
||||
```
|
||||
|
||||
## 事件装饰器
|
||||
|
||||
使用装饰器可以自动注册事件监听器,简化代码编写。
|
||||
|
||||
### 基础装饰器
|
||||
|
||||
```typescript
|
||||
import { EventHandler, AsyncEventHandler, EventPriority } from './ECS';
|
||||
|
||||
class GameManager {
|
||||
@EventHandler(ECSEventType.ENTITY_CREATED, { priority: EventPriority.HIGH })
|
||||
onEntityCreated(data: IEntityEventData) {
|
||||
console.log(`New entity: ${data.entityName}`);
|
||||
}
|
||||
|
||||
@AsyncEventHandler(ECSEventType.ENTITY_DESTROYED)
|
||||
async onEntityDestroyed(data: IEntityEventData) {
|
||||
await this.cleanupEntityResources(data.entityId);
|
||||
}
|
||||
|
||||
@EventHandler('custom:game:event', { once: true })
|
||||
onGameStart(data: any) {
|
||||
console.log('Game started!');
|
||||
}
|
||||
|
||||
// 需要手动调用初始化方法
|
||||
constructor() {
|
||||
// 如果类有initEventListeners方法,会自动注册装饰器定义的监听器
|
||||
if (this.initEventListeners) {
|
||||
this.initEventListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanupEntityResources(entityId: number) {
|
||||
// 清理实体相关资源
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 优先级和配置
|
||||
|
||||
```typescript
|
||||
class SystemManager {
|
||||
@EventHandler(ECSEventType.SYSTEM_ERROR, {
|
||||
priority: EventPriority.CRITICAL,
|
||||
context: this
|
||||
})
|
||||
handleSystemError(data: ISystemEventData) {
|
||||
this.logError(data);
|
||||
this.restartSystem(data.systemName);
|
||||
}
|
||||
|
||||
@AsyncEventHandler(ECSEventType.PERFORMANCE_WARNING, {
|
||||
priority: EventPriority.LOW,
|
||||
async: true
|
||||
})
|
||||
async handlePerformanceWarning(data: IPerformanceEventData) {
|
||||
await this.optimizePerformance(data);
|
||||
}
|
||||
|
||||
private logError(data: ISystemEventData) {
|
||||
// 错误日志记录
|
||||
}
|
||||
|
||||
private restartSystem(systemName: string) {
|
||||
// 重启系统
|
||||
}
|
||||
|
||||
private async optimizePerformance(data: IPerformanceEventData) {
|
||||
// 性能优化逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 事件批处理
|
||||
|
||||
```typescript
|
||||
// 设置批处理配置
|
||||
eventBus.setBatchConfig('entity:update', 100, 16); // 批量100个,延迟16ms
|
||||
|
||||
// 发射事件(会被批处理)
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
eventBus.emit('entity:update', { entityId: i, data: 'update' });
|
||||
}
|
||||
|
||||
// 手动刷新批处理队列
|
||||
eventBus.flushBatch('entity:update');
|
||||
```
|
||||
|
||||
### 事件统计和监控
|
||||
|
||||
```typescript
|
||||
// 获取单个事件统计
|
||||
const stats = eventBus.getStats('entity:created');
|
||||
console.log(`Event triggered ${stats.triggerCount} times`);
|
||||
console.log(`Average execution time: ${stats.averageExecutionTime}ms`);
|
||||
|
||||
// 获取所有事件统计
|
||||
const allStats = eventBus.getStats();
|
||||
if (allStats instanceof Map) {
|
||||
allStats.forEach((stat, eventType) => {
|
||||
console.log(`${eventType}: ${stat.triggerCount} triggers`);
|
||||
});
|
||||
}
|
||||
|
||||
// 重置统计
|
||||
eventBus.resetStats('entity:created');
|
||||
```
|
||||
|
||||
### 事件类型验证
|
||||
|
||||
```typescript
|
||||
import { EventTypeValidator } from './ECS';
|
||||
|
||||
// 检查事件类型是否有效
|
||||
if (EventTypeValidator.isValid('entity:created')) {
|
||||
eventBus.emit('entity:created', data);
|
||||
}
|
||||
|
||||
// 添加自定义事件类型
|
||||
EventTypeValidator.addCustomType('game:custom:event');
|
||||
|
||||
// 获取所有有效事件类型
|
||||
const validTypes = EventTypeValidator.getAllValidTypes();
|
||||
console.log('Valid event types:', validTypes);
|
||||
```
|
||||
|
||||
### 调试和日志
|
||||
|
||||
```typescript
|
||||
// 启用调试模式
|
||||
eventBus.setDebugMode(true);
|
||||
|
||||
// 设置最大监听器数量
|
||||
eventBus.setMaxListeners(50);
|
||||
|
||||
// 检查是否有监听器
|
||||
if (eventBus.hasListeners('entity:created')) {
|
||||
console.log('Has listeners for entity:created');
|
||||
}
|
||||
|
||||
// 获取监听器数量
|
||||
const count = eventBus.getListenerCount('entity:created');
|
||||
console.log(`${count} listeners for entity:created`);
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 事件过滤和条件监听
|
||||
|
||||
```typescript
|
||||
// 使用条件过滤减少不必要的事件处理
|
||||
eventBus.on<IEntityEventData>(ECSEventType.ENTITY_CREATED, (data) => {
|
||||
// 只处理玩家实体
|
||||
if (data.entityTag === 'player') {
|
||||
handlePlayerCreated(data);
|
||||
}
|
||||
});
|
||||
|
||||
// 更好的方式:使用具体的事件类型
|
||||
eventBus.on<IEntityEventData>('entity:player:created', handlePlayerCreated);
|
||||
```
|
||||
|
||||
### 内存管理
|
||||
|
||||
```typescript
|
||||
class EventManager {
|
||||
private listeners: string[] = [];
|
||||
|
||||
public setupListeners() {
|
||||
// 保存监听器ID以便清理
|
||||
this.listeners.push(
|
||||
eventBus.on('event1', this.handler1.bind(this)),
|
||||
eventBus.on('event2', this.handler2.bind(this))
|
||||
);
|
||||
}
|
||||
|
||||
public cleanup() {
|
||||
// 清理所有监听器
|
||||
this.listeners.forEach(id => {
|
||||
eventBus.off('event1', id);
|
||||
eventBus.off('event2', id);
|
||||
});
|
||||
this.listeners.length = 0;
|
||||
}
|
||||
|
||||
private handler1(data: any) { /* ... */ }
|
||||
private handler2(data: any) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### 异步事件优化
|
||||
|
||||
```typescript
|
||||
// 使用Promise.all并行处理多个异步事件
|
||||
const promises = [
|
||||
eventBus.emitAsync('save:player', playerData),
|
||||
eventBus.emitAsync('update:leaderboard', scoreData),
|
||||
eventBus.emitAsync('notify:friends', notificationData)
|
||||
];
|
||||
|
||||
await Promise.all(promises);
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 事件命名规范
|
||||
|
||||
```typescript
|
||||
// 推荐的事件命名格式:模块:对象:动作
|
||||
const EVENT_NAMES = {
|
||||
// 实体相关
|
||||
ENTITY_PLAYER_CREATED: 'entity:player:created',
|
||||
ENTITY_ENEMY_DESTROYED: 'entity:enemy:destroyed',
|
||||
|
||||
// 游戏逻辑相关
|
||||
GAME_LEVEL_COMPLETED: 'game:level:completed',
|
||||
GAME_SCORE_UPDATED: 'game:score:updated',
|
||||
|
||||
// UI相关
|
||||
UI_MENU_OPENED: 'ui:menu:opened',
|
||||
UI_BUTTON_CLICKED: 'ui:button:clicked'
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 类型安全
|
||||
|
||||
```typescript
|
||||
// 定义强类型的事件数据接口
|
||||
interface GameEvents {
|
||||
'player:levelup': { playerId: number; newLevel: number; experience: number };
|
||||
'inventory:item:added': { itemId: string; quantity: number; playerId: number };
|
||||
'combat:damage:dealt': { attackerId: number; targetId: number; damage: number };
|
||||
}
|
||||
|
||||
// 创建类型安全的事件发射器
|
||||
class TypedEventBus {
|
||||
private eventBus = new EventBus();
|
||||
|
||||
emit<K extends keyof GameEvents>(eventType: K, data: GameEvents[K]) {
|
||||
this.eventBus.emit(eventType, data);
|
||||
}
|
||||
|
||||
on<K extends keyof GameEvents>(
|
||||
eventType: K,
|
||||
handler: (data: GameEvents[K]) => void
|
||||
) {
|
||||
return this.eventBus.on(eventType, handler);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
```typescript
|
||||
// 在事件处理器中添加错误处理
|
||||
eventBus.on<IEntityEventData>(ECSEventType.ENTITY_CREATED, (data) => {
|
||||
try {
|
||||
processEntityCreation(data);
|
||||
} catch (error) {
|
||||
console.error('Error processing entity creation:', error);
|
||||
// 发射错误事件
|
||||
eventBus.emit(ECSEventType.ERROR_OCCURRED, {
|
||||
timestamp: Date.now(),
|
||||
source: 'EntityCreationHandler',
|
||||
error: error.message,
|
||||
context: data
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4. 模块化事件管理
|
||||
|
||||
```typescript
|
||||
// 为不同模块创建专门的事件管理器
|
||||
class PlayerEventManager {
|
||||
constructor(private eventBus: EventBus) {
|
||||
this.setupListeners();
|
||||
}
|
||||
|
||||
private setupListeners() {
|
||||
this.eventBus.onEntityCreated(this.onPlayerCreated.bind(this));
|
||||
this.eventBus.on('player:levelup', this.onPlayerLevelUp.bind(this));
|
||||
this.eventBus.on('player:died', this.onPlayerDied.bind(this));
|
||||
}
|
||||
|
||||
private onPlayerCreated(data: IEntityEventData) {
|
||||
if (data.entityTag === 'player') {
|
||||
// 处理玩家创建逻辑
|
||||
}
|
||||
}
|
||||
|
||||
private onPlayerLevelUp(data: any) {
|
||||
// 处理玩家升级逻辑
|
||||
}
|
||||
|
||||
private onPlayerDied(data: any) {
|
||||
// 处理玩家死亡逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 与EntityManager集成
|
||||
|
||||
```typescript
|
||||
import { EntityManager } from './ECS';
|
||||
|
||||
// EntityManager会自动设置事件总线
|
||||
const entityManager = new EntityManager();
|
||||
|
||||
// 获取事件总线实例
|
||||
const eventBus = entityManager.eventBus;
|
||||
|
||||
// 监听自动发射的ECS事件
|
||||
eventBus.onEntityCreated((data) => {
|
||||
console.log('Entity created automatically:', data);
|
||||
});
|
||||
|
||||
eventBus.onComponentAdded((data) => {
|
||||
console.log('Component added automatically:', data);
|
||||
});
|
||||
|
||||
// 创建实体时会自动发射事件
|
||||
const entity = entityManager.createEntity('Player');
|
||||
|
||||
// 添加组件时会自动发射事件
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
ECS框架的事件系统提供了:
|
||||
|
||||
- **类型安全**:完整的TypeScript类型支持
|
||||
- **高性能**:批处理、缓存和优化机制
|
||||
- **易用性**:装饰器、预定义事件类型
|
||||
- **可扩展**:自定义事件类型和验证
|
||||
- **调试友好**:详细的统计信息和调试模式
|
||||
|
||||
通过合理使用事件系统,可以实现松耦合的模块化架构,提高代码的可维护性和扩展性。
|
||||
549
docs/getting-started.md
Normal file
549
docs/getting-started.md
Normal file
@@ -0,0 +1,549 @@
|
||||
# 快速入门
|
||||
|
||||
本指南将帮助您快速上手 ECS Framework,这是一个专业级的实体组件系统框架,采用现代化架构设计,专为高性能游戏开发打造。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
ecs-framework/
|
||||
├── source/
|
||||
│ ├── src/ # 源代码
|
||||
│ │ ├── ECS/ # ECS核心系统
|
||||
│ │ ├── Types/ # 类型定义
|
||||
│ │ ├── Utils/ # 工具类
|
||||
│ │ └── Testing/ # 测试文件
|
||||
│ ├── scripts/ # 构建脚本
|
||||
│ └── tsconfig.json # TypeScript配置
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
## 安装和使用
|
||||
|
||||
### NPM 安装
|
||||
|
||||
```bash
|
||||
npm install @esengine/ecs-framework
|
||||
```
|
||||
|
||||
### 从源码构建
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/esengine/ecs-framework.git
|
||||
|
||||
# 进入源码目录
|
||||
cd ecs-framework/source
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 编译TypeScript
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 基础设置
|
||||
|
||||
### 1. 导入框架
|
||||
|
||||
```typescript
|
||||
// 导入核心类
|
||||
import {
|
||||
Core,
|
||||
Entity,
|
||||
Component,
|
||||
Scene,
|
||||
EntitySystem,
|
||||
EntityManager,
|
||||
ComponentIndexManager,
|
||||
ArchetypeSystem,
|
||||
DirtyTrackingSystem
|
||||
} from '@esengine/ecs-framework';
|
||||
```
|
||||
|
||||
### 2. 创建基础管理器
|
||||
|
||||
```typescript
|
||||
class GameManager {
|
||||
private core: Core;
|
||||
private scene: Scene;
|
||||
private entityManager: EntityManager;
|
||||
|
||||
constructor() {
|
||||
// 创建核心实例
|
||||
this.core = Core.create(true);
|
||||
|
||||
// 创建场景
|
||||
this.scene = new Scene();
|
||||
this.scene.name = "GameScene";
|
||||
|
||||
// 设置当前场景
|
||||
Core.scene = this.scene;
|
||||
|
||||
// 初始化实体管理器
|
||||
this.entityManager = new EntityManager(this.scene);
|
||||
|
||||
// 初始化性能优化
|
||||
this.setupPerformanceOptimizations();
|
||||
}
|
||||
|
||||
private setupPerformanceOptimizations() {
|
||||
// 启用组件索引(自动优化查询性能)
|
||||
// EntityManager内部已自动启用
|
||||
|
||||
// 可选:手动配置优化系统
|
||||
const componentIndex = this.entityManager.getComponentIndex();
|
||||
const archetypeSystem = this.entityManager.getArchetypeSystem();
|
||||
const dirtyTracking = this.entityManager.getDirtyTrackingSystem();
|
||||
|
||||
// 优化系统会自动工作,通常无需手动配置
|
||||
}
|
||||
|
||||
public update(deltaTime: number): void {
|
||||
// 更新场景
|
||||
this.scene.update();
|
||||
|
||||
// 处理系统逻辑
|
||||
this.updateSystems(deltaTime);
|
||||
}
|
||||
|
||||
private updateSystems(deltaTime: number): void {
|
||||
// 在这里添加您的系统更新逻辑
|
||||
}
|
||||
|
||||
// 提供实体管理器访问
|
||||
public getEntityManager(): EntityManager {
|
||||
return this.entityManager;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
import { Component, ComponentPoolManager } from '@esengine/ecs-framework';
|
||||
|
||||
// 位置组件
|
||||
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;
|
||||
}
|
||||
|
||||
// 对象池重置方法
|
||||
public reset() {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 速度组件
|
||||
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;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 生命值组件
|
||||
class HealthComponent extends Component {
|
||||
public maxHealth: number = 100;
|
||||
public currentHealth: number = 100;
|
||||
|
||||
constructor(maxHealth: number = 100) {
|
||||
super();
|
||||
this.maxHealth = maxHealth;
|
||||
this.currentHealth = maxHealth;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.maxHealth = 100;
|
||||
this.currentHealth = 100;
|
||||
}
|
||||
|
||||
public takeDamage(damage: number): void {
|
||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
||||
}
|
||||
|
||||
public heal(amount: number): void {
|
||||
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
|
||||
}
|
||||
|
||||
public isDead(): boolean {
|
||||
return this.currentHealth <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的组件定义
|
||||
// 注:框架会自动优化组件的存储和查询
|
||||
```
|
||||
|
||||
## 使用 EntityManager
|
||||
|
||||
EntityManager 是框架的核心功能,提供统一的实体管理和高性能查询接口。
|
||||
|
||||
### 1. 基础用法
|
||||
|
||||
```typescript
|
||||
// 获取EntityManager实例(在GameManager中已创建)
|
||||
const entityManager = gameManager.getEntityManager();
|
||||
|
||||
// 创建单个实体
|
||||
const player = entityManager.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(100, 100));
|
||||
player.addComponent(new VelocityComponent(50, 0));
|
||||
|
||||
// 批量创建实体
|
||||
const enemies = entityManager.createEntities(50, "Enemy");
|
||||
enemies.forEach((enemy, index) => {
|
||||
enemy.addComponent(new PositionComponent(
|
||||
Math.random() * 800,
|
||||
Math.random() * 600
|
||||
));
|
||||
enemy.addComponent(new HealthComponent(30));
|
||||
enemy.tag = "enemy";
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 高性能查询
|
||||
|
||||
```typescript
|
||||
// 流式查询API - 支持复杂查询条件
|
||||
const movingEntities = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, VelocityComponent])
|
||||
.withoutTag("dead")
|
||||
.active(true)
|
||||
.execute();
|
||||
|
||||
// 快速组件查询(使用O(1)索引)
|
||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||
|
||||
// 标签查询
|
||||
const allEnemies = entityManager.getEntitiesByTag("enemy");
|
||||
|
||||
// 名称查询
|
||||
const specificEnemy = entityManager.getEntityByName("BossEnemy");
|
||||
|
||||
// 复合查询
|
||||
const livingEnemies = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, HealthComponent])
|
||||
.withTag("enemy")
|
||||
.withoutTag("dead")
|
||||
.where(entity => {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
return health && health.currentHealth > 0;
|
||||
})
|
||||
.execute();
|
||||
```
|
||||
|
||||
### 3. 批量操作
|
||||
|
||||
```typescript
|
||||
// 批量处理实体
|
||||
entityManager.forEachEntity(entity => {
|
||||
// 处理所有实体
|
||||
if (entity.tag === "bullet" && entity.position.y < 0) {
|
||||
entity.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// 批量处理特定组件的实体
|
||||
entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => {
|
||||
if (health.currentHealth <= 0) {
|
||||
entity.addTag("dead");
|
||||
entity.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取统计信息
|
||||
const stats = entityManager.getStatistics();
|
||||
console.log(`总实体数: ${stats.entityCount}`);
|
||||
console.log(`索引命中率: ${stats.indexHits}/${stats.totalQueries}`);
|
||||
```
|
||||
|
||||
### 4. 性能优化功能
|
||||
|
||||
```typescript
|
||||
// 获取性能优化系统
|
||||
const componentIndex = entityManager.getComponentIndex();
|
||||
const archetypeSystem = entityManager.getArchetypeSystem();
|
||||
const dirtyTracking = entityManager.getDirtyTrackingSystem();
|
||||
|
||||
// 查看性能统计
|
||||
console.log('组件索引统计:', componentIndex.getPerformanceStats());
|
||||
console.log('Archetype统计:', archetypeSystem.getStatistics());
|
||||
console.log('脏标记统计:', dirtyTracking.getPerformanceStats());
|
||||
|
||||
// 手动优化(通常自动进行)
|
||||
entityManager.optimize();
|
||||
|
||||
// 内存清理
|
||||
entityManager.cleanup();
|
||||
```
|
||||
|
||||
## 创建系统
|
||||
|
||||
系统处理具有特定组件的实体集合,实现游戏逻辑。
|
||||
|
||||
```typescript
|
||||
import { EntitySystem, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
class MovementSystem extends EntitySystem {
|
||||
protected process(entities: Entity[]): void {
|
||||
// 使用EntityManager进行高效查询
|
||||
const entityManager = new EntityManager(this.scene);
|
||||
const movingEntities = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, VelocityComponent])
|
||||
.execute();
|
||||
|
||||
movingEntities.forEach(entity => {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
const velocity = entity.getComponent(VelocityComponent);
|
||||
|
||||
if (position && velocity) {
|
||||
// 更新位置
|
||||
position.x += velocity.x * 0.016; // 假设60FPS
|
||||
position.y += velocity.y * 0.016;
|
||||
|
||||
// 边界检查
|
||||
if (position.x < 0 || position.x > 800) {
|
||||
velocity.x = -velocity.x;
|
||||
}
|
||||
if (position.y < 0 || position.y > 600) {
|
||||
velocity.y = -velocity.y;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class HealthSystem extends EntitySystem {
|
||||
protected process(entities: Entity[]): void {
|
||||
const entityManager = new EntityManager(this.scene);
|
||||
|
||||
// 查找所有有生命值的实体
|
||||
entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => {
|
||||
if (health.isDead()) {
|
||||
entity.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 添加系统到场景
|
||||
gameManager.scene.addEntityProcessor(new MovementSystem());
|
||||
gameManager.scene.addEntityProcessor(new HealthSystem());
|
||||
```
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 事件系统
|
||||
|
||||
```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
|
||||
// 获取EntityManager性能统计
|
||||
const stats = entityManager.getStatistics();
|
||||
console.log(`总实体数: ${stats.entityCount}`);
|
||||
console.log(`索引命中率: ${stats.indexHits}/${stats.totalQueries}`);
|
||||
|
||||
// 获取各优化系统的统计
|
||||
console.log('组件索引:', entityManager.getComponentIndex().getPerformanceStats());
|
||||
console.log('Archetype:', entityManager.getArchetypeSystem().getStatistics());
|
||||
console.log('脏标记:', entityManager.getDirtyTrackingSystem().getPerformanceStats());
|
||||
```
|
||||
|
||||
## 简单示例
|
||||
|
||||
以下是一个完整的示例,展示了框架的主要功能:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Core,
|
||||
Entity,
|
||||
Component,
|
||||
Scene,
|
||||
EntitySystem,
|
||||
EntityManager
|
||||
} from '@esengine/ecs-framework';
|
||||
|
||||
// 游戏管理器
|
||||
class SimpleGame {
|
||||
private core: Core;
|
||||
private scene: Scene;
|
||||
private entityManager: EntityManager;
|
||||
|
||||
constructor() {
|
||||
this.core = Core.create(true);
|
||||
this.scene = new Scene();
|
||||
this.scene.name = "GameScene";
|
||||
Core.scene = this.scene;
|
||||
|
||||
this.entityManager = new EntityManager(this.scene);
|
||||
this.setupSystems();
|
||||
}
|
||||
|
||||
private setupSystems(): void {
|
||||
this.scene.addEntityProcessor(new MovementSystem());
|
||||
this.scene.addEntityProcessor(new HealthSystem());
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
// 创建游戏实体
|
||||
this.createPlayer();
|
||||
this.createEnemies(50);
|
||||
|
||||
// 启动游戏循环
|
||||
this.gameLoop();
|
||||
}
|
||||
|
||||
private createPlayer(): Entity {
|
||||
const player = this.entityManager.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(400, 300));
|
||||
player.addComponent(new VelocityComponent(0, 0));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
player.tag = "player";
|
||||
return player;
|
||||
}
|
||||
|
||||
private createEnemies(count: number): Entity[] {
|
||||
const enemies = this.entityManager.createEntities(count, "Enemy");
|
||||
|
||||
enemies.forEach((enemy, index) => {
|
||||
enemy.addComponent(new PositionComponent(
|
||||
Math.random() * 800,
|
||||
Math.random() * 600
|
||||
));
|
||||
enemy.addComponent(new VelocityComponent(
|
||||
(Math.random() - 0.5) * 100,
|
||||
(Math.random() - 0.5) * 100
|
||||
));
|
||||
enemy.addComponent(new HealthComponent(50));
|
||||
enemy.tag = "enemy";
|
||||
});
|
||||
|
||||
return enemies;
|
||||
}
|
||||
|
||||
private gameLoop(): void {
|
||||
const update = () => {
|
||||
// 更新场景
|
||||
this.scene.update();
|
||||
requestAnimationFrame(update);
|
||||
};
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// 启动游戏
|
||||
const game = new SimpleGame();
|
||||
game.start();
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 大规模实体处理
|
||||
- 使用 `EntityManager.createEntities()` 批量创建实体
|
||||
- 利用组件索引系统进行高效查询
|
||||
- 启用Archetype系统减少查询遍历
|
||||
|
||||
### 2. 查询优化
|
||||
- 使用 `EntityManager.query()` 流式API构建复杂查询
|
||||
- 缓存频繁查询的结果
|
||||
- 利用脏标记系统避免不必要的更新
|
||||
|
||||
### 3. 性能监控
|
||||
- 定期检查 `EntityManager.getStatistics()` 获取性能数据
|
||||
- 监控组件索引命中率
|
||||
- 使用框架提供的性能统计功能
|
||||
|
||||
## 下一步
|
||||
|
||||
现在您已经掌握了 ECS Framework 的基础用法,可以继续学习:
|
||||
|
||||
- [EntityManager 使用指南](entity-manager-example.md) - 详细了解实体管理器的高级功能
|
||||
- [性能优化指南](performance-optimization.md) - 深入了解三大性能优化系统
|
||||
- [核心概念](core-concepts.md) - 深入了解 ECS 架构和设计原理
|
||||
- [查询系统使用指南](query-system-usage.md) - 学习高性能查询系统的详细用法
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何在不同游戏引擎中集成?
|
||||
|
||||
A: ECS Framework 是引擎无关的,您只需要:
|
||||
1. 通过npm安装框架 `npm install @esengine/ecs-framework`
|
||||
2. 在游戏引擎的主循环中调用 `scene.update()`
|
||||
3. 根据需要集成渲染、输入等引擎特定功能
|
||||
|
||||
### Q: 如何处理输入?
|
||||
|
||||
A: 框架本身不提供输入处理,建议:
|
||||
1. 创建一个输入组件来存储输入状态
|
||||
2. 在游戏引擎的输入回调中更新输入组件
|
||||
3. 创建输入处理系统来响应输入状态
|
||||
|
||||
### Q: 如何优化大规模实体性能?
|
||||
|
||||
A: 关键优化策略:
|
||||
1. 使用 `EntityManager` 的高级查询功能
|
||||
2. 启用组件索引系统进行快速查询
|
||||
3. 利用Archetype系统减少查询遍历
|
||||
4. 使用脏标记系统避免不必要的更新
|
||||
|
||||
### Q: EntityManager 有什么优势?
|
||||
|
||||
A: EntityManager 提供了:
|
||||
- O(1) 复杂度的组件查询(使用索引)
|
||||
- 流式API的复杂查询构建
|
||||
- 自动的性能优化系统集成
|
||||
- 统一的实体管理接口
|
||||
497
docs/performance-optimization.md
Normal file
497
docs/performance-optimization.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# 性能优化指南
|
||||
|
||||
ECS Framework 提供了多层性能优化系统,确保在各种规模的游戏中都能提供卓越的性能表现。
|
||||
|
||||
## 性能优化架构
|
||||
|
||||
### 三大核心优化系统
|
||||
|
||||
1. **组件索引系统 (ComponentIndex)** - 提供 O(1) 组件查询性能
|
||||
2. **Archetype系统** - 按组件组合分组实体,减少查询遍历
|
||||
3. **脏标记系统 (DirtyTracking)** - 细粒度变更追踪,避免不必要更新
|
||||
|
||||
这三个系统协同工作,为不同场景提供最优的性能表现。
|
||||
|
||||
## 性能基准
|
||||
|
||||
### 核心操作性能
|
||||
|
||||
```
|
||||
实体创建: 640,000+ 个/秒
|
||||
组件查询: O(1) 复杂度(使用索引)
|
||||
内存优化: 30-50% 减少分配
|
||||
批量操作: 显著提升处理效率
|
||||
```
|
||||
|
||||
### 查询性能对比
|
||||
|
||||
| 查询类型 | 传统方式 | 使用索引 | 性能提升 |
|
||||
|----------|----------|----------|----------|
|
||||
| 单组件查询 | O(n) | O(1) | 1000x+ |
|
||||
| 多组件查询 | O(n*m) | O(k) | 100x+ |
|
||||
| 标签查询 | O(n) | O(1) | 1000x+ |
|
||||
| 复合查询 | O(n*m*k) | O(min(k1,k2)) | 500x+ |
|
||||
|
||||
*n=实体数量, m=组件种类, k=匹配实体数量*
|
||||
|
||||
## 组件索引系统
|
||||
|
||||
### 索引类型选择
|
||||
|
||||
框架提供两种索引实现:
|
||||
|
||||
#### 哈希索引 (HashComponentIndex)
|
||||
- **适用场景**: 通用查询,平衡的读写性能
|
||||
- **优势**: O(1) 查询,较低内存开销
|
||||
- **缺点**: 哈希冲突时性能下降
|
||||
|
||||
```typescript
|
||||
// 自动选择最优索引类型
|
||||
const componentIndex = entityManager.getComponentIndex();
|
||||
|
||||
// 手动配置哈希索引
|
||||
componentIndex.setIndexType(HealthComponent, 'hash');
|
||||
```
|
||||
|
||||
#### 位图索引 (BitmapComponentIndex)
|
||||
- **适用场景**: 大规模实体,频繁的组合查询
|
||||
- **优势**: 超快的 AND/OR 操作,空间压缩
|
||||
- **缺点**: 更新成本较高,内存开销随实体数量增长
|
||||
|
||||
```typescript
|
||||
// 配置位图索引用于大规模查询
|
||||
componentIndex.setIndexType(PositionComponent, 'bitmap');
|
||||
```
|
||||
|
||||
### 智能索引管理
|
||||
|
||||
ComponentIndexManager 会根据使用模式自动优化:
|
||||
|
||||
```typescript
|
||||
// 获取索引性能统计
|
||||
const stats = componentIndex.getPerformanceStats();
|
||||
console.log('索引性能:', {
|
||||
queriesPerSecond: stats.queriesPerSecond,
|
||||
hitRate: stats.hitRate,
|
||||
indexType: stats.recommendedType
|
||||
});
|
||||
|
||||
// 自动优化索引类型
|
||||
componentIndex.optimize(); // 根据使用模式切换索引类型
|
||||
```
|
||||
|
||||
## Archetype系统优化
|
||||
|
||||
### 原型分组策略
|
||||
|
||||
Archetype系统将实体按组件组合分组,实现快速批量操作:
|
||||
|
||||
```typescript
|
||||
// 获取Archetype统计
|
||||
const archetypeSystem = entityManager.getArchetypeSystem();
|
||||
const stats = archetypeSystem.getStatistics();
|
||||
|
||||
console.log('Archetype优化:', {
|
||||
totalArchetypes: stats.totalArchetypes, // 原型数量
|
||||
avgEntitiesPerArchetype: stats.averageEntitiesPerArchetype,
|
||||
queryCacheHits: stats.queryCacheHits // 缓存命中次数
|
||||
});
|
||||
```
|
||||
|
||||
### 查询缓存机制
|
||||
|
||||
```typescript
|
||||
// 启用查询缓存(默认开启)
|
||||
archetypeSystem.enableQueryCache(true);
|
||||
|
||||
// 缓存大小限制(避免内存泄漏)
|
||||
archetypeSystem.setMaxCacheSize(1000);
|
||||
|
||||
// 清理过期缓存
|
||||
archetypeSystem.cleanCache();
|
||||
```
|
||||
|
||||
### 最佳实践
|
||||
|
||||
1. **组件设计**: 避免创建过多单独的原型
|
||||
2. **批量操作**: 利用原型批量处理相同组件组合的实体
|
||||
3. **缓存管理**: 定期清理查询缓存
|
||||
|
||||
```typescript
|
||||
// ✅ 好的设计:复用组件组合
|
||||
class MovementSystem extends EntitySystem {
|
||||
process() {
|
||||
// 一次查询处理所有移动实体
|
||||
const movingEntities = this.entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, VelocityComponent])
|
||||
.execute(); // 利用Archetype快速获取
|
||||
|
||||
// 批量处理
|
||||
movingEntities.forEach(entity => {
|
||||
// 更新逻辑
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 避免:频繁查询不同组合
|
||||
class BadSystem extends EntitySystem {
|
||||
process() {
|
||||
// 多次小查询,无法充分利用Archetype
|
||||
const players = this.queryPlayers();
|
||||
const enemies = this.queryEnemies();
|
||||
const bullets = this.queryBullets();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 脏标记系统优化
|
||||
|
||||
### 脏标记类型
|
||||
|
||||
系统提供细粒度的脏标记追踪:
|
||||
|
||||
```typescript
|
||||
enum DirtyType {
|
||||
COMPONENT_ADDED, // 组件添加
|
||||
COMPONENT_REMOVED, // 组件移除
|
||||
COMPONENT_MODIFIED, // 组件修改
|
||||
ENTITY_ENABLED, // 实体启用
|
||||
ENTITY_DISABLED, // 实体禁用
|
||||
TAG_ADDED, // 标签添加
|
||||
TAG_REMOVED // 标签移除
|
||||
}
|
||||
```
|
||||
|
||||
### 批量处理配置
|
||||
|
||||
```typescript
|
||||
const dirtyTracking = entityManager.getDirtyTrackingSystem();
|
||||
|
||||
// 配置批量处理参数
|
||||
dirtyTracking.configure({
|
||||
batchSize: 100, // 每批处理100个脏标记
|
||||
timeSliceMs: 16, // 每帧最多处理16ms
|
||||
processingInterval: 1 // 每帧处理一次
|
||||
});
|
||||
|
||||
// 监听脏标记事件
|
||||
dirtyTracking.addListener(DirtyType.COMPONENT_MODIFIED, (entity, component) => {
|
||||
// 响应组件修改
|
||||
this.invalidateRenderCache(entity);
|
||||
}, { priority: 10 });
|
||||
```
|
||||
|
||||
### 性能监控
|
||||
|
||||
```typescript
|
||||
const dirtyStats = dirtyTracking.getPerformanceStats();
|
||||
console.log('脏标记性能:', {
|
||||
totalMarks: dirtyStats.totalMarks,
|
||||
batchesProcessed: dirtyStats.batchesProcessed,
|
||||
averageBatchTime: dirtyStats.averageBatchTime,
|
||||
queueSize: dirtyStats.currentQueueSize
|
||||
});
|
||||
```
|
||||
|
||||
## 查询优化策略
|
||||
|
||||
### 查询层次选择
|
||||
|
||||
根据查询复杂度选择最优方法:
|
||||
|
||||
```typescript
|
||||
// 1. 简单查询:直接使用索引
|
||||
const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent);
|
||||
|
||||
// 2. 双组件查询:使用Archetype
|
||||
const movingEntities = entityManager.getEntitiesWithComponents([
|
||||
PositionComponent,
|
||||
VelocityComponent
|
||||
]);
|
||||
|
||||
// 3. 复杂查询:组合使用
|
||||
const combatants = entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, HealthComponent]) // Archetype预筛选
|
||||
.withTag("combat") // 索引过滤
|
||||
.where(entity => { // 自定义精确过滤
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
return health.currentHealth > health.maxHealth * 0.3;
|
||||
})
|
||||
.execute();
|
||||
```
|
||||
|
||||
### 查询缓存策略
|
||||
|
||||
```typescript
|
||||
class CombatSystem extends EntitySystem {
|
||||
private cachedEnemies: Entity[] = [];
|
||||
private lastEnemyCacheUpdate = 0;
|
||||
|
||||
process() {
|
||||
const currentTime = performance.now();
|
||||
|
||||
// 每200ms更新一次敌人缓存
|
||||
if (currentTime - this.lastEnemyCacheUpdate > 200) {
|
||||
this.cachedEnemies = this.entityManager
|
||||
.getEntitiesByTag("enemy");
|
||||
this.lastEnemyCacheUpdate = currentTime;
|
||||
}
|
||||
|
||||
// 使用缓存的结果
|
||||
this.processCombat(this.cachedEnemies);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 内存优化
|
||||
|
||||
### 内存使用监控
|
||||
|
||||
```typescript
|
||||
// 获取各系统内存使用情况
|
||||
const memoryStats = entityManager.getMemoryUsage();
|
||||
console.log('内存使用情况:', {
|
||||
entityIndex: memoryStats.entityIndex, // 实体索引
|
||||
componentIndex: memoryStats.componentIndex, // 组件索引
|
||||
archetype: memoryStats.archetype, // 原型系统
|
||||
dirtyTracking: memoryStats.dirtyTracking, // 脏标记
|
||||
total: memoryStats.total
|
||||
});
|
||||
```
|
||||
|
||||
### 内存清理策略
|
||||
|
||||
```typescript
|
||||
// 定期内存清理
|
||||
setInterval(() => {
|
||||
entityManager.cleanup(); // 清理无效引用
|
||||
entityManager.compact(); // 压缩数据结构
|
||||
}, 30000); // 每30秒清理一次
|
||||
|
||||
// 游戏场景切换时的深度清理
|
||||
function switchScene() {
|
||||
entityManager.destroyAllEntities();
|
||||
entityManager.cleanup();
|
||||
entityManager.compact();
|
||||
|
||||
// 重置优化系统
|
||||
entityManager.getComponentIndex().reset();
|
||||
entityManager.getArchetypeSystem().clearCache();
|
||||
entityManager.getDirtyTrackingSystem().clear();
|
||||
}
|
||||
```
|
||||
|
||||
## 实战优化案例
|
||||
|
||||
### 大规模射击游戏优化
|
||||
|
||||
```typescript
|
||||
class BulletSystem extends EntitySystem {
|
||||
private bulletPool: Entity[] = [];
|
||||
private maxBullets = 1000;
|
||||
|
||||
constructor(entityManager: EntityManager) {
|
||||
super();
|
||||
this.prewarmBulletPool();
|
||||
}
|
||||
|
||||
private prewarmBulletPool() {
|
||||
// 预创建子弹池
|
||||
this.bulletPool = this.entityManager.createEntities(
|
||||
this.maxBullets,
|
||||
"Bullet"
|
||||
);
|
||||
|
||||
// 初始化为非激活状态
|
||||
this.bulletPool.forEach(bullet => {
|
||||
bullet.enabled = false;
|
||||
bullet.addComponent(new PositionComponent());
|
||||
bullet.addComponent(new VelocityComponent());
|
||||
bullet.addComponent(new BulletComponent());
|
||||
});
|
||||
}
|
||||
|
||||
public spawnBullet(x: number, y: number, vx: number, vy: number): Entity | null {
|
||||
// 从池中获取非激活子弹(使用索引快速查询)
|
||||
const availableBullet = this.entityManager
|
||||
.query()
|
||||
.withAll([BulletComponent])
|
||||
.active(false)
|
||||
.first();
|
||||
|
||||
if (availableBullet) {
|
||||
// 重用现有子弹
|
||||
const pos = availableBullet.getComponent(PositionComponent);
|
||||
const vel = availableBullet.getComponent(VelocityComponent);
|
||||
|
||||
pos.x = x; pos.y = y;
|
||||
vel.x = vx; vel.y = vy;
|
||||
availableBullet.enabled = true;
|
||||
|
||||
return availableBullet;
|
||||
}
|
||||
|
||||
return null; // 池已满
|
||||
}
|
||||
|
||||
process() {
|
||||
// 批量处理所有激活的子弹
|
||||
this.entityManager.forEachEntityWithComponent(
|
||||
BulletComponent,
|
||||
(entity, bullet) => {
|
||||
if (!entity.enabled) return;
|
||||
|
||||
// 更新位置
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
const vel = entity.getComponent(VelocityComponent);
|
||||
|
||||
pos.x += vel.x * Time.deltaTime;
|
||||
pos.y += vel.y * Time.deltaTime;
|
||||
|
||||
// 边界检查,回收到池中
|
||||
if (pos.x < 0 || pos.x > 800 || pos.y < 0 || pos.y > 600) {
|
||||
entity.enabled = false; // 回收而不是销毁
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AI系统性能优化
|
||||
|
||||
```typescript
|
||||
class AISystem extends EntitySystem {
|
||||
private spatialGrid: SpatialGrid;
|
||||
private updateFrequency = 60; // 60Hz更新频率
|
||||
private lastUpdate = 0;
|
||||
|
||||
process() {
|
||||
const currentTime = performance.now();
|
||||
|
||||
// 控制更新频率
|
||||
if (currentTime - this.lastUpdate < 1000 / this.updateFrequency) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用空间分区优化邻居查询
|
||||
const aiEntities = this.entityManager
|
||||
.query()
|
||||
.withAll([PositionComponent, AIComponent])
|
||||
.active(true)
|
||||
.execute();
|
||||
|
||||
// 分批处理AI实体
|
||||
const batchSize = 50;
|
||||
for (let i = 0; i < aiEntities.length; i += batchSize) {
|
||||
const batch = aiEntities.slice(i, i + batchSize);
|
||||
this.processBatch(batch);
|
||||
|
||||
// 时间片控制,避免单帧卡顿
|
||||
if (performance.now() - currentTime > 10) { // 10ms时间片
|
||||
break; // 下一帧继续处理
|
||||
}
|
||||
}
|
||||
|
||||
this.lastUpdate = currentTime;
|
||||
}
|
||||
|
||||
private processBatch(entities: Entity[]) {
|
||||
entities.forEach(entity => {
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
const ai = entity.getComponent(AIComponent);
|
||||
|
||||
// 空间查询优化邻居搜索
|
||||
const neighbors = this.spatialGrid.queryRadius(pos.x, pos.y, ai.sightRange);
|
||||
|
||||
// AI决策逻辑
|
||||
ai.update(neighbors);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能监控工具
|
||||
|
||||
### 实时性能仪表板
|
||||
|
||||
```typescript
|
||||
class PerformanceDashboard {
|
||||
private stats: any = {};
|
||||
private updateInterval = 1000; // 1秒更新一次
|
||||
|
||||
constructor(private entityManager: EntityManager) {
|
||||
setInterval(() => this.updateStats(), this.updateInterval);
|
||||
}
|
||||
|
||||
private updateStats() {
|
||||
this.stats = {
|
||||
// 基础统计
|
||||
entities: this.entityManager.getStatistics(),
|
||||
|
||||
// 组件索引
|
||||
componentIndex: this.entityManager.getComponentIndex().getPerformanceStats(),
|
||||
|
||||
// Archetype系统
|
||||
archetype: this.entityManager.getArchetypeSystem().getStatistics(),
|
||||
|
||||
// 脏标记系统
|
||||
dirtyTracking: this.entityManager.getDirtyTrackingSystem().getPerformanceStats(),
|
||||
|
||||
// 内存使用
|
||||
memory: this.entityManager.getMemoryUsage(),
|
||||
|
||||
// 计算性能指标
|
||||
performance: this.calculatePerformanceMetrics()
|
||||
};
|
||||
|
||||
this.displayStats();
|
||||
}
|
||||
|
||||
private calculatePerformanceMetrics() {
|
||||
const componentStats = this.stats.componentIndex;
|
||||
const archetypeStats = this.stats.archetype;
|
||||
|
||||
return {
|
||||
queryHitRate: componentStats.hitRate,
|
||||
archetypeEfficiency: archetypeStats.averageEntitiesPerArchetype,
|
||||
memoryEfficiency: this.stats.memory.compressionRatio,
|
||||
overallPerformance: this.calculateOverallScore()
|
||||
};
|
||||
}
|
||||
|
||||
private displayStats() {
|
||||
console.log('=== ECS性能仪表板 ===');
|
||||
console.log('查询命中率:', this.stats.performance.queryHitRate.toFixed(2) + '%');
|
||||
console.log('内存使用:', (this.stats.memory.total / 1024 / 1024).toFixed(2) + 'MB');
|
||||
console.log('整体性能评分:', this.stats.performance.overallPerformance.toFixed(1) + '/10');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 优化检查清单
|
||||
|
||||
### 开发阶段
|
||||
|
||||
- [ ] 使用EntityManager而不是直接操作Scene
|
||||
- [ ] 优先使用组件查询和标签查询
|
||||
- [ ] 设计合理的组件组合,避免过度碎片化
|
||||
- [ ] 实现对象池机制减少频繁创建/销毁
|
||||
|
||||
### 运行时优化
|
||||
|
||||
- [ ] 监控查询命中率,保持在80%以上
|
||||
- [ ] 控制Archetype数量,避免过度分散
|
||||
- [ ] 配置适当的脏标记批量处理参数
|
||||
- [ ] 定期进行内存清理和数据压缩
|
||||
|
||||
### 性能监控
|
||||
|
||||
- [ ] 定期检查性能统计数据
|
||||
- [ ] 监控内存使用趋势
|
||||
- [ ] 设置性能预警阈值
|
||||
- [ ] 在不同设备上进行性能测试
|
||||
|
||||
通过系统性地应用这些优化策略,您可以构建出在各种规模下都能提供卓越性能的ECS游戏系统。
|
||||
306
docs/performance.md
Normal file
306
docs/performance.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# ECS框架性能基准
|
||||
|
||||
本文档展示了ECS框架的真实性能数据和瓶颈分析。
|
||||
|
||||
## 🚀 快速测试
|
||||
|
||||
```bash
|
||||
# 快速性能基准测试
|
||||
npm run benchmark
|
||||
|
||||
# 完整性能测试
|
||||
npm run test:performance
|
||||
|
||||
# 单元测试
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
## 📊 性能基准数据
|
||||
|
||||
> 测试环境: Node.js, Windows 10, 现代桌面CPU
|
||||
|
||||
### 1. 实体创建性能
|
||||
|
||||
| 实体数量 | 创建时间 | 创建速度 | 每个实体耗时 | 性能等级 |
|
||||
|---------|---------|---------|-------------|---------|
|
||||
| 1,000 | 1.56ms | 640,697个/秒 | 0.0016ms | 🚀 极致 |
|
||||
| 5,000 | 19.47ms | 256,805个/秒 | 0.0039ms | 🚀 极致 |
|
||||
| 10,000 | 39.94ms | 250,345个/秒 | 0.0040ms | 🚀 极致 |
|
||||
| 50,000 | 258.17ms | 193,673个/秒 | 0.0052ms | ✅ 优秀 |
|
||||
| 100,000 | 463.04ms | 215,963个/秒 | 0.0046ms | ✅ 优秀 |
|
||||
| 500,000 | 3,087ms | 161,990个/秒 | 0.0062ms | ✅ 优秀 |
|
||||
|
||||
**结论**: 🚀 实体创建性能达到极致水平,大规模创建50万实体仅需3秒
|
||||
|
||||
### 2. 性能瓶颈分析 (500,000个实体)
|
||||
|
||||
**当前瓶颈分布**:
|
||||
```
|
||||
实体创建: 46.3% (1,429ms)
|
||||
组件添加: 53.5% (1,651ms) ← 主要瓶颈
|
||||
标签分配: 0.2% (7ms)
|
||||
```
|
||||
|
||||
**特征**: 框架实现了均衡的性能分布,各部分开销相对合理
|
||||
|
||||
### 3. 组件添加性能详细分析
|
||||
|
||||
| 组件类型 | 添加速度 | 平均耗时 | 性能等级 |
|
||||
|---------|---------|---------|---------|
|
||||
| PositionComponent | 596,929组件/秒 | 0.0017ms | 🚀 极致 |
|
||||
| VelocityComponent | 1,186,770组件/秒 | 0.0008ms | 🚀 极致 |
|
||||
| HealthComponent | 841,982组件/秒 | 0.0012ms | 🚀 极致 |
|
||||
| RenderComponent | 763,351组件/秒 | 0.0013ms | 🚀 极致 |
|
||||
| AIComponent | 185,964组件/秒 | 0.0054ms | ✅ 优秀 |
|
||||
|
||||
### 4. 优化技术性能影响
|
||||
|
||||
| 优化技术 | 性能提升 | 内存影响 | 适用场景 |
|
||||
|---------|---------|---------|---------|
|
||||
| 组件对象池 | 30-50% | 减少分配 | 频繁创建/销毁 |
|
||||
| 位掩码优化器 | 20-40% | 缓存开销 | 大量查询操作 |
|
||||
| 批量操作 | 显著提升 | 无明显影响 | 大规模实体创建 |
|
||||
| 延迟索引更新 | 60-80% | 临时内存增加 | 批量实体操作 |
|
||||
| 索引去重优化 | 避免O(n) | 轻微内存增加 | 防止重复实体 |
|
||||
|
||||
### 5. 查询系统性能
|
||||
|
||||
#### 5.1 基础查询性能
|
||||
| 查询类型 | 查询速度 | 每次查询耗时 | 性能等级 |
|
||||
|---------|---------|-------------|---------|
|
||||
| 单组件查询 | 12,178次/秒 | 0.082ms | ✅ 优秀 |
|
||||
| 多组件查询 | 9,439次/秒 | 0.106ms | ✅ 优秀 |
|
||||
| 复合查询 | 7,407次/秒 | 0.135ms | ✅ 良好 |
|
||||
|
||||
#### 5.2 缓存查询性能
|
||||
| 缓存状态 | 访问速度 | 性能特征 |
|
||||
|---------|---------|---------|
|
||||
| 缓存命中 | 零延迟 | 🚀 即时响应 |
|
||||
| 缓存未命中 | 标准查询 | ✅ 自动构建 |
|
||||
| 缓存清理 | 批量延迟 | 🔧 优化策略 |
|
||||
|
||||
### 6. 新功能性能基准
|
||||
|
||||
#### 6.1 组件对象池性能
|
||||
```
|
||||
📊 对象池 vs 直接创建 (10,000次操作)
|
||||
对象池获取: 1.65ms (6,060,606次/秒)
|
||||
直接创建: 1.51ms (6,622,516次/秒)
|
||||
|
||||
⚠️ 小规模测试中对象池可能略慢,但在大规模应用中:
|
||||
- 减少30-50%的内存分配
|
||||
- 避免垃圾回收压力
|
||||
- 提升长期运行稳定性
|
||||
```
|
||||
|
||||
#### 6.2 位掩码优化器性能
|
||||
```
|
||||
🔥 位掩码操作性能 (100,000次操作)
|
||||
单个掩码创建: 20.00ms (5,000,000次/秒)
|
||||
组合掩码创建: 53.69ms (1,862,285次/秒)
|
||||
缓存掩码访问: <1ms (近零延迟)
|
||||
```
|
||||
|
||||
## 🎯 性能扩展性分析
|
||||
|
||||
### 实体创建扩展性
|
||||
```
|
||||
📈 创建速度趋势分析
|
||||
1K-10K实体: 250,000-640,000 实体/秒 (优秀)
|
||||
10K-100K实体: 200,000-250,000 实体/秒 (良好)
|
||||
100K-500K实体: 160,000-220,000 实体/秒 (稳定)
|
||||
|
||||
结论: 性能随规模稳定下降,无突然性能悬崖
|
||||
```
|
||||
|
||||
### 内存使用效率
|
||||
| 实体数量 | 内存使用 | 每实体内存 | 内存效率 |
|
||||
|---------|---------|-----------|---------|
|
||||
| 1,000 | 3.5MB | 3.5KB | 🚀 极致 |
|
||||
| 5,000 | 7.1MB | 1.4KB | 🚀 极致 |
|
||||
| 10,000 | 20.8MB | 2.1KB | ✅ 优秀 |
|
||||
| 50,000 | ~100MB | ~2KB | ✅ 优秀 |
|
||||
|
||||
## 💡 性能优化建议
|
||||
|
||||
### 1. 实体创建最佳实践
|
||||
|
||||
**✅ 推荐做法**:
|
||||
```typescript
|
||||
// 使用批量创建API
|
||||
const entities = scene.createEntities(10000, "Enemies");
|
||||
|
||||
// 延迟缓存清理
|
||||
entities.forEach(entity => {
|
||||
scene.addEntity(entity, false); // 延迟清理
|
||||
});
|
||||
scene.querySystem.clearCache(); // 手动清理
|
||||
```
|
||||
|
||||
**❌ 避免做法**:
|
||||
```typescript
|
||||
// 避免循环单个创建
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
scene.createEntity("Enemy" + i); // 每次触发缓存清理
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 组件池优化策略
|
||||
|
||||
**预热策略**:
|
||||
```typescript
|
||||
// 预热常用组件池
|
||||
ComponentPoolManager.getInstance().preWarmPools({
|
||||
BulletComponent: 2000, // 子弹大量创建
|
||||
EffectComponent: 1000, // 特效频繁使用
|
||||
PickupComponent: 500 // 道具适量缓存
|
||||
});
|
||||
```
|
||||
|
||||
**使用模式**:
|
||||
```typescript
|
||||
// 高效的组件复用
|
||||
const bullet = ComponentPoolManager.getInstance().getComponent(BulletComponent);
|
||||
bullet.reset(); // 重置状态
|
||||
entity.addComponent(bullet);
|
||||
|
||||
// 销毁时释放到池
|
||||
ComponentPoolManager.getInstance().releaseComponent(bullet);
|
||||
```
|
||||
|
||||
### 3. 查询优化策略
|
||||
|
||||
**缓存策略**:
|
||||
```typescript
|
||||
// 缓存频繁查询结果
|
||||
class MovementSystem extends EntitySystem {
|
||||
private cachedMovingEntities: Entity[];
|
||||
private lastCacheFrame: number = 0;
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
// 每5帧更新一次缓存
|
||||
if (Time.frameCount - this.lastCacheFrame > 5) {
|
||||
this.cachedMovingEntities = scene.getEntitiesWithComponents([Position, Velocity]);
|
||||
this.lastCacheFrame = Time.frameCount;
|
||||
}
|
||||
|
||||
// 使用缓存结果
|
||||
this.processMovement(this.cachedMovingEntities);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 不同规模应用建议
|
||||
|
||||
#### 小型游戏 (< 5,000实体)
|
||||
- ✅ 可以随意使用所有功能
|
||||
- ✅ 不需要特殊优化
|
||||
- ✅ 专注于游戏逻辑开发
|
||||
|
||||
#### 中型游戏 (5,000-50,000实体)
|
||||
- ✅ 使用批量操作API
|
||||
- ✅ 启用组件对象池
|
||||
- ⚠️ 注意查询频率
|
||||
|
||||
#### 大型游戏 (50,000+实体)
|
||||
- 🚀 必须使用批量操作
|
||||
- 🚀 必须启用对象池
|
||||
- 🚀 必须缓存查询结果
|
||||
- 🚀 考虑分区处理
|
||||
|
||||
## 🌍 平台性能对比
|
||||
|
||||
### Windows 桌面端 (测试平台)
|
||||
- **实体创建**: 640,697实体/秒
|
||||
- **组件操作**: 596,929组件/秒
|
||||
- **推荐实体数**: ≤ 200,000
|
||||
|
||||
### 预估其他平台性能
|
||||
|
||||
| 平台类型 | 预估性能比例 | 推荐实体数 | 特殊注意 |
|
||||
|---------|-------------|-----------|---------|
|
||||
| macOS桌面 | 90-100% | ≤ 180,000 | 内存管理优秀 |
|
||||
| Linux桌面 | 95-105% | ≤ 200,000 | 性能最优 |
|
||||
| Chrome浏览器 | 60-80% | ≤ 100,000 | V8引擎优化 |
|
||||
| Firefox浏览器 | 50-70% | ≤ 80,000 | SpiderMonkey限制 |
|
||||
| Safari浏览器 | 55-75% | ≤ 90,000 | JavaScriptCore |
|
||||
| Node.js服务器 | 100-110% | ≤ 500,000 | 服务器级性能 |
|
||||
| Android Chrome | 30-50% | ≤ 30,000 | 移动端限制 |
|
||||
| iOS Safari | 40-60% | ≤ 40,000 | iOS优化较好 |
|
||||
|
||||
## 🔬 测试环境详情
|
||||
|
||||
### 硬件环境
|
||||
- **操作系统**: Windows 10 (Build 26100)
|
||||
- **处理器**: 现代桌面CPU
|
||||
- **内存**: 充足RAM
|
||||
- **存储**: SSD高速存储
|
||||
|
||||
### 软件环境
|
||||
- **Node.js**: v16+
|
||||
- **TypeScript**: v5.8.3
|
||||
- **ECS框架版本**: v2.0.6
|
||||
- **测试工具**: 内置基准测试套件
|
||||
|
||||
### 测试方法
|
||||
- **实体配置**: 位置、速度、生命值、渲染、AI组件随机分配
|
||||
- **测试迭代**: 多次测试取平均值
|
||||
- **内存监控**: 实时内存使用情况
|
||||
- **性能指标**: performance.now()高精度计时
|
||||
|
||||
## 📋 性能测试清单
|
||||
|
||||
### 运行完整性能测试
|
||||
|
||||
```bash
|
||||
# 1. 快速基准测试 (2-3分钟)
|
||||
npm run benchmark
|
||||
|
||||
# 2. 完整性能测试 (10-15分钟)
|
||||
npm run test:performance
|
||||
|
||||
# 3. 单元测试验证 (30秒)
|
||||
npm run test:unit
|
||||
|
||||
# 4. 所有测试 (15-20分钟)
|
||||
npm run test
|
||||
```
|
||||
|
||||
### 自定义性能测试
|
||||
|
||||
```typescript
|
||||
import { runEntityCreationBenchmark } from '@esengine/ecs-framework/Testing/Performance/benchmark';
|
||||
|
||||
// 自定义规模测试
|
||||
await runEntityCreationBenchmark([1000, 5000, 10000]);
|
||||
|
||||
// 组件性能测试
|
||||
await runComponentPerformanceTest();
|
||||
|
||||
// 查询性能测试
|
||||
await runQueryPerformanceTest();
|
||||
```
|
||||
|
||||
## 🏆 性能总结
|
||||
|
||||
### 🎯 核心能力
|
||||
1. **实体创建速度**: 最高64万实体/秒
|
||||
2. **大规模处理**: 50万实体仅需3秒创建
|
||||
3. **均衡性能**: 各组件开销分布合理
|
||||
4. **扩展性**: 性能随规模线性下降,无突然悬崖
|
||||
|
||||
### 🔧 技术特点
|
||||
1. **批量操作架构** - 大幅减少单次操作开销
|
||||
2. **智能缓存策略** - 延迟清理机制
|
||||
3. **索引系统优化** - 避免O(n)操作
|
||||
4. **内存管理优化** - 对象池和位掩码缓存
|
||||
|
||||
### 🌟 实际应用价值
|
||||
- **小型游戏**: 性能过剩,专注玩法
|
||||
- **中型游戏**: 性能充足,适度优化
|
||||
- **大型游戏**: 需要优化策略,但完全可行
|
||||
- **服务器端**: 可处理大规模实体管理
|
||||
|
||||
---
|
||||
|
||||
**结论**: ECS框架达到了产品级性能标准,能够满足从休闲小游戏到复杂RTS游戏的各种需求。框架层面的性能已经充分优化,为开发者提供了坚实的性能基础。
|
||||
324
docs/query-system-usage.md
Normal file
324
docs/query-system-usage.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# QuerySystem 使用指南
|
||||
|
||||
QuerySystem 是 ECS Framework 中的高性能实体查询系统,支持多级索引、智能缓存和类型安全的查询操作。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 1. 获取查询系统
|
||||
|
||||
```typescript
|
||||
import { Scene, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
// 创建场景,查询系统会自动创建
|
||||
const scene = new Scene();
|
||||
const querySystem = scene.querySystem;
|
||||
|
||||
// 或者从Core获取当前场景的查询系统
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
const currentQuerySystem = Core.scene?.querySystem;
|
||||
```
|
||||
|
||||
### 2. 基本查询操作
|
||||
|
||||
```typescript
|
||||
// 查询包含所有指定组件的实体
|
||||
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
console.log(`找到 ${result.count} 个实体`);
|
||||
|
||||
// 查询包含任意指定组件的实体
|
||||
const anyResult = querySystem.queryAny(HealthComponent, ManaComponent);
|
||||
|
||||
// 查询不包含指定组件的实体
|
||||
const noneResult = querySystem.queryNone(DeadComponent);
|
||||
```
|
||||
|
||||
### 3. 类型安全查询
|
||||
|
||||
```typescript
|
||||
// 类型安全的查询,返回实体和对应的组件
|
||||
const typedResult = querySystem.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 '@esengine/ecs-framework';
|
||||
|
||||
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. 代码组织
|
||||
|
||||
- 将复杂查询封装到专门的方法中
|
||||
- 使用查询构建器创建可读性更好的查询
|
||||
- 在系统中合理组织查询逻辑
|
||||
Submodule extensions/behaviourTree-ai deleted from 192de123d3
@@ -1,319 +0,0 @@
|
||||
declare module es {
|
||||
abstract class AbstractTweenable implements ITweenable {
|
||||
protected _isPaused: boolean;
|
||||
protected _isCurrentlyManagedByTweenManager: boolean;
|
||||
abstract tick(): boolean;
|
||||
recycleSelf(): void;
|
||||
isRunning(): boolean;
|
||||
start(): void;
|
||||
pause(): void;
|
||||
resume(): void;
|
||||
stop(bringToCompletion?: boolean): void;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
class PropertyTweens {
|
||||
static NumberPropertyTo(self: any, memberName: string, to: number, duration: number): ITween<number>;
|
||||
static Vector2PropertyTo(self: any, memeberName: string, to: Vector2, duration: number): ITween<Vector2>;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
class TransformSpringTween extends AbstractTweenable {
|
||||
readonly targetType: TransformTargetType;
|
||||
private _transform;
|
||||
private _targetType;
|
||||
private _targetValue;
|
||||
private _velocity;
|
||||
dampingRatio: number;
|
||||
angularFrequency: number;
|
||||
constructor(transform: Transform, targetType: TransformTargetType, targetValue: Vector2);
|
||||
setTargetValue(targetValue: Vector2): void;
|
||||
updateDampingRatioWithHalfLife(lambda: number): void;
|
||||
tick(): boolean;
|
||||
private setTweenedValue;
|
||||
private getCurrentValueOfTweenedTargetType;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
enum LoopType {
|
||||
none = 0,
|
||||
restartFromBeginning = 1,
|
||||
pingpong = 2
|
||||
}
|
||||
enum TweenState {
|
||||
running = 0,
|
||||
paused = 1,
|
||||
complete = 2
|
||||
}
|
||||
abstract class Tween<T> implements ITweenable, ITween<T> {
|
||||
protected _target: ITweenTarget<T>;
|
||||
protected _isFromValueOverridden: boolean;
|
||||
protected _fromValue: T;
|
||||
protected _toValue: T;
|
||||
protected _easeType: EaseType;
|
||||
protected _shouldRecycleTween: boolean;
|
||||
protected _isRelative: boolean;
|
||||
protected _completionHandler: (tween: ITween<T>) => void;
|
||||
protected _loopCompleteHandler: (tween: ITween<T>) => void;
|
||||
protected _nextTween: ITweenable;
|
||||
protected _tweenState: TweenState;
|
||||
private _isTimeScaleIndependent;
|
||||
protected _delay: number;
|
||||
protected _duration: number;
|
||||
protected _timeScale: number;
|
||||
protected _elapsedTime: number;
|
||||
protected _loopType: LoopType;
|
||||
protected _loops: number;
|
||||
protected _delayBetweenLoops: number;
|
||||
private _isRunningInReverse;
|
||||
context: any;
|
||||
setEaseType(easeType: EaseType): ITween<T>;
|
||||
setDelay(delay: number): ITween<T>;
|
||||
setDuration(duration: number): ITween<T>;
|
||||
setTimeScale(timeSclae: number): ITween<T>;
|
||||
setIsTimeScaleIndependent(): ITween<T>;
|
||||
setCompletionHandler(completeHandler: (tween: ITween<T>) => void): ITween<T>;
|
||||
setLoops(loopType: LoopType, loops?: number, delayBetweenLoops?: number): ITween<T>;
|
||||
setLoopCompletionHanlder(loopCompleteHandler: (tween: ITween<T>) => void): ITween<T>;
|
||||
setFrom(from: T): ITween<T>;
|
||||
prepareForReuse(from: T, to: T, duration: number): ITween<T>;
|
||||
setRecycleTween(shouldRecycleTween: boolean): ITween<T>;
|
||||
abstract setIsRelative(): ITween<T>;
|
||||
setContext(context: any): ITween<T>;
|
||||
setNextTween(nextTween: ITweenable): ITween<T>;
|
||||
tick(): boolean;
|
||||
recycleSelf(): void;
|
||||
isRunning(): boolean;
|
||||
start(): void;
|
||||
pause(): void;
|
||||
resume(): void;
|
||||
stop(bringToCompletion?: boolean): void;
|
||||
jumpToElapsedTime(elapsedTime: any): void;
|
||||
reverseTween(): void;
|
||||
waitForCompletion(): IterableIterator<any>;
|
||||
getTargetObject(): any;
|
||||
private resetState;
|
||||
initialize(target: ITweenTarget<T>, to: T, duration: number): void;
|
||||
private handleLooping;
|
||||
protected abstract updateValue(): any;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
class NumberTween extends Tween<number> {
|
||||
static create(): NumberTween;
|
||||
constructor(target?: ITweenTarget<number>, to?: number, duration?: number);
|
||||
setIsRelative(): ITween<number>;
|
||||
protected updateValue(): void;
|
||||
recycleSelf(): void;
|
||||
}
|
||||
class Vector2Tween extends Tween<Vector2> {
|
||||
static create(): Vector2Tween;
|
||||
constructor(target?: ITweenTarget<Vector2>, to?: Vector2, duration?: number);
|
||||
setIsRelative(): ITween<Vector2>;
|
||||
protected updateValue(): void;
|
||||
recycleSelf(): void;
|
||||
}
|
||||
class RectangleTween extends Tween<Rectangle> {
|
||||
static create(): RectangleTween;
|
||||
constructor(target?: ITweenTarget<Rectangle>, to?: Rectangle, duration?: number);
|
||||
setIsRelative(): ITween<Rectangle>;
|
||||
protected updateValue(): void;
|
||||
recycleSelf(): void;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
enum TransformTargetType {
|
||||
position = 0,
|
||||
localPosition = 1,
|
||||
scale = 2,
|
||||
localScale = 3,
|
||||
rotationDegrees = 4,
|
||||
localRotationDegrees = 5
|
||||
}
|
||||
class TransformVector2Tween extends Vector2Tween implements ITweenTarget<Vector2> {
|
||||
private _transform;
|
||||
private _targetType;
|
||||
setTweenedValue(value: Vector2): void;
|
||||
getTweenedValue(): Vector2;
|
||||
getTargetObject(): Transform;
|
||||
setTargetAndType(transform: Transform, targetType: TransformTargetType): void;
|
||||
protected updateValue(): void;
|
||||
recycleSelf(): void;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
enum EaseType {
|
||||
linear = 0,
|
||||
sineIn = 1,
|
||||
sineOut = 2,
|
||||
sineInOut = 3,
|
||||
quadIn = 4,
|
||||
quadOut = 5,
|
||||
quadInOut = 6,
|
||||
quintIn = 7,
|
||||
quintOut = 8,
|
||||
quintInOut = 9,
|
||||
cubicIn = 10,
|
||||
cubicOut = 11,
|
||||
cubicInOut = 12,
|
||||
quartIn = 13,
|
||||
quartOut = 14,
|
||||
quartInOut = 15,
|
||||
expoIn = 16,
|
||||
expoOut = 17,
|
||||
expoInOut = 18,
|
||||
circleIn = 19,
|
||||
circleOut = 20,
|
||||
circleInOut = 21,
|
||||
elasticIn = 22,
|
||||
elasticOut = 23,
|
||||
elasticInOut = 24,
|
||||
punch = 25,
|
||||
backIn = 26,
|
||||
backOut = 27,
|
||||
backInOut = 28,
|
||||
bounceIn = 29,
|
||||
bounceOut = 30,
|
||||
bounceInOut = 31
|
||||
}
|
||||
class EaseHelper {
|
||||
static oppositeEaseType(easeType: EaseType): EaseType.linear | EaseType.sineIn | EaseType.sineOut | EaseType.sineInOut | EaseType.quadIn | EaseType.quadOut | EaseType.quadInOut | EaseType.quintIn | EaseType.quintOut | EaseType.quintInOut | EaseType.cubicIn | EaseType.cubicOut | EaseType.cubicInOut | EaseType.quartIn | EaseType.quartInOut | EaseType.expoIn | EaseType.expoOut | EaseType.expoInOut | EaseType.circleIn | EaseType.circleOut | EaseType.circleInOut | EaseType.elasticIn | EaseType.elasticOut | EaseType.elasticInOut | EaseType.punch | EaseType.backIn | EaseType.backOut | EaseType.backInOut | EaseType.bounceIn | EaseType.bounceOut | EaseType.bounceInOut;
|
||||
static ease(easeType: EaseType, t: number, duration: number): number;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
class TweenManager extends GlobalManager {
|
||||
static defaultEaseType: EaseType;
|
||||
static removeAllTweensOnLevelLoad: boolean;
|
||||
static cacheNumberTweens: boolean;
|
||||
static cacheVector2Tweens: boolean;
|
||||
static cacheRectTweens: boolean;
|
||||
private _activeTweens;
|
||||
private _tempTweens;
|
||||
private _isUpdating;
|
||||
private static _instance;
|
||||
constructor();
|
||||
update(): void;
|
||||
static addTween(tween: ITweenable): void;
|
||||
static removeTween(tween: ITweenable): void;
|
||||
static stopAllTweens(bringToCompletion?: boolean): void;
|
||||
static allTweensWithContext(context: any): ITweenable[];
|
||||
static stopAllTweensWithContext(context: any, bringToCompletion?: boolean): void;
|
||||
static allTweenWithTarget(target: any): ITweenable[];
|
||||
static stopAllTweensWithTarget(target: any, bringToCompletion?: boolean): void;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
module Easing {
|
||||
class Linear {
|
||||
static easeNone(t: number, d: number): number;
|
||||
}
|
||||
class Quadratic {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Back {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Bounce {
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Circular {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Cubic {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Elastic {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
static punch(t: number, d: number): number;
|
||||
}
|
||||
class Exponential {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Quartic {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Quintic {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
class Sinusoidal {
|
||||
static easeIn(t: number, d: number): number;
|
||||
static easeOut(t: number, d: number): number;
|
||||
static easeInOut(t: number, d: number): number;
|
||||
}
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
class Lerps {
|
||||
static lerp(from: number, to: number, t: number): number;
|
||||
static lerpVector2(from: Vector2, to: Vector2, t: number): Vector2;
|
||||
static lerpRectangle(from: Rectangle, to: Rectangle, t: number): Rectangle;
|
||||
static angleLerp(from: Vector2, to: Vector2, t: number): Vector2;
|
||||
static ease(easeType: EaseType, from: number, to: number, t: number, duration: number): number;
|
||||
static easeVector2(easeType: EaseType, from: Vector2, to: Vector2, t: number, duration: number): Vector2;
|
||||
static easeRectangle(easeType: EaseType, from: Rectangle, to: Rectangle, t: number, duration: number): Rectangle;
|
||||
static easeAngle(easeType: EaseType, from: Vector2, to: Vector2, t: number, duration: number): Vector2;
|
||||
static fastSpring(currentValue: Vector2, targetValue: Vector2, velocity: Vector2, dampingRatio: number, angularFrequency: number): Vector2;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
interface ITween<T> extends ITweenControl {
|
||||
setEaseType(easeType: EaseType): ITween<T>;
|
||||
setDelay(delay: number): ITween<T>;
|
||||
setDuration(duration: number): ITween<T>;
|
||||
setTimeScale(timeScale: number): ITween<T>;
|
||||
setIsTimeScaleIndependent(): ITween<T>;
|
||||
setCompletionHandler(completionHandler: (tween: ITween<T>) => void): ITween<T>;
|
||||
setLoops(loopType: LoopType, loops: number, delayBetweenLoops: number): ITween<T>;
|
||||
setFrom(from: T): ITween<T>;
|
||||
prepareForReuse(from: T, to: T, duration: number): ITween<T>;
|
||||
setRecycleTween(shouldRecycleTween: boolean): ITween<T>;
|
||||
setIsRelative(): ITween<T>;
|
||||
setContext(context: any): ITween<T>;
|
||||
setNextTween(nextTween: ITweenable): ITween<T>;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
interface ITweenControl extends ITweenable {
|
||||
context: any;
|
||||
jumpToElapsedTime(elapsedTime: number): any;
|
||||
waitForCompletion(): any;
|
||||
getTargetObject(): any;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
interface ITweenTarget<T> {
|
||||
setTweenedValue(value: T): any;
|
||||
getTweenedValue(): T;
|
||||
getTargetObject(): any;
|
||||
}
|
||||
}
|
||||
declare module es {
|
||||
interface ITweenable {
|
||||
tick(): boolean;
|
||||
recycleSelf(): any;
|
||||
isRunning(): boolean;
|
||||
start(): any;
|
||||
pause(): any;
|
||||
resume(): any;
|
||||
stop(bringToCompletion: boolean): any;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Submodule extensions/ecs-star deleted from 0380608377
Submodule extensions/ecs-tween deleted from 2154498b2f
1333
package-lock.json
generated
Normal file
1333
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
59
package.json
Normal file
59
package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.1.11",
|
||||
"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": {
|
||||
"clean": "rimraf bin wasm dist",
|
||||
"clean:wasm": "rimraf src/wasm/rust-ecs-core/pkg src/wasm/rust-ecs-core/target",
|
||||
"build:wasm": "cd src/wasm/rust-ecs-core && wasm-pack build --target web --out-dir ../../../bin/wasm --release",
|
||||
"build:ts": "tsc",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "npm run build:wasm && npm run build:ts",
|
||||
"build:watch": "tsc --watch",
|
||||
"rebuild": "npm run clean && npm run clean:wasm && npm run build",
|
||||
"build:npm": "npm run build && node scripts/build-rollup.js",
|
||||
"test:benchmark": "npm run build && node bin/Testing/Performance/benchmark.js",
|
||||
"test:unit": "npm run build && node bin/Testing/test-runner.js",
|
||||
"benchmark": "node scripts/benchmark.js",
|
||||
"preversion": "npm run rebuild",
|
||||
"publish:patch": "npm version patch && npm run build:npm && cd dist && npm publish",
|
||||
"publish:minor": "npm version minor && npm run build:npm && cd dist && npm publish",
|
||||
"publish:major": "npm version major && npm run build:npm && cd dist && npm publish",
|
||||
"publish:npm": "npm run build:npm && cd dist && npm publish"
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/node": "^20.19.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup": "^4.42.0",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/esengine/ecs-framework.git"
|
||||
}
|
||||
}
|
||||
99
rollup.config.js
Normal file
99
rollup.config.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const resolve = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const dts = require('rollup-plugin-dts').default;
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
|
||||
|
||||
const banner = `/**
|
||||
* @esengine/ecs-framework v${pkg.version}
|
||||
* 高性能ECS框架 - 适用于Cocos Creator和Laya引擎
|
||||
*
|
||||
* @author ${pkg.author}
|
||||
* @license ${pkg.license}
|
||||
*/`;
|
||||
|
||||
const external = [];
|
||||
|
||||
const commonPlugins = [
|
||||
resolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
}),
|
||||
commonjs({
|
||||
include: /node_modules/
|
||||
})
|
||||
];
|
||||
|
||||
module.exports = [
|
||||
// ES模块构建
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
format: 'es',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
propertyReadSideEffects: false,
|
||||
unknownGlobalSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// UMD构建(可选)
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.umd.js',
|
||||
format: 'umd',
|
||||
name: 'ECSFramework',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// 类型定义构建
|
||||
{
|
||||
input: 'bin/index.d.ts',
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
format: 'es',
|
||||
banner: `/**
|
||||
* @esengine/ecs-framework v${pkg.version}
|
||||
* TypeScript definitions
|
||||
*/`
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
respectExternal: true
|
||||
})
|
||||
],
|
||||
external: []
|
||||
}
|
||||
];
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 MiB |
39
scripts/benchmark.js
Normal file
39
scripts/benchmark.js
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* ECS框架性能基准测试入口
|
||||
*
|
||||
* 使用方法:
|
||||
* node benchmark.js
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
console.log('🚀 启动ECS框架性能基准测试...\n');
|
||||
|
||||
const sourceDir = path.join(__dirname, '..');
|
||||
|
||||
try {
|
||||
console.log('📦 准备构建项目...');
|
||||
|
||||
// 构建TypeScript代码
|
||||
console.log('🔨 构建TypeScript代码...');
|
||||
execSync('npm run build', {
|
||||
stdio: 'inherit',
|
||||
cwd: sourceDir
|
||||
});
|
||||
console.log('✅ TypeScript构建完成\n');
|
||||
|
||||
// 运行性能测试
|
||||
console.log('🏃 运行性能基准测试...');
|
||||
execSync('node bin/Testing/Performance/benchmark.js', {
|
||||
stdio: 'inherit',
|
||||
cwd: sourceDir
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 性能测试失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
148
scripts/build-rollup.js
Normal file
148
scripts/build-rollup.js
Normal file
@@ -0,0 +1,148 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
console.log('🚀 使用 Rollup 构建npm包...');
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// 清理旧的dist目录
|
||||
if (fs.existsSync('./dist')) {
|
||||
console.log('🧹 清理旧的构建文件...');
|
||||
execSync('rimraf ./dist', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// 执行Rollup构建
|
||||
console.log('📦 执行 Rollup 构建...');
|
||||
execSync('rollup -c', { stdio: 'inherit' });
|
||||
|
||||
// 生成package.json
|
||||
console.log('📋 生成 package.json...');
|
||||
generatePackageJson();
|
||||
|
||||
// 复制其他文件
|
||||
console.log('📁 复制必要文件...');
|
||||
copyFiles();
|
||||
|
||||
// 输出构建结果
|
||||
showBuildResults();
|
||||
|
||||
console.log('✅ 构建完成!');
|
||||
console.log('\n🚀 发布命令:');
|
||||
console.log('cd dist && npm publish');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 构建失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function generatePackageJson() {
|
||||
const sourcePackage = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
|
||||
const distPackage = {
|
||||
name: sourcePackage.name,
|
||||
version: sourcePackage.version,
|
||||
description: sourcePackage.description,
|
||||
main: 'index.js',
|
||||
module: 'index.js',
|
||||
types: 'index.d.ts',
|
||||
type: 'module',
|
||||
exports: {
|
||||
'.': {
|
||||
import: './index.js',
|
||||
types: './index.d.ts'
|
||||
}
|
||||
},
|
||||
files: [
|
||||
'index.js',
|
||||
'index.js.map',
|
||||
'index.umd.js',
|
||||
'index.umd.js.map',
|
||||
'index.d.ts',
|
||||
'wasm',
|
||||
'README.md',
|
||||
'LICENSE',
|
||||
'SECURITY.md',
|
||||
'.npmignore'
|
||||
],
|
||||
keywords: [
|
||||
'ecs',
|
||||
'entity-component-system',
|
||||
'game-engine',
|
||||
'typescript',
|
||||
'cocos-creator',
|
||||
'laya',
|
||||
'rollup'
|
||||
],
|
||||
author: sourcePackage.author,
|
||||
license: sourcePackage.license,
|
||||
repository: sourcePackage.repository,
|
||||
bugs: sourcePackage.bugs,
|
||||
homepage: sourcePackage.homepage,
|
||||
engines: {
|
||||
node: '>=16.0.0'
|
||||
},
|
||||
sideEffects: false
|
||||
};
|
||||
|
||||
fs.writeFileSync('./dist/package.json', JSON.stringify(distPackage, null, 2));
|
||||
}
|
||||
|
||||
function copyFiles() {
|
||||
const filesToCopy = [
|
||||
{ src: './README.md', dest: './dist/README.md' },
|
||||
{ src: './LICENSE', dest: './dist/LICENSE' },
|
||||
{ src: './SECURITY.md', dest: './dist/SECURITY.md' },
|
||||
{ src: './.npmignore', dest: './dist/.npmignore' }
|
||||
];
|
||||
|
||||
filesToCopy.forEach(({ src, dest }) => {
|
||||
if (fs.existsSync(src)) {
|
||||
fs.copyFileSync(src, dest);
|
||||
console.log(` ✓ 复制: ${path.basename(dest)}`);
|
||||
} else {
|
||||
console.log(` ⚠️ 文件不存在: ${src}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 复制WASM文件(过滤.gitignore)
|
||||
const wasmDir = './bin/wasm';
|
||||
if (fs.existsSync(wasmDir)) {
|
||||
const distWasmDir = './dist/wasm';
|
||||
if (!fs.existsSync(distWasmDir)) {
|
||||
fs.mkdirSync(distWasmDir);
|
||||
}
|
||||
|
||||
let copiedCount = 0;
|
||||
fs.readdirSync(wasmDir).forEach(file => {
|
||||
// 过滤掉.gitignore文件
|
||||
if (file !== '.gitignore') {
|
||||
fs.copyFileSync(
|
||||
path.join(wasmDir, file),
|
||||
path.join(distWasmDir, file)
|
||||
);
|
||||
copiedCount++;
|
||||
}
|
||||
});
|
||||
if (copiedCount > 0) {
|
||||
console.log(` ✓ 复制: ${copiedCount}个WASM文件`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showBuildResults() {
|
||||
const distDir = './dist';
|
||||
const files = ['index.js', 'index.umd.js', 'index.d.ts'];
|
||||
|
||||
console.log('\n📊 构建结果:');
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(distDir, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const size = fs.statSync(filePath).size;
|
||||
console.log(` ${file}: ${(size / 1024).toFixed(1)}KB`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
2
source/.idea/.gitignore
generated
vendored
2
source/.idea/.gitignore
generated
vendored
@@ -1,2 +0,0 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
||||
6
source/.idea/misc.xml
generated
6
source/.idea/misc.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
8
source/.idea/modules.xml
generated
8
source/.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/source.iml" filepath="$PROJECT_DIR$/.idea/source.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
12
source/.idea/source.iml
generated
12
source/.idea/source.iml
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
source/.idea/vcs.xml
generated
6
source/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
13
source/.vscode/tasks.json
vendored
13
source/.vscode/tasks.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "gulp",
|
||||
"task": "build",
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
}
|
||||
5030
source/bin/framework.d.ts
vendored
5030
source/bin/framework.d.ts
vendored
File diff suppressed because it is too large
Load Diff
12816
source/bin/framework.js
12816
source/bin/framework.js
File diff suppressed because it is too large
Load Diff
1
source/bin/framework.min.js
vendored
1
source/bin/framework.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,43 +0,0 @@
|
||||
'use strict';
|
||||
const gulp = require("gulp");
|
||||
const minify = require('gulp-minify');
|
||||
const inject = require("gulp-inject-string");
|
||||
const ts = require('gulp-typescript');
|
||||
const merge = require('merge2');
|
||||
const tsProject = ts.createProject('tsconfig.json');
|
||||
|
||||
gulp.task('buildJs', () => {
|
||||
return tsProject.src()
|
||||
.pipe(tsProject())
|
||||
.js.pipe(inject.replace('var es;', ''))
|
||||
.pipe(inject.prepend('window.es = {};\n'))
|
||||
.pipe(inject.replace('var __extends =', 'window.__extends ='))
|
||||
.pipe(minify({ ext: { min: ".min.js" } }))
|
||||
.pipe(gulp.dest('./bin'));
|
||||
});
|
||||
|
||||
gulp.task("buildDts", ["buildJs"], () => {
|
||||
return tsProject.src()
|
||||
.pipe(tsProject())
|
||||
// .dts.pipe(inject.append('import e = framework;'))
|
||||
.pipe(gulp.dest('./bin'));
|
||||
});
|
||||
|
||||
gulp.task("copy", ["buildDts"], () => {
|
||||
return gulp.src('bin/**/*')
|
||||
.pipe(gulp.dest('../demo/egret_demo/libs/framework/'))
|
||||
.pipe(gulp.dest('../extensions/behaviourTree-ai/egret-demo/libs/framework/'))
|
||||
});
|
||||
|
||||
gulp.task('build', ['copy'], () => {
|
||||
return merge([
|
||||
gulp.src('bin/*.js')
|
||||
.pipe(gulp.dest('../demo/laya_demo/bin/libs/')),
|
||||
gulp.src('bin/*.ts')
|
||||
.pipe(gulp.dest('../demo/laya_demo/libs/')),
|
||||
gulp.src('bin/framework.d.ts')
|
||||
.pipe(gulp.dest('../extensions/behaviourTree-ai/source/lib/'))
|
||||
.pipe(gulp.dest('../extensions/ecs-star/lib/'))
|
||||
.pipe(gulp.dest('../extensions/ecs-tween/lib/'))
|
||||
])
|
||||
});
|
||||
6201
source/package-lock.json
generated
6201
source/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"name": "@esengine/egret-framework",
|
||||
"version": "1.0.1",
|
||||
"description": "用于egret 包含众多高性能方法以供使用",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --recursive --reporter tap --growl",
|
||||
"eslint": "eslint src --ext .ts"
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.6.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"browserify": "^14.3.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-inject-string": "^1.1.2",
|
||||
"gulp-minify": "^3.1.0",
|
||||
"gulp-string-replace": "^1.1.2",
|
||||
"gulp-typescript": "^3.1.6",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"tsify": "^3.0.1",
|
||||
"typedoc": "^0.19.2",
|
||||
"typescript": "^2.2.2",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"watchify": "^3.9.0",
|
||||
"merge2": "^1.4.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com/359807859@qq.com"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 全局核心类
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 用于凝聚GraphicsDeviceReset事件
|
||||
*/
|
||||
public _graphicsDeviceChangeTimer: ITimer;
|
||||
/**
|
||||
* 全局访问系统
|
||||
*/
|
||||
public _globalManagers: GlobalManager[] = [];
|
||||
public _coroutineManager: CoroutineManager = new CoroutineManager();
|
||||
public _timerManager: TimerManager = new TimerManager();
|
||||
|
||||
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
|
||||
Core._instance = this;
|
||||
Core.emitter = new Emitter<CoreEvents>();
|
||||
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
|
||||
|
||||
Core.registerGlobalManager(this._coroutineManager);
|
||||
Core.registerGlobalManager(this._timerManager);
|
||||
Core.entitySystemsEnabled = enableEntitySystems;
|
||||
|
||||
this.debug = debug;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供对单例/游戏实例的访问
|
||||
* @constructor
|
||||
*/
|
||||
public static get Instance() {
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
public _frameCounterElapsedTime: number = 0;
|
||||
public _frameCounter: number = 0;
|
||||
public _totalMemory: number = 0;
|
||||
public _titleMemory: (totalMemory: number, frameCounter: number) => void;
|
||||
public _scene: Scene;
|
||||
|
||||
/**
|
||||
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
|
||||
*/
|
||||
public static get scene() {
|
||||
if (!this._instance)
|
||||
return null;
|
||||
return this._instance._scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
|
||||
* @param value
|
||||
*/
|
||||
public static set scene(value: Scene) {
|
||||
Insist.isNotNull(value, "场景不能为空");
|
||||
|
||||
if (this._instance._scene == null) {
|
||||
this._instance._scene = value;
|
||||
this._instance.onSceneChanged();
|
||||
this._instance._scene.begin();
|
||||
} else {
|
||||
this._instance._nextScene = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认实现创建核心
|
||||
*/
|
||||
public static create(debug: boolean = true): Core {
|
||||
if (this._instance == null) {
|
||||
this._instance = new es.Core(debug);
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个全局管理器对象,它的更新方法将调用场景前的每一帧。
|
||||
* @param manager
|
||||
*/
|
||||
public static registerGlobalManager(manager: es.GlobalManager) {
|
||||
this._instance._globalManagers.push(manager);
|
||||
manager.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除全局管理器对象
|
||||
* @param manager
|
||||
*/
|
||||
public static unregisterGlobalManager(manager: es.GlobalManager) {
|
||||
new es.List(this._instance._globalManagers).remove(manager);
|
||||
manager.enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为T的全局管理器
|
||||
* @param type
|
||||
*/
|
||||
public static getGlobalManager<T extends es.GlobalManager>(type: new (...args) => T): T {
|
||||
for (let i = 0; i < this._instance._globalManagers.length; i++) {
|
||||
if (this._instance._globalManagers[i] instanceof type)
|
||||
return this._instance._globalManagers[i] as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动一个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() {
|
||||
if (!this.debug) return;
|
||||
this._frameCounter++;
|
||||
this._frameCounterElapsedTime += Time.deltaTime;
|
||||
if (this._frameCounterElapsedTime >= 1) {
|
||||
let memoryInfo = window.performance["memory"];
|
||||
if (memoryInfo != null) {
|
||||
this._totalMemory = Number((memoryInfo.totalJSHeapSize / 1048576).toFixed(2));
|
||||
}
|
||||
if (this._titleMemory) this._titleMemory(this._totalMemory, this._frameCounter);
|
||||
this._frameCounter = 0;
|
||||
this._frameCounterElapsedTime -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一个场景结束后,下一个场景开始之前调用
|
||||
*/
|
||||
public onSceneChanged() {
|
||||
Time.sceneChanged();
|
||||
}
|
||||
|
||||
protected initialize() {
|
||||
|
||||
}
|
||||
|
||||
protected async update(currentTime: number = -1) {
|
||||
if (Core.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
Time.update(currentTime);
|
||||
if (this._scene != null) {
|
||||
for (let i = this._globalManagers.length - 1; i >= 0; i--) {
|
||||
if (this._globalManagers[i].enabled)
|
||||
this._globalManagers[i].update();
|
||||
}
|
||||
|
||||
this._scene.update();
|
||||
|
||||
if (this._nextScene != null) {
|
||||
this._scene.end();
|
||||
|
||||
this._scene = this._nextScene;
|
||||
this._nextScene = null;
|
||||
this.onSceneChanged();
|
||||
|
||||
this._scene.begin();
|
||||
}
|
||||
}
|
||||
|
||||
this.startDebugDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
module es {
|
||||
export enum LogType {
|
||||
error,
|
||||
warn,
|
||||
log,
|
||||
info,
|
||||
trace
|
||||
}
|
||||
|
||||
export class Debug {
|
||||
public static warnIf(condition: boolean, format: string, ...args: any[]) {
|
||||
if (condition)
|
||||
this.log(LogType.warn, format, args);
|
||||
}
|
||||
|
||||
public static warn(format: string, ...args: any[]) {
|
||||
this.log(LogType.warn, format, args);
|
||||
}
|
||||
|
||||
public static error(format: string, ...args: any[]) {
|
||||
this.log(LogType.error, format, 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,56 +0,0 @@
|
||||
module es {
|
||||
export class Insist {
|
||||
public static fail(message: string = null, ...args: any[]) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 执行顺序
|
||||
* - onAddedToEntity
|
||||
* - OnEnabled
|
||||
*
|
||||
* 删除执行顺序
|
||||
* - onRemovedFromEntity
|
||||
*/
|
||||
export abstract class Component {
|
||||
public static _idGenerator: number = 0;
|
||||
/**
|
||||
* 此组件的唯一标识
|
||||
*/
|
||||
public readonly id: number;
|
||||
/**
|
||||
* 此组件附加的实体
|
||||
*/
|
||||
public entity: Entity;
|
||||
|
||||
constructor() {
|
||||
this.id = Component._idGenerator++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速访问 this.entity.transform
|
||||
*/
|
||||
public get transform(): Transform {
|
||||
return this.entity.transform;
|
||||
}
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
|
||||
*/
|
||||
public get enabled() {
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
private _updateOrder = 0;
|
||||
|
||||
/** 更新此实体上组件的顺序 */
|
||||
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: transform.Component) {
|
||||
}
|
||||
|
||||
/**
|
||||
*当父实体或此组件启用时调用
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 +0,0 @@
|
||||
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';
|
||||
}
|
||||
@@ -1,244 +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 = new Vector2();
|
||||
|
||||
/**
|
||||
* 质量为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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用刚体的质量给刚体加上一个瞬间的力脉冲。力是一个加速度,单位是每秒像素每秒。将力乘以100000,使数值使用更合理
|
||||
* @param force
|
||||
*/
|
||||
public addImpulse(force: Vector2) {
|
||||
if (!this.isImmovable) {
|
||||
this.velocity = Vector2.add(this.velocity, Vector2.multiply(force, new Vector2(100000))
|
||||
.multiply(new Vector2(this._inverseMass * Time.deltaTime)));
|
||||
}
|
||||
}
|
||||
|
||||
public onAddedToEntity() {
|
||||
this._collider = this.entity.getComponent(es.Collider);
|
||||
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 = Vector2.add(this.velocity, Vector2.multiply(Physics.gravity, new Vector2(Time.deltaTime)));
|
||||
|
||||
this.entity.transform.position = Vector2.add(this.entity.transform.position, Vector2.multiply(this.velocity, new Vector2(Time.deltaTime)));
|
||||
let collisionResult = new CollisionResult();
|
||||
|
||||
// 捞取我们在新的位置上可能会碰撞到的任何东西
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this._collider, this._collider.collidesWithLayers.value);
|
||||
for (let neighbor of neighbors) {
|
||||
// 如果邻近的对撞机是同一个实体,则忽略它
|
||||
if (neighbor.entity.equals(this.entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this._collider.collidesWithNonMotion(neighbor, collisionResult)) {
|
||||
// 如果附近有一个ArcadeRigidbody,我们就会处理完整的碰撞响应。如果没有,我们会根据附近是不可移动的来计算事情
|
||||
let neighborRigidbody = neighbor.entity.getComponent<ArcadeRigidbody>(ArcadeRigidbody);
|
||||
if (neighborRigidbody != null) {
|
||||
this.processOverlap(neighborRigidbody, collisionResult.minimumTranslationVector);
|
||||
this.processCollision(neighborRigidbody, collisionResult.minimumTranslationVector);
|
||||
} else {
|
||||
// 没有ArcadeRigidbody,所以我们假设它是不动的,只移动我们自己的
|
||||
this.entity.transform.position = Vector2.subtract(this.entity.transform.position, collisionResult.minimumTranslationVector);
|
||||
let relativeVelocity = this.velocity.clone();
|
||||
this.calculateResponseVelocity(relativeVelocity, collisionResult.minimumTranslationVector, relativeVelocity);
|
||||
this.velocity = Vector2.add(this.velocity, relativeVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将两个重叠的刚体分开。也处理其中一个不可移动的情况
|
||||
* @param other
|
||||
* @param minimumTranslationVector
|
||||
*/
|
||||
public processOverlap(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
|
||||
if (this.isImmovable) {
|
||||
other.entity.transform.position = Vector2.add(other.entity.transform.position, minimumTranslationVector);
|
||||
} else if (other.isImmovable) {
|
||||
this.entity.transform.position = Vector2.subtract(this.entity.transform.position, minimumTranslationVector);
|
||||
} else {
|
||||
this.entity.transform.position = Vector2.subtract(this.entity.transform.position, Vector2.multiply(minimumTranslationVector, Vector2Ext.halfVector()));
|
||||
other.entity.transform.position = Vector2.add(other.entity.transform.position, Vector2.multiply(minimumTranslationVector, Vector2Ext.halfVector()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理两个非重叠的刚体的碰撞。新的速度将根据情况分配给每个刚体
|
||||
* @param other
|
||||
* @param minimumTranslationVector
|
||||
*/
|
||||
public processCollision(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
|
||||
// 我们计算两个相撞物体的响应。
|
||||
// 计算的基础是沿碰撞表面法线反射的物体的相对速度。
|
||||
// 然后,响应的一部分会根据质量加到每个物体上
|
||||
let relativeVelocity = Vector2.subtract(this.velocity, other.velocity);
|
||||
|
||||
this.calculateResponseVelocity(relativeVelocity, minimumTranslationVector, relativeVelocity);
|
||||
|
||||
// 现在,我们使用质量来线性缩放两个刚体上的响应
|
||||
let totalinverseMass = this._inverseMass + other._inverseMass;
|
||||
let ourResponseFraction = this._inverseMass / totalinverseMass;
|
||||
let otherResponseFraction = other._inverseMass / totalinverseMass;
|
||||
|
||||
this.velocity = Vector2.add(this.velocity, new Vector2(relativeVelocity.x * ourResponseFraction, relativeVelocity.y * ourResponseFraction));
|
||||
other.velocity = Vector2.subtract(other.velocity, new Vector2(relativeVelocity.x * otherResponseFraction, relativeVelocity.y * otherResponseFraction));
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定两个物体和MTV之间的相对速度,本方法修改相对速度,使其成为碰撞响应
|
||||
* @param relativeVelocity
|
||||
* @param minimumTranslationVector
|
||||
* @param responseVelocity
|
||||
*/
|
||||
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2, responseVelocity: Vector2 = new Vector2()) {
|
||||
// 首先,我们得到反方向的归一化MTV:表面法线
|
||||
let inverseMTV = Vector2.multiply(minimumTranslationVector, new Vector2(-1));
|
||||
let normal = Vector2.normalize(inverseMTV);
|
||||
|
||||
// 速度是沿碰撞法线和碰撞平面分解的。
|
||||
// 弹性将影响沿法线的响应(法线速度分量),摩擦力将影响速度的切向分量(切向速度分量)
|
||||
let n = Vector2.dot(relativeVelocity, normal);
|
||||
|
||||
let normalVelocityComponent = new Vector2(normal.x * n, normal.y * n);
|
||||
let tangentialVelocityComponent = Vector2.subtract(relativeVelocity, normalVelocityComponent);
|
||||
|
||||
if (n > 0)
|
||||
normalVelocityComponent = Vector2.zero;
|
||||
|
||||
// 如果切向分量的平方幅度小于glue,那么我们就把摩擦力提升到最大
|
||||
let coefficientOfFriction = this._friction;
|
||||
if (tangentialVelocityComponent.lengthSquared() < this._glue)
|
||||
coefficientOfFriction = 1.01;
|
||||
|
||||
// 弹性影响速度的法向分量,摩擦力影响速度的切向分量
|
||||
let t = Vector2.multiply(new Vector2((1 + this._elasticity)), normalVelocityComponent)
|
||||
.multiply(new Vector2(-1))
|
||||
.subtract(Vector2.multiply(new Vector2(coefficientOfFriction), tangentialVelocityComponent));
|
||||
responseVelocity.x = t.x;
|
||||
relativeVelocity.y = t.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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, y: number, width: number, height: number) {
|
||||
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) {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
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 {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, box.height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的高度
|
||||
* @param height
|
||||
*/
|
||||
public setHeight(height: number) {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
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,47 +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) {
|
||||
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 {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
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,270 +0,0 @@
|
||||
module es {
|
||||
export class Collider extends Component {
|
||||
/**
|
||||
* 对撞机的基本形状
|
||||
*/
|
||||
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();
|
||||
|
||||
protected _colliderRequiresAutoSizing: boolean;
|
||||
|
||||
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 = offset;
|
||||
this._localOffsetLength = this._localOffset.length();
|
||||
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: transform.Component) {
|
||||
switch (comp) {
|
||||
case transform.Component.position:
|
||||
this._isPositionDirty = true;
|
||||
break;
|
||||
case transform.Component.scale:
|
||||
this._isPositionDirty = true;
|
||||
break;
|
||||
case transform.Component.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: CollisionResult = new CollisionResult()): boolean {
|
||||
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
|
||||
let oldPosition = this.entity.position.clone();
|
||||
this.entity.position = Vector2.add(this.entity.position, motion);
|
||||
|
||||
let didCollide = this.shape.collidesWithShape(collider.shape, result);
|
||||
if (didCollide)
|
||||
result.collider = collider;
|
||||
|
||||
// 将图形位置返回到检查前的位置
|
||||
this.entity.position = oldPosition;
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查这个对撞机是否与对撞机发生碰撞。如果碰撞,则返回true,结果将被填充
|
||||
* @param collider
|
||||
* @param result
|
||||
*/
|
||||
public collidesWithNonMotion(collider: Collider, result: CollisionResult = new CollisionResult()): boolean {
|
||||
if (this.shape.collidesWithShape(collider.shape, result)) {
|
||||
result.collider = collider;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查此碰撞器是否已应用运动(增量运动矢量)与任何碰撞器发生碰撞。
|
||||
* 如果是这样,则将返回true,并且将使用碰撞数据填充结果。 运动将设置为碰撞器在碰撞之前可以行进的最大距离。
|
||||
* @param motion
|
||||
* @param result
|
||||
*/
|
||||
public collidesWithAny(motion: Vector2, result: 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.clone();
|
||||
this.shape.position = Vector2.add(this.shape.position, motion);
|
||||
|
||||
let didCollide = false;
|
||||
for (let neighbor of neighbors) {
|
||||
if (neighbor.isTrigger)
|
||||
continue;
|
||||
|
||||
if (this.collidesWithNonMotion(neighbor, result)) {
|
||||
motion = Vector2.subtract(motion, result.minimumTranslationVector);
|
||||
this.shape.position = Vector2.subtract(this.shape.position, result.minimumTranslationVector);
|
||||
didCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 将形状位置返回到检查之前的位置
|
||||
this.shape.position = oldPosition;
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查此碰撞器是否与场景中的其他碰撞器碰撞。它相交的第一个碰撞器将在碰撞结果中返回碰撞数据。
|
||||
* @param result
|
||||
*/
|
||||
public collidesWithAnyNonMotion(result: CollisionResult = 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,27 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 多边形应该以顺时针方式定义
|
||||
*/
|
||||
export class PolygonCollider extends Collider {
|
||||
/**
|
||||
* 如果这些点没有居中,它们将以localOffset的差异为居中。
|
||||
* @param points
|
||||
*/
|
||||
constructor(points: Vector2[]) {
|
||||
super();
|
||||
|
||||
// 第一点和最后一点决不能相同。我们想要一个开放的多边形
|
||||
let isPolygonClosed = points[0] == points[points.length - 1];
|
||||
|
||||
let linqPoints = new es.List(points);
|
||||
// 最后一个移除
|
||||
if (isPolygonClosed)
|
||||
linqPoints.remove(linqPoints.last());
|
||||
|
||||
let center = Polygon.findPolygonCenter(points);
|
||||
this.setLocalOffset(center);
|
||||
Polygon.recenterPolygonVerts(points);
|
||||
this.shape = new Polygon(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +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[]){
|
||||
for (let component of entity.components._components) {
|
||||
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,89 +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: CollisionResult): boolean {
|
||||
if (this.entity.getComponent(Collider) == null || this._triggerHelper == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移动所有的非触发碰撞器并获得最近的碰撞
|
||||
let colliders: Collider[] = this.entity.getComponents(Collider);
|
||||
for (let i = 0; i < colliders.length; i++) {
|
||||
let collider = colliders[i];
|
||||
|
||||
// 不检测触发器 在我们移动后会重新访问它
|
||||
if (collider.isTrigger)
|
||||
continue;
|
||||
|
||||
// 获取我们在新位置可能发生碰撞的任何东西
|
||||
let bounds = collider.bounds.clone();
|
||||
bounds.x += motion.x;
|
||||
bounds.y += motion.y;
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, bounds, collider.collidesWithLayers.value);
|
||||
|
||||
neighbors.forEach(value => {
|
||||
let neighbor = value;
|
||||
// 不检测触发器
|
||||
if (neighbor.isTrigger)
|
||||
return;
|
||||
|
||||
let _internalcollisionResult: CollisionResult = new CollisionResult();
|
||||
if (collider.collidesWith(neighbor, motion, _internalcollisionResult)) {
|
||||
// 如果碰撞 则退回之前的移动量
|
||||
motion.subtract(_internalcollisionResult.minimumTranslationVector);
|
||||
|
||||
// 如果我们碰到多个对象,为了简单起见,只取第一个。
|
||||
if (_internalcollisionResult.collider != null) {
|
||||
collisionResult = _internalcollisionResult;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ListPool.free(colliders);
|
||||
|
||||
return collisionResult.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: CollisionResult) {
|
||||
this.calculateMovement(motion, collisionResult);
|
||||
this.applyMovement(motion);
|
||||
return collisionResult.collider != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 移动时考虑到碰撞,只用于向任何ITriggerListeners报告。
|
||||
* 物体总是会全量移动,所以如果需要的话,由调用者在撞击时销毁它。
|
||||
*/
|
||||
export class ProjectileMover extends Component {
|
||||
private _tempTriggerList: ITriggerListener[] = [];
|
||||
private _collider: Collider;
|
||||
|
||||
public onAddedToEntity() {
|
||||
this._collider = this.entity.getComponent(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);
|
||||
for (let neighbor of neighbors){
|
||||
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 +0,0 @@
|
||||
module es {
|
||||
export class SceneComponent implements IComparer<SceneComponent> {
|
||||
/**
|
||||
* 这个场景组件被附加到的场景
|
||||
*/
|
||||
public scene: Scene;
|
||||
|
||||
/**
|
||||
* 如果启用了SceneComponent,则为true。状态的改变会导致调用onEnabled/onDisable。
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果启用了SceneComponent,则为true。状态的改变会导致调用onEnabled/onDisable。
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新此场景中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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
module es {
|
||||
export enum CoreEvents {
|
||||
/**
|
||||
* 当场景发生变化时触发
|
||||
*/
|
||||
sceneChanged,
|
||||
/**
|
||||
* 每帧更新事件
|
||||
*/
|
||||
frameUpdated,
|
||||
}
|
||||
}
|
||||
@@ -1,518 +0,0 @@
|
||||
module es {
|
||||
export class EntityComparer implements IComparer<Entity> {
|
||||
public compare(self: Entity, other: Entity): number {
|
||||
let compare = self.updateOrder - other.updateOrder;
|
||||
if (compare == 0)
|
||||
compare = self.id - other.id;
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
|
||||
export class Entity implements IEqualityComparable {
|
||||
public static _idGenerator: number = 0;
|
||||
public static entityComparer: IComparer<Entity> = new EntityComparer();
|
||||
/**
|
||||
* 当前实体所属的场景
|
||||
*/
|
||||
public scene: Scene;
|
||||
/**
|
||||
* 实体名称。用于在场景范围内搜索实体
|
||||
*/
|
||||
public name: string;
|
||||
/**
|
||||
* 此实体的唯一标识
|
||||
*/
|
||||
public readonly id: number;
|
||||
/**
|
||||
* 封装实体的位置/旋转/缩放,并允许设置一个高层结构
|
||||
*/
|
||||
public readonly transform: Transform;
|
||||
/**
|
||||
* 当前附加到此实体的所有组件的列表
|
||||
*/
|
||||
public readonly components: ComponentList;
|
||||
/**
|
||||
* 指定应该调用这个entity update方法的频率。1表示每一帧,2表示每一帧,以此类推
|
||||
*/
|
||||
public updateInterval: number = 1;
|
||||
/**
|
||||
* 返回一个BitSet实例,包含实体拥有的组件的位
|
||||
*/
|
||||
public componentBits: BitSet;
|
||||
private systemBits_: BitSet;
|
||||
|
||||
constructor(name: string) {
|
||||
this.components = new ComponentList(this);
|
||||
this.transform = new Transform(this);
|
||||
this.name = name;
|
||||
this.id = Entity._idGenerator++;
|
||||
|
||||
this.systemBits_ = new BitSet();
|
||||
this.componentBits = new BitSet();
|
||||
}
|
||||
|
||||
public _isDestroyed: boolean;
|
||||
|
||||
/**
|
||||
* 如果调用了destroy,那么在下一次处理实体之前这将一直为true
|
||||
*/
|
||||
public get isDestroyed() {
|
||||
return this._isDestroyed;
|
||||
}
|
||||
|
||||
private _tag: number = 0;
|
||||
|
||||
/**
|
||||
* 你可以随意使用。稍后可以使用它来查询场景中具有特定标记的所有实体
|
||||
*/
|
||||
public get tag(): number {
|
||||
return this._tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 你可以随意使用。稍后可以使用它来查询场景中具有特定标记的所有实体
|
||||
* @param value
|
||||
*/
|
||||
public set tag(value: number) {
|
||||
this.setTag(value);
|
||||
}
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 启用/禁用实体。当禁用碰撞器从物理系统和组件中移除时,方法将不会被调用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用实体。当禁用碰撞器从物理系统和组件中移除时,方法将不会被调用
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
private _updateOrder: number = 0;
|
||||
|
||||
/**
|
||||
* 更新此实体的顺序。updateOrder还用于对scene.entities上的标签列表进行排序
|
||||
*/
|
||||
public get updateOrder() {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新此实体的顺序。updateOrder还用于对scene.entities上的标签列表进行排序
|
||||
* @param value
|
||||
*/
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
public getSystemBits(): BitSet {
|
||||
return this.systemBits_;
|
||||
}
|
||||
|
||||
public get parent(): Transform {
|
||||
return this.transform.parent;
|
||||
}
|
||||
|
||||
public set parent(value: Transform) {
|
||||
this.transform.setParent(value);
|
||||
}
|
||||
|
||||
public get childCount() {
|
||||
return this.transform.childCount;
|
||||
}
|
||||
|
||||
public get position(): Vector2 {
|
||||
return this.transform.position;
|
||||
}
|
||||
|
||||
public set position(value: Vector2) {
|
||||
this.transform.setPosition(value.x, value.y);
|
||||
}
|
||||
|
||||
public get localPosition(): Vector2 {
|
||||
return this.transform.localPosition;
|
||||
}
|
||||
|
||||
public set localPosition(value: Vector2) {
|
||||
this.transform.setLocalPosition(value);
|
||||
}
|
||||
|
||||
public get rotation(): number {
|
||||
return this.transform.rotation;
|
||||
}
|
||||
|
||||
public set rotation(value: number) {
|
||||
this.transform.setRotation(value);
|
||||
}
|
||||
|
||||
public get rotationDegrees(): number {
|
||||
return this.transform.rotationDegrees;
|
||||
}
|
||||
|
||||
public set rotationDegrees(value: number) {
|
||||
this.transform.setRotationDegrees(value);
|
||||
}
|
||||
|
||||
public get localRotation(): number {
|
||||
return this.transform.localRotation;
|
||||
}
|
||||
|
||||
public set localRotation(value: number) {
|
||||
this.transform.setLocalRotation(value);
|
||||
}
|
||||
|
||||
public get localRotationDegrees(): number {
|
||||
return this.transform.localRotationDegrees;
|
||||
}
|
||||
|
||||
public set localRotationDegrees(value: number) {
|
||||
this.transform.setLocalRotationDegrees(value);
|
||||
}
|
||||
|
||||
public get scale(): Vector2 {
|
||||
return this.transform.scale;
|
||||
}
|
||||
|
||||
public set scale(value: Vector2) {
|
||||
this.transform.setScale(value);
|
||||
}
|
||||
|
||||
public get localScale(): Vector2 {
|
||||
return this.transform.localScale;
|
||||
}
|
||||
|
||||
public set localScale(value: Vector2) {
|
||||
this.transform.setLocalScale(value);
|
||||
}
|
||||
|
||||
public get worldInverseTransform(): Matrix2D {
|
||||
return this.transform.worldInverseTransform;
|
||||
}
|
||||
|
||||
public get localToWorldTransform(): Matrix2D {
|
||||
return this.transform.localToWorldTransform;
|
||||
}
|
||||
|
||||
public get worldToLocalTransform(): Matrix2D {
|
||||
return this.transform.worldToLocalTransform;
|
||||
}
|
||||
|
||||
public onTransformChanged(comp: transform.Component) {
|
||||
// 通知我们的子项改变了位置
|
||||
this.components.onEntityTransformChanged(comp);
|
||||
}
|
||||
|
||||
public setParent(parent: Entity);
|
||||
public setParent(parent: Transform);
|
||||
public setParent(parent: Transform | Entity) {
|
||||
if (parent instanceof Transform) {
|
||||
this.transform.setParent(parent);
|
||||
} else if (parent instanceof Entity) {
|
||||
this.transform.setParent(parent.transform);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setPosition(x: number, y: number) {
|
||||
this.transform.setPosition(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLocalPosition(localPosition: Vector2) {
|
||||
this.transform.setLocalPosition(localPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setRotation(radians: number) {
|
||||
this.transform.setRotation(radians);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setRotationDegrees(degrees: number) {
|
||||
this.transform.setRotationDegrees(degrees);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLocalRotation(radians: number) {
|
||||
this.transform.setLocalRotation(radians);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLocalRotationDegrees(degrees: number) {
|
||||
this.transform.setLocalRotationDegrees(degrees);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setScale(scale: number);
|
||||
public setScale(scale: Vector2);
|
||||
public setScale(scale: Vector2 | number) {
|
||||
if (scale instanceof Vector2) {
|
||||
this.transform.setScale(scale);
|
||||
} else {
|
||||
this.transform.setScale(new Vector2(scale));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLocalScale(scale: number);
|
||||
public setLocalScale(scale: Vector2);
|
||||
public setLocalScale(scale: Vector2 | number) {
|
||||
if (scale instanceof Vector2) {
|
||||
this.transform.setLocalScale(scale);
|
||||
} else {
|
||||
this.transform.setLocalScale(new Vector2(scale));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体的标记
|
||||
* @param tag
|
||||
*/
|
||||
public setTag(tag: number): Entity {
|
||||
if (this._tag != tag) {
|
||||
// 我们只有在已经有场景的情况下才会调用entityTagList。如果我们还没有场景,我们会被添加到entityTagList
|
||||
if (this.scene)
|
||||
this.scene.entities.removeFromTagList(this);
|
||||
this._tag = tag;
|
||||
if (this.scene)
|
||||
this.scene.entities.addToTagList(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体的启用状态。当禁用碰撞器从物理系统和组件中移除时,方法将不会被调用
|
||||
* @param isEnabled
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
|
||||
if (this._enabled)
|
||||
this.components.onEntityEnabled();
|
||||
else
|
||||
this.components.onEntityDisabled();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此实体的更新顺序。updateOrder还用于对scene.entities上的标签列表进行排序
|
||||
* @param updateOrder
|
||||
*/
|
||||
public setUpdateOrder(updateOrder: number) {
|
||||
if (this._updateOrder != updateOrder) {
|
||||
this._updateOrder = updateOrder;
|
||||
if (this.scene) {
|
||||
this.scene.entities.markEntityListUnsorted();
|
||||
this.scene.entities.markTagUnsorted(this.tag);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除实体并销毁所有子元素
|
||||
*/
|
||||
public destroy() {
|
||||
this._isDestroyed = true;
|
||||
this.scene.entities.remove(this);
|
||||
this.transform.parent = null;
|
||||
|
||||
// 销毁所有子项
|
||||
for (let i = this.transform.childCount - 1; i >= 0; i--) {
|
||||
let child = this.transform.getChild(i);
|
||||
child.entity.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体从场景中分离。下面的生命周期方法将被调用在组件上:OnRemovedFromEntity
|
||||
*/
|
||||
public detachFromScene() {
|
||||
this.scene.entities.remove(this);
|
||||
this.components.deregisterAllComponents();
|
||||
|
||||
for (let i = 0; i < this.transform.childCount; i++)
|
||||
this.transform.getChild(i).entity.detachFromScene();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个先前分离的实体附加到一个新的场景
|
||||
* @param newScene
|
||||
*/
|
||||
public attachToScene(newScene: Scene) {
|
||||
this.scene = newScene;
|
||||
newScene.entities.add(this);
|
||||
this.components.registerAllComponents();
|
||||
|
||||
for (let i = 0; i < this.transform.childCount; i++) {
|
||||
this.transform.getChild(i).entity.attachToScene(newScene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在提交了所有挂起的实体更改后,将此实体添加到场景时调用
|
||||
*/
|
||||
public onAddedToScene() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当此实体从场景中删除时调用
|
||||
*/
|
||||
public onRemovedFromScene() {
|
||||
// 如果已经被销毁了,移走我们的组件。如果我们只是分离,我们需要保持我们的组件在实体上。
|
||||
if (this._isDestroyed)
|
||||
this.components.removeAllComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧进行调用进行更新组件
|
||||
*/
|
||||
public update() {
|
||||
this.components.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建组件的新实例。返回实例组件
|
||||
* @param componentType
|
||||
*/
|
||||
public createComponent<T extends Component>(componentType: new (...args) => T): T {
|
||||
let component = new componentType();
|
||||
this.addComponent(component);
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将组件添加到组件列表中。返回组件。
|
||||
* @param component
|
||||
*/
|
||||
public addComponent<T extends Component>(component: T): T {
|
||||
component.entity = this;
|
||||
this.components.add(component);
|
||||
component.initialize();
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型T的第一个组件并返回它。如果没有找到组件,则返回null。
|
||||
* @param type
|
||||
*/
|
||||
public getComponent<T extends Component>(type: new (...args) => T): T {
|
||||
return this.components.getComponent(type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型T的第一个并已加入场景的组件并返回它。如果没有找到组件,则返回null。
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
public getComponentInScene<T extends Component>(type: new (...args) => T): T {
|
||||
return this.components.getComponent(type, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取T类型的组件。如果未找到任何组件,则返回false
|
||||
* @param type
|
||||
* @param outComponent
|
||||
* @returns
|
||||
*/
|
||||
public tryGetComponent<T extends Component>(type: new (...args) => T, outComponent: Ref<T>): boolean {
|
||||
outComponent.value = this.components.getComponent<T>(type, false);
|
||||
return outComponent.value != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否具有该组件
|
||||
* @param type
|
||||
*/
|
||||
public hasComponent<T extends Component>(type: new (...args) => T) {
|
||||
return this.components.getComponent<T>(type, false) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型T的第一个组件并返回它。如果没有找到组件,将创建组件。
|
||||
* @param type
|
||||
*/
|
||||
public getOrCreateComponent<T extends Component>(type: new (...args) => T) {
|
||||
let comp = this.components.getComponent<T>(type, true);
|
||||
if (!comp) {
|
||||
comp = this.addComponent<T>(new type());
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取typeName类型的所有组件,但不使用列表分配
|
||||
* @param typeName
|
||||
* @param componentList
|
||||
*/
|
||||
public getComponents(typeName: any, componentList?) {
|
||||
return this.components.getComponents(typeName, componentList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组件列表中删除组件
|
||||
* @param component
|
||||
*/
|
||||
public removeComponent(component: Component) {
|
||||
this.components.remove(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组件列表中删除类型为T的第一个组件
|
||||
* @param type
|
||||
*/
|
||||
public removeComponentForType<T extends Component>(type) {
|
||||
let comp = this.getComponent<T>(type);
|
||||
if (comp) {
|
||||
this.removeComponent(comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体中删除所有组件
|
||||
*/
|
||||
public removeAllComponents() {
|
||||
for (let i = 0; i < this.components.count; i++) {
|
||||
this.removeComponent(this.components.buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public compareTo(other: Entity): number {
|
||||
let compare = this._updateOrder - other._updateOrder;
|
||||
if (compare == 0)
|
||||
compare = this.id - other.id;
|
||||
return compare;
|
||||
}
|
||||
|
||||
public equals(other: Entity): boolean {
|
||||
return this.compareTo(other) == 0;
|
||||
}
|
||||
|
||||
public getHashCode(): number {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `[Entity: name: ${this.name}, tag: ${this.tag}, enabled: ${this.enabled}, depth: ${this.updateOrder}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
///<reference path="../Math/Vector2.ts" />
|
||||
module es {
|
||||
/** 场景 */
|
||||
export class Scene {
|
||||
/**
|
||||
* 这个场景中的实体列表
|
||||
*/
|
||||
public readonly entities: EntityList;
|
||||
|
||||
/**
|
||||
* 管理所有实体处理器
|
||||
*/
|
||||
public readonly entityProcessors: EntityProcessorList;
|
||||
|
||||
public readonly _sceneComponents: SceneComponent[] = [];
|
||||
private _didSceneBegin: boolean;
|
||||
|
||||
constructor() {
|
||||
this.entities = new EntityList(this);
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景子类中重写这个,然后在这里进行加载。
|
||||
* 在场景设置好之后,但在调用begin之前,从contructor中调用这个函数
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当Core将这个场景设置为活动场景时,这个将被调用
|
||||
*/
|
||||
public onStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景子类中重写这个,并在这里做任何必要的卸载。
|
||||
* 当Core把这个场景从活动槽中移除时,这个被调用。
|
||||
*/
|
||||
public unload() {
|
||||
}
|
||||
|
||||
public begin() {
|
||||
Physics.reset();
|
||||
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.begin();
|
||||
|
||||
this._didSceneBegin = true;
|
||||
this.onStart();
|
||||
|
||||
}
|
||||
|
||||
public end() {
|
||||
this._didSceneBegin = false;
|
||||
|
||||
this.entities.removeAllEntities();
|
||||
|
||||
for (let i = 0; i < this._sceneComponents.length; i++) {
|
||||
this._sceneComponents[i].onRemovedFromScene();
|
||||
}
|
||||
this._sceneComponents.length = 0;
|
||||
|
||||
Physics.clear();
|
||||
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.end();
|
||||
|
||||
this.unload();
|
||||
}
|
||||
|
||||
public update() {
|
||||
// 更新我们的列表,以防它们有任何变化
|
||||
this.entities.updateLists();
|
||||
|
||||
for (let i = this._sceneComponents.length - 1; i >= 0; i--) {
|
||||
if (this._sceneComponents[i].enabled)
|
||||
this._sceneComponents[i].update();
|
||||
}
|
||||
|
||||
// 更新我们的实体解析器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.update();
|
||||
|
||||
// 更新我们的实体组
|
||||
this.entities.update();
|
||||
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.lateUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向组件列表添加并返回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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为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;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为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());
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从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 name
|
||||
*/
|
||||
public createEntity(name: string) {
|
||||
let entity = new Entity(name);
|
||||
return this.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;
|
||||
|
||||
for (let i = 0; i < entity.transform.childCount; i++)
|
||||
this.addEntity(entity.transform.getChild(i).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.findEntitesOfComponent(...types);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem) {
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除EntitySystem处理器
|
||||
* @param processor
|
||||
*/
|
||||
public removeEntityProcessor(processor: EntitySystem) {
|
||||
this.entityProcessors.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取EntitySystem处理器
|
||||
*/
|
||||
public getEntityProcessor<T extends EntitySystem>(): T {
|
||||
return this.entityProcessors.getProcessor<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
///<reference path="./EntitySystem.ts"/>
|
||||
module es {
|
||||
/**
|
||||
* 追踪每个实体的冷却时间,当实体的计时器耗尽时进行处理
|
||||
*
|
||||
* 一个示例系统将是ExpirationSystem,该系统将在特定生存期后删除实体。
|
||||
* 你不必运行会为每个实体递减timeLeft值的系统
|
||||
* 而只需使用此系统在寿命最短的实体时在将来执行
|
||||
* 然后重置系统在未来的某一个最短命实体的时间运行
|
||||
*
|
||||
* 另一个例子是一个动画系统
|
||||
* 你知道什么时候你必须对某个实体进行动画制作,比如300毫秒内。
|
||||
* 所以你可以设置系统以300毫秒为单位运行来执行动画
|
||||
*
|
||||
* 这将在某些情况下节省CPU周期
|
||||
*/
|
||||
export abstract class DelayedIteratingSystem extends EntitySystem {
|
||||
/**
|
||||
* 一个实体应被处理的时间
|
||||
*/
|
||||
private delay: number = 0;
|
||||
/**
|
||||
* 如果系统正在运行,并倒计时延迟
|
||||
*/
|
||||
private running: boolean = false;
|
||||
/**
|
||||
* 倒计时
|
||||
*/
|
||||
private acc: number = 0;
|
||||
|
||||
constructor(matcher: Matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
let processed = entities.length;
|
||||
if (processed == 0) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
this.delay = Number.MAX_VALUE;
|
||||
for (let i = 0; processed > i; i++) {
|
||||
let e = entities[i];
|
||||
this.processDelta(e, this.acc);
|
||||
let remaining = this.getRemainingDelay(e);
|
||||
if (remaining <= 0) {
|
||||
this.processExpired(e);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理本系统感兴趣的实体
|
||||
* 从实体定义的延迟中抽象出accumulativeDelta
|
||||
* @param entity
|
||||
* @param accumulatedDelta 本系统最后一次执行后的delta时间
|
||||
*/
|
||||
protected abstract processDelta(entity: Entity, accumulatedDelta: number);
|
||||
|
||||
protected abstract processExpired(entity: Entity);
|
||||
|
||||
/**
|
||||
* 返回该实体处理前的延迟时间
|
||||
* @param entity
|
||||
*/
|
||||
protected abstract getRemainingDelay(entity: Entity): number;
|
||||
|
||||
/**
|
||||
* 获取系统被命令处理实体后的初始延迟
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
///<reference path="./EntitySystem.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 基本实体处理系统。将其用作处理具有特定组件的许多实体的基础
|
||||
*
|
||||
* 按实体引用遍历实体订阅成员实体的系统
|
||||
* 当你需要处理与Matcher相匹配的实体,并且你更喜欢使用Entity的时候,可以使用这个功能。
|
||||
*/
|
||||
export abstract class EntityProcessingSystem extends EntitySystem {
|
||||
constructor(matcher: Matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理特定的实体
|
||||
* @param entity
|
||||
*/
|
||||
public abstract processEntity(entity: Entity);
|
||||
|
||||
public lateProcessEntity(entity: Entity) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历这个系统的所有实体并逐个处理它们
|
||||
* @param entities
|
||||
*/
|
||||
protected process(entities: Entity[]) {
|
||||
entities.forEach(entity => this.processEntity(entity));
|
||||
}
|
||||
|
||||
protected lateProcess(entities: Entity[]) {
|
||||
entities.forEach(entity => this.lateProcessEntity(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
///<reference path="../../Utils/Collections/HashMap.ts"/>
|
||||
module es {
|
||||
export class SystemIndexManager {
|
||||
public static INDEX = 0;
|
||||
private static indices: Map<Function, number> = new Map<Function, number>();
|
||||
|
||||
public static getIndexFor(es: Class): number {
|
||||
let index: number = SystemIndexManager.indices.get(es);
|
||||
if (!index) {
|
||||
index = SystemIndexManager.INDEX++;
|
||||
SystemIndexManager.indices.set(es, index);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 追踪实体的子集,但不实现任何排序或迭代。
|
||||
*/
|
||||
export abstract class EntitySystem {
|
||||
private _entities: Entity[] = [];
|
||||
private systemIndex_: number;
|
||||
|
||||
constructor(matcher?: Matcher) {
|
||||
this._matcher = matcher ? matcher : Matcher.empty();
|
||||
this.systemIndex_ = SystemIndexManager.getIndexFor(this.constructor);
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private _scene: Scene;
|
||||
|
||||
/**
|
||||
* 这个系统所属的场景
|
||||
*/
|
||||
public get scene() {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
public set scene(value: Scene) {
|
||||
this._scene = value;
|
||||
this._entities = [];
|
||||
}
|
||||
|
||||
private _matcher: Matcher;
|
||||
|
||||
public get matcher() {
|
||||
return this._matcher;
|
||||
}
|
||||
|
||||
private _startTime = 0;
|
||||
private _endTime = 0;
|
||||
private _useTime = 0;
|
||||
/** 获取系统在当前帧所消耗的时间 仅在debug模式下生效 */
|
||||
public get useTime() {
|
||||
return this._useTime;
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
|
||||
}
|
||||
|
||||
public onChanged(entity: Entity) {
|
||||
let contains = entity.getSystemBits().get(this.systemIndex_);
|
||||
let interest = this._matcher.isInterestedEntity(entity);
|
||||
|
||||
if (interest && !contains)
|
||||
this.add(entity);
|
||||
else if (!interest && contains)
|
||||
this.remove(entity);
|
||||
}
|
||||
|
||||
public add(entity: Entity) {
|
||||
this._entities.push(entity);
|
||||
entity.getSystemBits().set(this.systemIndex_);
|
||||
this.onAdded(entity);
|
||||
}
|
||||
|
||||
public onAdded(entity: Entity) { }
|
||||
|
||||
public remove(entity: Entity) {
|
||||
new es.List(this._entities).remove(entity);
|
||||
entity.getSystemBits().clear(this.systemIndex_);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
///<reference path="./IntervalSystem.ts"/>
|
||||
module es {
|
||||
/**
|
||||
* 每x个ticks处理一个实体的子集
|
||||
*
|
||||
* 典型的用法是每隔一定的时间间隔重新生成弹药或生命值
|
||||
* 而无需在每个游戏循环中都进行
|
||||
* 而是每100毫秒一次或每秒
|
||||
*/
|
||||
export abstract class IntervalIteratingSystem extends IntervalSystem {
|
||||
constructor(matcher: Matcher, interval: number) {
|
||||
super(matcher, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理本系统感兴趣的实体
|
||||
* @param entity
|
||||
*/
|
||||
public abstract processEntity(entity: Entity);
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
entities.forEach(entity => this.processEntity(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 实体系统以一定的时间间隔进行处理
|
||||
*/
|
||||
export abstract class IntervalSystem extends EntitySystem {
|
||||
/**
|
||||
* 累积增量以跟踪间隔
|
||||
*/
|
||||
protected acc: number = 0;
|
||||
/**
|
||||
* 更新之间需要等待多长时间
|
||||
*/
|
||||
private readonly interval: number = 0;
|
||||
private intervalDelta: number = 0;
|
||||
|
||||
constructor(matcher: Matcher, interval: number) {
|
||||
super(matcher);
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
protected checkProcessing() {
|
||||
this.acc += Time.deltaTime;
|
||||
if (this.acc >= this.interval) {
|
||||
this.acc -= this.interval;
|
||||
this.intervalDelta = (this.acc - this.intervalDelta);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本系统上次处理后的实际delta值
|
||||
*/
|
||||
protected getIntervalDelta() {
|
||||
return this.interval + this.intervalDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* JobSystem使用实体的子集调用Execute(entities),并在指定数量的线程上分配工作负载。
|
||||
*/
|
||||
export abstract class JobSystem extends EntitySystem {
|
||||
public readonly _threads: number;
|
||||
public readonly _jobs: Job[];
|
||||
public readonly _executeStr: string;
|
||||
|
||||
constructor(matcher: Matcher, threads: number) {
|
||||
super(matcher);
|
||||
|
||||
this._threads = threads;
|
||||
this._jobs = new Array(threads);
|
||||
for (let i = 0; i < this._jobs.length; i++) {
|
||||
this._jobs[i] = new Job();
|
||||
}
|
||||
this._executeStr = JSON.stringify(this.execute, function (key, val) {
|
||||
if (typeof val === 'function') {
|
||||
return val + '';
|
||||
}
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
let remainder = entities.length & this._threads;
|
||||
let slice = entities.length / this._threads + (remainder == 0 ? 0 : 1);
|
||||
for (let t = 0; t < this._threads; t++) {
|
||||
let from = t * slice;
|
||||
let to = from + slice;
|
||||
if (to > entities.length) {
|
||||
to = entities.length;
|
||||
}
|
||||
|
||||
let job = this._jobs[t];
|
||||
job.set(entities, from, to, this._executeStr, this);
|
||||
if (from != to) {
|
||||
const worker = WorkerUtils.makeWorker(this.queueOnThread);
|
||||
const workerDo = WorkerUtils.workerMessage(worker);
|
||||
workerDo(job).then((message) => {
|
||||
let job = message as Job;
|
||||
this.resetJob(job);
|
||||
worker.terminate();
|
||||
}).catch((err) => {
|
||||
job.err = err;
|
||||
worker.terminate();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private queueOnThread() {
|
||||
onmessage = ({ data: { jobId, message } }) => {
|
||||
let job = message[0] as Job;
|
||||
let executeFunc: Function = JSON.parse(job.execute, function (k, v) {
|
||||
if (v.indexOf && v.indexOf('function') > -1) {
|
||||
return eval("(function(){return " + v + " })()")
|
||||
}
|
||||
|
||||
return v;
|
||||
});
|
||||
for (let i = job.from; i < job.to; i++) {
|
||||
executeFunc.call(job.context, job.entities[i]);
|
||||
}
|
||||
|
||||
postMessage({ jobId, result: message }, null);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 当操作完成时,改变的值需要用户进行手动传递
|
||||
* 由于worker数据无法共享,所以这块需要特殊处理
|
||||
* @example this.test = job[0].context.test;
|
||||
* @param job
|
||||
*/
|
||||
protected abstract resetJob(job: Job);
|
||||
/**
|
||||
* 对指定实体进行多线程操作
|
||||
* @param entity
|
||||
*/
|
||||
protected abstract execute(entity: Entity);
|
||||
}
|
||||
|
||||
class Job {
|
||||
public entities: Entity[];
|
||||
public from: number;
|
||||
public to: number;
|
||||
public worker: Worker;
|
||||
public execute: string;
|
||||
public err: string;
|
||||
public context;
|
||||
|
||||
public set(entities: Entity[], from: number, to: number, execute: string, context: any) {
|
||||
this.entities = entities;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.execute = execute;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
module es {
|
||||
export abstract class PassiveSystem extends EntitySystem {
|
||||
public onChanged(entity: Entity) {
|
||||
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
// 我们用我们自己的不考虑实体的基本实体系统来代替
|
||||
this.begin();
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/** 用于协调其他系统的通用系统基类 */
|
||||
module es {
|
||||
export abstract class ProcessingSystem extends EntitySystem {
|
||||
public onChanged(entity: Entity) {
|
||||
|
||||
}
|
||||
|
||||
/** 处理我们的系统 每帧调用 */
|
||||
public abstract processSystem();
|
||||
|
||||
protected process(entities: Entity[]) {
|
||||
this.begin();
|
||||
this.processSystem();
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,501 +0,0 @@
|
||||
module transform {
|
||||
export enum Component {
|
||||
position,
|
||||
scale,
|
||||
rotation,
|
||||
}
|
||||
}
|
||||
|
||||
module es {
|
||||
export enum DirtyType {
|
||||
clean = 0,
|
||||
positionDirty = 1,
|
||||
scaleDirty = 2,
|
||||
rotationDirty = 4,
|
||||
}
|
||||
|
||||
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;
|
||||
/**
|
||||
* 值将自动从本地和父矩阵重新计算。
|
||||
*/
|
||||
public _worldTransform = Matrix2D.identity;
|
||||
public _rotationMatrix: Matrix2D = Matrix2D.identity;
|
||||
public _translationMatrix: Matrix2D = Matrix2D.identity;
|
||||
public _scaleMatrix: Matrix2D;
|
||||
public _children: Transform[] = [];
|
||||
|
||||
constructor(entity: Entity) {
|
||||
this.entity = entity;
|
||||
this.scale = this._localScale = Vector2.one;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个转换的所有子元素
|
||||
*/
|
||||
public get childCount() {
|
||||
return this._children.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间的旋转度
|
||||
*/
|
||||
public get rotationDegrees(): number {
|
||||
return MathHelper.toDegrees(this._rotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换在世界空间的旋转度
|
||||
* @param value
|
||||
*/
|
||||
public set rotationDegrees(value: number) {
|
||||
this.setRotation(MathHelper.toRadians(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转相对于父变换旋转的角度
|
||||
*/
|
||||
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) {
|
||||
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 {
|
||||
return this._children[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此转换的父转换
|
||||
* @param parent
|
||||
*/
|
||||
public setParent(parent: Transform): Transform {
|
||||
if (this._parent == parent)
|
||||
return this;
|
||||
|
||||
if (this._parent != null) {
|
||||
let children = new es.List(this._parent._children);
|
||||
children.remove(this);
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
let children = new es.List(parent._children);
|
||||
children.add(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) {
|
||||
this.localRotation = this.parent.rotation + radians;
|
||||
} else {
|
||||
this.localRotation = radians;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变换在世界空间的旋转度
|
||||
* @param degrees
|
||||
*/
|
||||
public setRotationDegrees(degrees: number): Transform {
|
||||
return this.setRotation(MathHelper.toRadians(degrees));
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转精灵的顶部,使其朝向位置
|
||||
* @param pos
|
||||
*/
|
||||
public lookAt(pos: Vector2) {
|
||||
let sign = this.position.x > pos.x ? -1 : 1;
|
||||
let vectorToAlignTo = Vector2.normalize(Vector2.subtract(this.position, pos));
|
||||
this.rotation = sign * Math.acos(Vector2.dot(vectorToAlignTo, 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) {
|
||||
this.localScale = Vector2.divide(scale, this.parent._scale);
|
||||
} else {
|
||||
this.localScale = scale;
|
||||
}
|
||||
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) {
|
||||
this._translationMatrix = Matrix2D.createTranslation(this._localPosition.x, this._localPosition.y);
|
||||
this._localPositionDirty = false;
|
||||
}
|
||||
|
||||
if (this._localRotationDirty) {
|
||||
this._rotationMatrix = Matrix2D.createRotation(this._localRotation);
|
||||
this._localRotationDirty = false;
|
||||
}
|
||||
|
||||
if (this._localScaleDirty) {
|
||||
this._scaleMatrix = Matrix2D.createScale(this._localScale.x, this._localScale.y);
|
||||
this._localScaleDirty = false;
|
||||
}
|
||||
|
||||
this._localTransform = this._scaleMatrix.multiply(this._rotationMatrix);
|
||||
this._localTransform = this._localTransform.multiply(this._translationMatrix);
|
||||
|
||||
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) {
|
||||
this._worldTransform = this._localTransform.multiply(this.parent._worldTransform);
|
||||
|
||||
this._rotation = this._localRotation + this.parent._rotation;
|
||||
this._scale = Vector2.multiply(this.parent._scale, this._localScale);
|
||||
this._worldInverseDirty = true;
|
||||
}
|
||||
|
||||
this._worldToLocalDirty = true;
|
||||
this._positionDirty = true;
|
||||
this.hierarchyDirty = DirtyType.clean;
|
||||
}
|
||||
}
|
||||
|
||||
public setDirty(dirtyFlagType: DirtyType) {
|
||||
if ((this.hierarchyDirty & dirtyFlagType) == 0) {
|
||||
this.hierarchyDirty |= dirtyFlagType;
|
||||
|
||||
switch (dirtyFlagType) {
|
||||
case es.DirtyType.positionDirty:
|
||||
this.entity.onTransformChanged(transform.Component.position);
|
||||
break;
|
||||
case es.DirtyType.rotationDirty:
|
||||
this.entity.onTransformChanged(transform.Component.rotation);
|
||||
break;
|
||||
case es.DirtyType.scaleDirty:
|
||||
this.entity.onTransformChanged(transform.Component.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;
|
||||
this._localPosition = transform._localPosition;
|
||||
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}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 这个类可以从两方面来考虑。你可以把它看成一个位向量或者一组非负整数。这个名字有点误导人。
|
||||
*
|
||||
* 它是由一个位向量实现的,但同样可以把它看成是一个非负整数的集合;集合中的每个整数由对应索引处的集合位表示。该结构的大小由集合中的最大整数决定。
|
||||
*/
|
||||
export class BitSet {
|
||||
private static ADDRESS_BITS_PER_WORD = 5;
|
||||
private static BITS_PER_WORD = 1 << BitSet.ADDRESS_BITS_PER_WORD; // 32
|
||||
private static WORD_MASK : number = 0xffffffff;
|
||||
private words_: number[];
|
||||
|
||||
constructor(nbits: number = 0) {
|
||||
this.words_ = [];
|
||||
}
|
||||
|
||||
public clear(bitIndex?: number) {
|
||||
if (bitIndex === null) {
|
||||
const words = this.words_;
|
||||
let wordsInUse = words.length;
|
||||
while (wordsInUse > 0) {
|
||||
words[--wordsInUse] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const wordIndex = bitIndex >> BitSet.ADDRESS_BITS_PER_WORD;
|
||||
this.words_[wordIndex] &= ~(1 << bitIndex);
|
||||
}
|
||||
|
||||
public get(bitIndex: number): boolean {
|
||||
const wordIndex = bitIndex >> BitSet.ADDRESS_BITS_PER_WORD;
|
||||
const words = this.words_;
|
||||
const wordsInUse = words.length;
|
||||
|
||||
return wordIndex < wordsInUse && (words[wordIndex] & (1 << bitIndex)) != 0;
|
||||
}
|
||||
|
||||
public intersects(set: BitSet) {
|
||||
const words = this.words_;
|
||||
const wordsInUse = words.length;
|
||||
|
||||
for (let i = Math.min(wordsInUse, set.words_.length) - 1; i >= 0; i--)
|
||||
if ((words[i] & set.words_[i]) != 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.words_.length === 0;
|
||||
}
|
||||
|
||||
public nextSetBit(fromIndex: number) {
|
||||
let u = fromIndex >> BitSet.ADDRESS_BITS_PER_WORD;
|
||||
const words = this.words_;
|
||||
const wordsInUse = words.length;
|
||||
|
||||
let word = words[u] & (BitSet.WORD_MASK << fromIndex);
|
||||
while (true) {
|
||||
if (word !== 0) return u * BitSet.BITS_PER_WORD + this.numberOfTrailingZeros(word);
|
||||
if (++u === wordsInUse) return -1;
|
||||
word = words[u];
|
||||
}
|
||||
}
|
||||
|
||||
private numberOfTrailingZeros(i: number): number {
|
||||
if (i == 0) return 64;
|
||||
let x: number = i;
|
||||
let y: number;
|
||||
let n = 63;
|
||||
y = x << 32;
|
||||
if (y != 0) {
|
||||
n -= 32;
|
||||
x = y;
|
||||
}
|
||||
y = x << 16;
|
||||
if (y != 0) {
|
||||
n -= 16;
|
||||
x = y;
|
||||
}
|
||||
y = x << 8;
|
||||
if (y != 0) {
|
||||
n -= 8;
|
||||
x = y;
|
||||
}
|
||||
y = x << 4;
|
||||
if (y != 0) {
|
||||
n -= 4;
|
||||
x = y;
|
||||
}
|
||||
y = x << 2;
|
||||
if (y != 0) {
|
||||
n -= 2;
|
||||
x = y;
|
||||
}
|
||||
return n - ((x << 1) >>> 63);
|
||||
}
|
||||
|
||||
public set(bitIndex: number, value: boolean = true) {
|
||||
const wordIndex = bitIndex >> BitSet.ADDRESS_BITS_PER_WORD;
|
||||
const words = this.words_;
|
||||
const wordsInUse = words.length;
|
||||
const wordsRequired = wordIndex + 1;
|
||||
|
||||
if (wordsInUse < wordsRequired) {
|
||||
words.length = Math.max(2 * wordsInUse, wordsRequired);
|
||||
for (let i = wordsInUse, l = words.length; i < l; i++) {
|
||||
words[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return (words[wordIndex] |= 1 << bitIndex);
|
||||
} else {
|
||||
return (words[wordIndex] &= ~(1 << bitIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 性能优化的位组实现。某些操作是以不安全为前缀的, 这些方法不执行验证,主要是在内部利用来优化实体ID位集的访问
|
||||
*/
|
||||
export class BitVector {
|
||||
private words: number[] = [0];
|
||||
|
||||
/**
|
||||
* 创建一个初始大小足够大的bitset,以明确表示0到nbits-1范围内指数的bit
|
||||
* @param nbits nbits 位集的初始大小
|
||||
*/
|
||||
constructor(nbits?: number | BitVector) {
|
||||
if (nbits) {
|
||||
if (typeof nbits == 'number')
|
||||
this.checkCapacity(nbits >>> 6);
|
||||
else {
|
||||
// 基于另一个位向量创建一个位集
|
||||
this.words = nbits.words.slice(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index 位的索引
|
||||
* @returns 该位是否被设置
|
||||
*/
|
||||
public get(index: number): boolean {
|
||||
const word = index >>> 6;
|
||||
return word < this.words.length &&
|
||||
(this.words[word] & (1 << index)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index 位的索引
|
||||
*/
|
||||
public set(index: number, value: boolean = true) {
|
||||
if (value) {
|
||||
const word = index >>> 6;
|
||||
this.checkCapacity(word);
|
||||
this.words[word] |= 1 << index;
|
||||
} else {
|
||||
this.clear(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index 位的索引
|
||||
* @returns 该位是否被设置
|
||||
*/
|
||||
public unsafeGet(index: number): boolean {
|
||||
return (this.words[index >>> 6] & (1 << index)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index 要设置的位的索引
|
||||
*/
|
||||
public unsafeSet(index: number) {
|
||||
this.words[index >>> 6] |= 1 << index;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index 要翻转的位的索引
|
||||
*/
|
||||
public flip(index: number) {
|
||||
const word = index >>> 6;
|
||||
this.checkCapacity(word);
|
||||
this.words[word] ^= 1 << index;
|
||||
}
|
||||
|
||||
/**
|
||||
* 要清除的位的索引
|
||||
* @param index
|
||||
*/
|
||||
public clear(index?: number) {
|
||||
if (index != null) {
|
||||
const word = index >>> 6;
|
||||
if (word >= this.words.length) return;
|
||||
this.words[word] &= ~(1 << index);
|
||||
} else {
|
||||
this.words.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该位组的 "逻辑大小":位组中最高设置位的索引加1。如果比特集不包含集合位,则返回0
|
||||
*/
|
||||
public length(): number {
|
||||
let bits = this.words.slice(0);
|
||||
for (let word = bits.length - 1; word >= 0; --word) {
|
||||
let bitsAtWord = bits[word];
|
||||
if (bitsAtWord != 0)
|
||||
return (word << 6) + 64 - this.numberOfLeadingZeros(bitsAtWord);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 如果这个位组中没有设置为true的位,则为true
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
let bits = this.words.slice(0);
|
||||
let length = bits.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (bits[i] != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回在指定的起始索引上或之后出现的第一个被设置为真的位的索引。
|
||||
* 如果不存在这样的位,则返回-1
|
||||
* @param fromIndex
|
||||
*/
|
||||
public nextSetBit(fromIndex: number): number {
|
||||
let word = fromIndex >>> 6;
|
||||
if (word >= this.words.length)
|
||||
return -1;
|
||||
|
||||
let bitmap = this.words[word] >>> fromIndex;
|
||||
if (bitmap != 0)
|
||||
return fromIndex + this.numberOfTrailingZeros(bitmap);
|
||||
|
||||
for (let i = 1 + word; i < this.words.length; i++) {
|
||||
bitmap = this.words[i];
|
||||
if (bitmap != 0) {
|
||||
return i * 64 + this.numberOfTrailingZeros(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回在指定的起始索引上或之后发生的第一个被设置为false的位的索引
|
||||
* @param fromIndex
|
||||
*/
|
||||
public nextClearBit(fromIndex: number): number {
|
||||
let word = fromIndex >>> 6;
|
||||
if (word >= this.words.length)
|
||||
return Math.min(fromIndex, this.words.length << 6);
|
||||
|
||||
let bitmap = ~(this.words[word] >>> fromIndex);
|
||||
if (bitmap != 0)
|
||||
return fromIndex + this.numberOfTrailingZeros(bitmap);
|
||||
|
||||
for (let i = 1 + word; i < this.words.length; i++) {
|
||||
bitmap = ~this.words[i];
|
||||
if (bitmap != 0) {
|
||||
return i * 64 + this.numberOfTrailingZeros(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(fromIndex, this.words.length << 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对这个目标位集和参数位集进行逻辑AND。
|
||||
* 这个位集被修改,使它的每一个位都有值为真,如果且仅当它最初的值为真,并且位集参数中的相应位也有值为真
|
||||
* @param other
|
||||
*/
|
||||
public and(other: BitVector) {
|
||||
let commonWords = Math.min(this.words.length, other.words.length);
|
||||
for (let i = 0; commonWords > i; i++) {
|
||||
this.words[i] &= other.words[i];
|
||||
}
|
||||
|
||||
if (this.words.length > commonWords) {
|
||||
for (let i = commonWords, s = this.words.length; s > i; i++) {
|
||||
this.words[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除该位集的所有位,其对应的位被设置在指定的位集中
|
||||
* @param other
|
||||
*/
|
||||
public andNot(other: BitVector) {
|
||||
let commonWords = Math.min(this.words.length, other.words.length);
|
||||
for (let i = 0; commonWords > i; i++)
|
||||
this.words[i] &= ~other.words[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用位集参数执行这个位集的逻辑OR。
|
||||
* 如果且仅当位集参数中的位已经有值为真或位集参数中的对应位有值为真时,该位集才会被修改,从而使位集中的位有值为真
|
||||
* @param other
|
||||
*/
|
||||
public or(other: BitVector) {
|
||||
let commonWords = Math.min(this.words.length, other.words.length);
|
||||
for (let i = 0; commonWords > i; i++)
|
||||
this.words[i] |= other.words[i];
|
||||
|
||||
if (commonWords < other.words.length) {
|
||||
this.checkCapacity(other.words.length);
|
||||
for (let i = commonWords, s = other.words.length; s > i; i++) {
|
||||
this.words[i] = other.words[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用位集参数对这个位集进行逻辑XOR。
|
||||
* 这个位集被修改了,所以如果且仅当以下语句之一成立时,位集中的一个位的值为真
|
||||
* @param other
|
||||
*/
|
||||
public xor(other: BitVector) {
|
||||
let commonWords = Math.min(this.words.length, other.words.length);
|
||||
|
||||
for (let i = 0; commonWords > i; i++)
|
||||
this.words[i] ^= other.words[i];
|
||||
|
||||
if (commonWords < other.words.length) {
|
||||
this.checkCapacity(other.words.length);
|
||||
for (let i = commonWords, s = other.words.length; s > i; i++) {
|
||||
this.words[i] = other.words[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果指定的BitVector有任何位被设置为true,并且在这个BitVector中也被设置为true,则返回true
|
||||
* @param other
|
||||
*/
|
||||
public intersects(other: BitVector): boolean {
|
||||
let bits = this.words.slice(0);
|
||||
let otherBits = other.words;
|
||||
for (let i = 0, s = Math.min(bits.length, otherBits.length); s > i; i++) {
|
||||
if ((bits[i] & otherBits[i]) != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果这个位集是指定位集的超级集,即它的所有位都被设置为真,那么返回true
|
||||
* @param other
|
||||
*/
|
||||
public containsAll(other: BitVector): boolean {
|
||||
let bits = this.words.slice(0);
|
||||
let otherBits = other.words;
|
||||
let otherBitsLength = otherBits.length;
|
||||
let bitsLength = bits.length;
|
||||
|
||||
|
||||
for (let i = bitsLength; i < otherBitsLength; i++) {
|
||||
if (otherBits[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, s = Math.min(bitsLength, otherBitsLength); s > i; i++) {
|
||||
if ((bits[i] & otherBits[i]) != otherBits[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public cardinality() {
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.words.length; i++)
|
||||
count += this.bitCount(this.words[i]);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public hashCode() {
|
||||
const word = this.length() >>> 6;
|
||||
let hash = 0;
|
||||
for (let i = 0; word >= i; i++)
|
||||
hash = 127 * hash + (this.words[i] ^ (this.words[i] >>> 32));
|
||||
return hash;
|
||||
}
|
||||
|
||||
private bitCount(i: number) {
|
||||
i = i - ((i >>> 1) & 0x55555555);
|
||||
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
|
||||
i = (i + (i >>> 4)) & 0x0f0f0f0f;
|
||||
i = i + (i >>> 8);
|
||||
i = i + (i >>> 16);
|
||||
return i & 0x3f;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回二进制补码二进制表示形式中最高位(“最左端”)一位之前的零位数量
|
||||
* @param i
|
||||
*/
|
||||
private numberOfLeadingZeros(i: number) {
|
||||
if (i == 0) return 64;
|
||||
let n = 1;
|
||||
let x = i >>> 32;
|
||||
if (x == 0) { n += 32; x = i; }
|
||||
if (x >>> 16 == 0) { n += 16; x <<= 16; }
|
||||
if (x >>> 24 == 0) { n += 8; x <<= 8; }
|
||||
if (x >>> 28 == 0) { n += 4; x <<= 4; }
|
||||
if (x >>> 30 == 0) { n += 2; x <<= 2; }
|
||||
n -= x >>> 31;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回指定二进制数的补码二进制表示形式中最低序(“最右”)一位之后的零位数量
|
||||
* @param i
|
||||
*/
|
||||
public numberOfTrailingZeros(i: number) {
|
||||
let x: number = 0, y: number = 0;
|
||||
if (i == 0) return 64;
|
||||
let n = 63;
|
||||
y = i; if (y != 0) { n = n - 32; x = y; } else x = (i >>> 32);
|
||||
y = x << 16; if (y != 0) { n = n - 16; x = y; }
|
||||
y = x << 8; if (y != 0) { n = n - 8; x = y; }
|
||||
y = x << 4; if (y != 0) { n = n - 4; x = y; }
|
||||
y = x << 2; if (y != 0) { n = n - 2; x = y; }
|
||||
return n - ((x << 1) >>> 31);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index 要清除的位的索引
|
||||
*/
|
||||
public unsafeClear(index: number) {
|
||||
this.words[index >>> 6] &= ~(1 << index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增长支持数组,使其能够容纳所请求的位
|
||||
* @param bits 位数
|
||||
*/
|
||||
public ensureCapacity(bits: number) {
|
||||
this.checkCapacity(bits >>> 6);
|
||||
}
|
||||
|
||||
private checkCapacity(len: number) {
|
||||
if (len >= this.words.length) {
|
||||
let newBits: number[] = new Array(len + 1);
|
||||
for (let i = 0; i < this.words.length; i++) {
|
||||
newBits[i] = this.words[i];
|
||||
}
|
||||
this.words = newBits;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
///<reference path="../Components/IUpdatable.ts" />
|
||||
module es {
|
||||
export class ComponentList {
|
||||
/**
|
||||
* 组件列表的全局updateOrder排序
|
||||
*/
|
||||
public static compareUpdatableOrder: IUpdatableComparer = new IUpdatableComparer();
|
||||
public _entity: Entity;
|
||||
|
||||
/**
|
||||
* 添加到实体的组件列表
|
||||
*/
|
||||
public _components: Component[] = [];
|
||||
/** 记录component的快速读取列表 */
|
||||
public fastComponentsMap = new Map<new (...args: any[]) => Component, es.Component[]>();
|
||||
public fastComponentsToAddMap = new Map<new (...args: any[]) => Component, es.Component[]>();
|
||||
/**
|
||||
* 所有需要更新的组件列表
|
||||
*/
|
||||
public _updatableComponents: IUpdatable[] = [];
|
||||
/**
|
||||
* 添加到此框架的组件列表。用来对组件进行分组,这样我们就可以同时进行加工
|
||||
*/
|
||||
public _componentsToAdd: {[index: number]: Component} = {};
|
||||
/**
|
||||
* 标记要删除此框架的组件列表。用来对组件进行分组,这样我们就可以同时进行加工
|
||||
*/
|
||||
public _componentsToRemove: {[index: number]: Component} = {};
|
||||
public _tempBufferList: Component[] = [];
|
||||
/**
|
||||
* 用于确定是否需要对该框架中的组件进行排序的标志
|
||||
*/
|
||||
public _isComponentListUnsorted: boolean;
|
||||
|
||||
constructor(entity: Entity) {
|
||||
this._entity = entity;
|
||||
}
|
||||
|
||||
public get count() {
|
||||
return this._components.length;
|
||||
}
|
||||
|
||||
public get buffer() {
|
||||
return this._components;
|
||||
}
|
||||
|
||||
public markEntityListUnsorted() {
|
||||
this._isComponentListUnsorted = true;
|
||||
}
|
||||
|
||||
public add(component: Component) {
|
||||
this._componentsToAdd[component.id] = component;
|
||||
this.addFastComponentToAdd(component);
|
||||
}
|
||||
|
||||
public remove(component: Component) {
|
||||
Debug.warnIf(!!this._componentsToRemove[component.id], `您正在尝试删除一个您已经删除的组件(${component})`);
|
||||
//
|
||||
// 这可能不是一个活动的组件,所以我们必须注意它是否还没有被处理,它可能正在同一帧中被删除
|
||||
if (this._componentsToAdd[component.id]) {
|
||||
delete this._componentsToAdd[component.id];
|
||||
this.removeFastComponentToAdd(component);
|
||||
return;
|
||||
}
|
||||
|
||||
this._componentsToRemove[component.id] = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即从组件列表中删除所有组件
|
||||
*/
|
||||
public removeAllComponents() {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
this.handleRemove(this._components[i]);
|
||||
}
|
||||
|
||||
this.fastComponentsMap.clear();
|
||||
this.fastComponentsToAddMap.clear();
|
||||
this._components.length = 0;
|
||||
this._updatableComponents.length = 0;
|
||||
this._componentsToAdd = {};
|
||||
this._componentsToRemove = {};
|
||||
}
|
||||
|
||||
public deregisterAllComponents() {
|
||||
for (let component of this._components) {
|
||||
if (!component) continue;
|
||||
|
||||
// 处理IUpdatable
|
||||
if (isIUpdatable(component))
|
||||
new es.List(this._updatableComponents).remove(component);
|
||||
|
||||
this._entity.componentBits.set(ComponentTypeManager.getIndexFor(TypeUtils.getType(component)), false);
|
||||
this._entity.scene.entityProcessors.onComponentRemoved(this._entity);
|
||||
}
|
||||
}
|
||||
|
||||
public registerAllComponents() {
|
||||
for (let component of this._components) {
|
||||
if (isIUpdatable(component))
|
||||
this._updatableComponents.push(component);
|
||||
|
||||
this._entity.componentBits.set(ComponentTypeManager.getIndexFor(TypeUtils.getType(component)));
|
||||
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理任何需要删除或添加的组件
|
||||
*/
|
||||
public updateLists() {
|
||||
for (let i in this._componentsToRemove) {
|
||||
let component = this._componentsToRemove[i];
|
||||
this.handleRemove(component);
|
||||
for (let index = 0; index < this._components.length; index ++) {
|
||||
let searchComponent = this._components[index];
|
||||
if (searchComponent.id == component.id) {
|
||||
this._components.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.removeFastComponent(component);
|
||||
}
|
||||
|
||||
this._componentsToRemove = {};
|
||||
|
||||
for (let i in this._componentsToAdd) {
|
||||
let component = this._componentsToAdd[i];
|
||||
|
||||
if (isIUpdatable(component))
|
||||
this._updatableComponents.push(component);
|
||||
|
||||
this._entity.componentBits.set(ComponentTypeManager.getIndexFor(TypeUtils.getType(component)));
|
||||
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
|
||||
this.addFastComponent(component);
|
||||
|
||||
this._components.push(component);
|
||||
this._tempBufferList.push(component);
|
||||
}
|
||||
|
||||
// 在调用onAddedToEntity之前清除,以防添加更多组件
|
||||
this._componentsToAdd = {};
|
||||
this.fastComponentsToAddMap.clear();
|
||||
this._isComponentListUnsorted = true;
|
||||
|
||||
// 现在所有的组件都添加到了场景中,我们再次循环并调用onAddedToEntity/onEnabled
|
||||
for (let i = 0; i < this._tempBufferList.length; i++) {
|
||||
let component = this._tempBufferList[i];
|
||||
component.onAddedToEntity();
|
||||
|
||||
// enabled检查实体和组件
|
||||
if (component.enabled) {
|
||||
component.onEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
this._tempBufferList.length = 0;
|
||||
}
|
||||
|
||||
public handleRemove(component: Component) {
|
||||
if (isIUpdatable(component))
|
||||
new es.List(this._updatableComponents).remove(component);
|
||||
|
||||
this._entity.componentBits.set(ComponentTypeManager.getIndexFor(TypeUtils.getType(component)), false);
|
||||
this._entity.scene.entityProcessors.onComponentRemoved(this._entity);
|
||||
|
||||
component.onRemovedFromEntity();
|
||||
component.entity = null;
|
||||
}
|
||||
|
||||
private removeFastComponent(component: Component) {
|
||||
let fastList = this.fastComponentsMap.get(TypeUtils.getType(component));
|
||||
let fastIndex = fastList.findIndex(c => c.id == component.id);
|
||||
if (fastIndex != -1)
|
||||
fastList.splice(fastIndex, 1);
|
||||
}
|
||||
|
||||
private addFastComponent(component: Component) {
|
||||
let fastList = this.fastComponentsMap.get(TypeUtils.getType(component));
|
||||
if (!fastList)
|
||||
fastList = [];
|
||||
fastList.push(component);
|
||||
this.fastComponentsMap.set(TypeUtils.getType(component), fastList);
|
||||
}
|
||||
|
||||
private removeFastComponentToAdd(component: Component) {
|
||||
let fastList = this.fastComponentsToAddMap.get(TypeUtils.getType(component));
|
||||
let fastIndex = fastList.findIndex(c => c.id == component.id);
|
||||
if (fastIndex != -1)
|
||||
fastList.splice(fastIndex, 1);
|
||||
}
|
||||
|
||||
private addFastComponentToAdd(component: Component) {
|
||||
let fastList = this.fastComponentsToAddMap.get(TypeUtils.getType(component));
|
||||
if (!fastList)
|
||||
fastList = [];
|
||||
fastList.push(component);
|
||||
this.fastComponentsToAddMap.set(TypeUtils.getType(component), fastList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型T的第一个组件并返回它
|
||||
* 可以选择跳过检查未初始化的组件(尚未调用onAddedToEntity方法的组件)
|
||||
* 如果没有找到组件,则返回null。
|
||||
* @param type
|
||||
* @param onlyReturnInitializedComponents
|
||||
*/
|
||||
public getComponent<T extends Component>(type, onlyReturnInitializedComponents: boolean): T {
|
||||
let fastList = this.fastComponentsMap.get(type);
|
||||
if (fastList && fastList.length > 0)
|
||||
return fastList[0] as T;
|
||||
|
||||
// 我们可以选择检查挂起的组件,以防addComponent和getComponent在同一个框架中被调用
|
||||
if (!onlyReturnInitializedComponents) {
|
||||
let fastToAddList = this.fastComponentsToAddMap.get(type);
|
||||
if (fastToAddList && fastToAddList.length > 0)
|
||||
return fastToAddList[0] as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取T类型的所有组件,但不使用列表分配
|
||||
* @param typeName
|
||||
* @param components
|
||||
*/
|
||||
public getComponents(typeName: any, components?: any[]) {
|
||||
if (!components)
|
||||
components = [];
|
||||
|
||||
let fastList = this.fastComponentsMap.get(typeName);
|
||||
if (fastList)
|
||||
components.concat(fastList);
|
||||
|
||||
let fastToAddList = this.fastComponentsToAddMap.get(typeName);
|
||||
if (fastToAddList)
|
||||
components.concat(fastToAddList);
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
public update() {
|
||||
this.updateLists();
|
||||
for (let i = 0; i < this._updatableComponents.length; i++) {
|
||||
if (this._updatableComponents[i].enabled)
|
||||
this._updatableComponents[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: transform.Component) {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
if (this._components[i].enabled)
|
||||
this._components[i].onEntityTransformChanged(comp);
|
||||
}
|
||||
|
||||
for (let i in this._componentsToAdd) {
|
||||
let component = this._componentsToAdd[i];
|
||||
if (component.enabled)
|
||||
component.onEntityTransformChanged(comp);
|
||||
}
|
||||
}
|
||||
|
||||
public onEntityEnabled() {
|
||||
for (let i = 0; i < this._components.length; i++)
|
||||
this._components[i].onEnabled();
|
||||
}
|
||||
|
||||
public onEntityDisabled() {
|
||||
for (let i = 0; i < this._components.length; i++)
|
||||
this._components[i].onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
module es {
|
||||
interface IdentityHashMap {
|
||||
[key: string]: ComponentType;
|
||||
}
|
||||
|
||||
export class ComponentTypeFactory {
|
||||
private componentTypes_: IdentityHashMap;
|
||||
|
||||
private componentTypeCount_ = 0;
|
||||
|
||||
public types: Bag<ComponentType>;
|
||||
|
||||
constructor() {
|
||||
this.componentTypes_ = {};
|
||||
this.types = new Bag<ComponentType>();
|
||||
}
|
||||
|
||||
public getTypeFor(c): ComponentType {
|
||||
if ("number" === typeof c) {
|
||||
return this.types.get(c);
|
||||
}
|
||||
|
||||
let type: ComponentType = this.componentTypes_[getClassName(c)];
|
||||
|
||||
if (type == null) {
|
||||
const index: number = this.componentTypeCount_++;
|
||||
type = new ComponentType(c, index);
|
||||
this.componentTypes_[getClassName(c)] = type;
|
||||
this.types.set(index, type);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public getIndexFor(c): number {
|
||||
return this.getTypeFor(c).getIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
module es {
|
||||
export class ComponentTypeManager {
|
||||
private static _componentTypesMask: Map<any, number> = new Map<any, number>();
|
||||
|
||||
public static add(type) {
|
||||
if (!this._componentTypesMask.has(type))
|
||||
this._componentTypesMask.set(type, this._componentTypesMask.size);
|
||||
}
|
||||
|
||||
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 v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
module es {
|
||||
export class EntityList {
|
||||
public scene: Scene;
|
||||
/**
|
||||
* 场景中添加的实体列表
|
||||
*/
|
||||
public _entities: Entity[] = [];
|
||||
/**
|
||||
* 本帧添加的实体列表。用于对实体进行分组,以便我们可以同时处理它们
|
||||
*/
|
||||
// public _entitiesToAdded: Entity[] = [];
|
||||
public _entitiesToAdded: {[index: number]: Entity} = {};
|
||||
/**
|
||||
* 本帧被标记为删除的实体列表。用于对实体进行分组,以便我们可以同时处理它们
|
||||
*/
|
||||
public _entitiesToRemove: {[index: number]: Entity} = {};
|
||||
/**
|
||||
* 标志,用于确定我们是否需要在这一帧中对实体进行排序
|
||||
*/
|
||||
public _isEntityListUnsorted: boolean;
|
||||
/**
|
||||
* 通过标签跟踪实体,便于检索
|
||||
*/
|
||||
public _entityDict: Map<number, Set<Entity>> = new Map<number, Set<Entity>>();
|
||||
public _unsortedTags: Set<number> = new Set<number>();
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
public get count() {
|
||||
return this._entities.length;
|
||||
}
|
||||
|
||||
public get buffer() {
|
||||
return this._entities;
|
||||
}
|
||||
|
||||
public markEntityListUnsorted() {
|
||||
this._isEntityListUnsorted = true;
|
||||
}
|
||||
|
||||
public markTagUnsorted(tag: number) {
|
||||
this._unsortedTags.add(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个实体添加到列表中。所有的生命周期方法将在下一帧中被调用
|
||||
* @param entity
|
||||
*/
|
||||
public add(entity: Entity) {
|
||||
this._entitiesToAdded[entity.id] = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表中删除一个实体。所有的生命周期方法将在下一帧中被调用
|
||||
* @param entity
|
||||
*/
|
||||
public remove(entity: Entity) {
|
||||
// 防止在同一帧中添加或删除实体
|
||||
if (this._entitiesToAdded[entity.id]) {
|
||||
delete this._entitiesToAdded[entity.id];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._entitiesToRemove[entity.id])
|
||||
this._entitiesToRemove[entity.id] = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体列表中删除所有实体
|
||||
*/
|
||||
public removeAllEntities() {
|
||||
this._unsortedTags.clear();
|
||||
this._entitiesToAdded = {};
|
||||
this._isEntityListUnsorted = false;
|
||||
|
||||
// 为什么我们要在这里更新列表?主要是为了处理在场景切换前被分离的实体。
|
||||
// 它们仍然会在_entitiesToRemove列表中,这将由updateLists处理。
|
||||
this.updateLists();
|
||||
|
||||
for (let i = 0; i < this._entities.length; i++) {
|
||||
this._entities[i]._isDestroyed = true;
|
||||
this._entities[i].onRemovedFromScene();
|
||||
this._entities[i].scene = null;
|
||||
}
|
||||
|
||||
this._entities.length = 0;
|
||||
this._entityDict.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体目前是否由这个EntityList管理
|
||||
* @param entity
|
||||
*/
|
||||
public contains(entity: Entity): boolean {
|
||||
return !!this._entitiesToAdded[entity.id];
|
||||
}
|
||||
|
||||
public getTagList(tag: number) {
|
||||
let list = this._entityDict.get(tag);
|
||||
if (!list) {
|
||||
list = new Set();
|
||||
this._entityDict.set(tag, list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public addToTagList(entity: Entity) {
|
||||
this.getTagList(entity.tag).add(entity);
|
||||
this._unsortedTags.add(entity.tag);
|
||||
}
|
||||
|
||||
public removeFromTagList(entity: Entity) {
|
||||
let list = this._entityDict.get(entity.tag);
|
||||
if (list)
|
||||
list.delete(entity);
|
||||
}
|
||||
|
||||
public update() {
|
||||
for (let entity of this._entities) {
|
||||
if (entity.enabled && (entity.updateInterval == 1 || Time.frameCount % entity.updateInterval == 0))
|
||||
entity.update();
|
||||
}
|
||||
}
|
||||
|
||||
public updateLists() {
|
||||
for (let i in this._entitiesToRemove) {
|
||||
let entity = this._entitiesToRemove[i];
|
||||
this.removeFromTagList(entity);
|
||||
|
||||
// 处理常规实体列表
|
||||
new es.List(this._entities).remove(entity);
|
||||
entity.onRemovedFromScene();
|
||||
entity.scene = null;
|
||||
|
||||
this.scene.entityProcessors.onEntityRemoved(entity);
|
||||
}
|
||||
this._entitiesToRemove = {};
|
||||
|
||||
for (let i in this._entitiesToAdded) {
|
||||
let entity = this._entitiesToAdded[i];
|
||||
this._entities.push(entity);
|
||||
entity.scene = this.scene;
|
||||
|
||||
this.addToTagList(entity);
|
||||
|
||||
this.scene.entityProcessors.onEntityAdded(entity);
|
||||
}
|
||||
|
||||
for (let i in this._entitiesToAdded) {
|
||||
this._entitiesToAdded[i].onAddedToScene();
|
||||
}
|
||||
|
||||
this._entitiesToAdded = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个找到的名字为name的实体。如果没有找到则返回null
|
||||
* @param name
|
||||
*/
|
||||
public findEntity(name: string) {
|
||||
for (let i = 0; i < this._entities.length; i++) {
|
||||
if (this._entities[i].name == name)
|
||||
return this._entities[i];
|
||||
}
|
||||
|
||||
for (let i in this._entitiesToAdded) {
|
||||
let entity = this._entitiesToAdded[i];
|
||||
if (entity.name == name)
|
||||
return entity;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
public findEntityById(id: number) {
|
||||
for (let i = 0; i < this._entities.length; i ++) {
|
||||
if (this._entities[i].id == id)
|
||||
return this._entities[i];
|
||||
}
|
||||
|
||||
return this._entitiesToAdded[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带有标签的所有实体的列表。如果没有实体有标签,则返回一个空列表。
|
||||
* 返回的List可以通过ListPool.free放回池中
|
||||
* @param tag
|
||||
*/
|
||||
public entitiesWithTag(tag: number) {
|
||||
let list = this.getTagList(tag);
|
||||
|
||||
let returnList = ListPool.obtain<Entity>();
|
||||
for (let entity of list) {
|
||||
returnList.push(entity);
|
||||
}
|
||||
|
||||
return returnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个找到该tag的实体
|
||||
* @param tag
|
||||
* @returns
|
||||
*/
|
||||
public entityWithTag(tag: number) {
|
||||
let list = this.getTagList(tag);
|
||||
|
||||
for (let entity of list) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回在场景中找到的第一个T类型的组件。
|
||||
* @param type
|
||||
*/
|
||||
public findComponentOfType<T extends Component>(type): T {
|
||||
for (let i = 0; i < this._entities.length; i++) {
|
||||
if (this._entities[i].enabled) {
|
||||
let comp = this._entities[i].getComponent<T>(type);
|
||||
if (comp)
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
// for (let i = 0; i < this._entitiesToAdded.getCount(); i++) {
|
||||
// let entity: Entity = this._entitiesToAdded.toArray()[i];
|
||||
// if (entity.enabled) {
|
||||
// let comp = entity.getComponent<T>(type);
|
||||
// if (comp)
|
||||
// return comp;
|
||||
// }
|
||||
// }
|
||||
|
||||
for (let i in this._entitiesToAdded) {
|
||||
let entity = this._entitiesToAdded[i];
|
||||
if (entity.enabled) {
|
||||
let comp = entity.getComponent<T>(type);
|
||||
if (comp)
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回在场景中找到的所有T类型的组件。
|
||||
* 返回的List可以通过ListPool.free放回池中。
|
||||
* @param type
|
||||
*/
|
||||
public findComponentsOfType<T extends Component>(type): T[] {
|
||||
let comps = ListPool.obtain<T>();
|
||||
for (let i = 0; i < this._entities.length; i++) {
|
||||
if (this._entities[i].enabled)
|
||||
this._entities[i].getComponents(type, comps);
|
||||
}
|
||||
|
||||
// for (let i = 0; i < this._entitiesToAdded.getCount(); i++) {
|
||||
// let entity = this._entitiesToAdded.toArray()[i];
|
||||
// if (entity.enabled)
|
||||
// entity.getComponents(type, comps);
|
||||
// }
|
||||
|
||||
for (let i in this._entitiesToAdded) {
|
||||
let entity = this._entitiesToAdded[i];
|
||||
if (entity.enabled)
|
||||
entity.getComponents(type, comps);
|
||||
}
|
||||
|
||||
return comps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回场景中包含特定组件的实体列表
|
||||
* @param types
|
||||
* @returns
|
||||
*/
|
||||
public findEntitesOfComponent(...types): Entity[] {
|
||||
let entities = [];
|
||||
for (let i = 0; i < this._entities.length; i++) {
|
||||
if (this._entities[i].enabled) {
|
||||
let meet = true;
|
||||
for (let type of types) {
|
||||
let hasComp = this._entities[i].hasComponent(type);
|
||||
if (!hasComp) {
|
||||
meet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (meet) {
|
||||
entities.push(this._entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for (let i = 0; i < this._entitiesToAdded.getCount(); i++) {
|
||||
// let entity: Entity = this._entitiesToAdded.toArray()[i];
|
||||
// if (entity.enabled) {
|
||||
// let meet = true;
|
||||
// for (let type of types) {
|
||||
// let hasComp = entity.hasComponent(type);
|
||||
// if (!hasComp) {
|
||||
// meet = false;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (meet) {
|
||||
// entities.push(entity);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
for (let i in this._entitiesToAdded) {
|
||||
let entity = this._entitiesToAdded[i];
|
||||
if (entity.enabled) {
|
||||
let meet = true;
|
||||
for (let type of types) {
|
||||
let hasComp = entity.hasComponent(type);
|
||||
if (!hasComp) {
|
||||
meet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (meet) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
module es {
|
||||
export class EntityProcessorList {
|
||||
public _processors: EntitySystem[] = [];
|
||||
|
||||
public add(processor: EntitySystem) {
|
||||
this._processors.push(processor);
|
||||
}
|
||||
|
||||
public remove(processor: EntitySystem) {
|
||||
new es.List(this._processors).remove(processor);
|
||||
}
|
||||
|
||||
public onComponentAdded(entity: Entity) {
|
||||
this.notifyEntityChanged(entity);
|
||||
}
|
||||
|
||||
public onComponentRemoved(entity: Entity) {
|
||||
this.notifyEntityChanged(entity);
|
||||
}
|
||||
|
||||
public onEntityAdded(entity: Entity) {
|
||||
this.notifyEntityChanged(entity);
|
||||
}
|
||||
|
||||
public onEntityRemoved(entity: Entity) {
|
||||
this.removeFromProcessors(entity);
|
||||
}
|
||||
|
||||
public begin() {
|
||||
|
||||
}
|
||||
|
||||
public update() {
|
||||
for (let i = 0; i < this._processors.length; i++) {
|
||||
this._processors[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
public lateUpdate() {
|
||||
for (let i = 0; i < this._processors.length; i++) {
|
||||
this._processors[i].lateUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public end() {
|
||||
|
||||
}
|
||||
|
||||
public getProcessor<T extends EntitySystem>(): T {
|
||||
for (let i = 0; i < this._processors.length; i++) {
|
||||
let processor = this._processors[i];
|
||||
if (processor instanceof EntitySystem)
|
||||
return processor as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected notifyEntityChanged(entity: Entity) {
|
||||
for (let i = 0; i < this._processors.length; i++) {
|
||||
this._processors[i].onChanged(entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected removeFromProcessors(entity: Entity) {
|
||||
for (let i = 0; i < this._processors.length; i++) {
|
||||
this._processors[i].remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
module es {
|
||||
export class HashHelpers {
|
||||
public static readonly hashCollisionThreshold: number = 100;
|
||||
public static readonly hashPrime: number = 101;
|
||||
|
||||
/**
|
||||
* 用来作为哈希表大小的质数表。
|
||||
* 一个典型的调整大小的算法会在这个数组中选取比之前容量大两倍的最小质数。
|
||||
* 假设我们的Hashtable当前的容量为x,并且添加了足够多的元素,因此需要进行大小调整。
|
||||
* 调整大小首先计算2x,然后在表中找到第一个大于2x的质数,即如果质数的顺序是p_1,p_2,...,p_i,...,则找到p_n,使p_n-1 < 2x < p_n。
|
||||
* 双倍对于保持哈希特操作的渐近复杂度是很重要的,比如添加。
|
||||
* 拥有一个质数可以保证双倍哈希不会导致无限循环。 IE,你的哈希函数将是h1(key)+i*h2(key),0 <= i < size.h2和size必须是相对质数。
|
||||
*/
|
||||
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];
|
||||
|
||||
/**
|
||||
* 这是比Array.MaxArrayLength小的最大质数
|
||||
*/
|
||||
public static readonly maxPrimeArrayLength = 0x7FEFFFFD;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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){
|
||||
if (this.isPrime(i) && ((i - 1) % this.hashPrime != 0))
|
||||
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);
|
||||
}
|
||||
|
||||
public static getHashCode(str){
|
||||
let s;
|
||||
if (typeof str == 'object'){
|
||||
s = JSON.stringify(str);
|
||||
} else {
|
||||
s = str.toString();
|
||||
}
|
||||
|
||||
let hash = 0;
|
||||
if (s.length == 0) return hash;
|
||||
for (let i = 0; i < s.length; i ++){
|
||||
let char = s.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
module es {
|
||||
export class Matcher {
|
||||
protected allSet = new BitSet();
|
||||
protected exclusionSet = new BitSet();
|
||||
protected oneSet = new BitSet();
|
||||
|
||||
public static empty() {
|
||||
return new Matcher();
|
||||
}
|
||||
|
||||
public getAllSet() {
|
||||
return this.allSet;
|
||||
}
|
||||
|
||||
public getExclusionSet() {
|
||||
return this.exclusionSet;
|
||||
}
|
||||
|
||||
public getOneSet() {
|
||||
return this.oneSet;
|
||||
}
|
||||
|
||||
public isInterestedEntity(e: Entity) {
|
||||
return this.isInterested(e.componentBits);
|
||||
}
|
||||
|
||||
public isInterested(componentBits: BitSet) {
|
||||
// 检查实体是否拥有该方面中定义的所有组件
|
||||
if (!this.allSet.isEmpty()) {
|
||||
for (let i = this.allSet.nextSetBit(0); i >= 0; i = this.allSet.nextSetBit(i + 1)) {
|
||||
if (!componentBits.get(i))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果我们仍然感兴趣,检查该实体是否拥有任何一个排除组件,如果有,那么系统就不感兴趣
|
||||
if (!this.exclusionSet.isEmpty() && this.exclusionSet.intersects(componentBits))
|
||||
return false;
|
||||
|
||||
// 如果我们仍然感兴趣,检查该实体是否拥有oneSet中的任何一个组件。如果是,系统就会感兴趣
|
||||
if (!this.oneSet.isEmpty() && !this.oneSet.intersects(componentBits))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public all(...types: any[]): Matcher {
|
||||
types.forEach(type => {
|
||||
this.allSet.set(ComponentTypeManager.getIndexFor(type));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public exclude(...types: any[]) {
|
||||
types.forEach(type => {
|
||||
this.exclusionSet.set(ComponentTypeManager.getIndexFor(type));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public one(...types: any[]) {
|
||||
types.forEach(type => {
|
||||
this.oneSet.set(ComponentTypeManager.getIndexFor(type));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回执行替换后的字符串
|
||||
* @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,51 +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;
|
||||
private static _lastTime = -1;
|
||||
|
||||
public static update(currentTime: number) {
|
||||
if (currentTime == -1)
|
||||
currentTime = Date.now();
|
||||
if (this._lastTime == -1)
|
||||
this._lastTime = currentTime;
|
||||
|
||||
let dt = currentTime - this._lastTime;
|
||||
if (dt > this.maxDeltaTime)
|
||||
dt = this.maxDeltaTime;
|
||||
this.totalTime += dt;
|
||||
this.deltaTime = dt * this.timeScale;
|
||||
this.unscaledDeltaTime = dt;
|
||||
this.timeSinceSceneLoad += dt;
|
||||
this.frameCount++;
|
||||
|
||||
this._lastTime = currentTime;
|
||||
}
|
||||
|
||||
public static sceneChanged() {
|
||||
this.timeSinceSceneLoad = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许在间隔检查。只应该使用高于delta的间隔值,否则它将始终返回true。
|
||||
* @param interval
|
||||
*/
|
||||
public static checkEvery(interval: number) {
|
||||
// 我们减去了delta,因为timeSinceSceneLoad已经包含了这个update ticks delta
|
||||
return this.timeSinceSceneLoad / interval > (this.timeSinceSceneLoad - this.deltaTime) / interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
class TimeUtils {
|
||||
/**
|
||||
* 计算月份ID
|
||||
* @param d 指定计算日期
|
||||
* @returns 月ID
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算日期ID
|
||||
* @param d 指定计算日期
|
||||
* @returns 日期ID
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算周ID
|
||||
* @param d 指定计算日期
|
||||
* @returns 周ID
|
||||
*/
|
||||
public static weekId(d: Date = null, first: boolean = true): number {
|
||||
d = d ? d : new Date();
|
||||
let c: Date = new Date();
|
||||
c.setTime(d.getTime());
|
||||
c.setDate(1);
|
||||
c.setMonth(0);//当年第一天
|
||||
|
||||
let 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);
|
||||
}
|
||||
let week: number = num / 7;
|
||||
let 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)) {
|
||||
c.setFullYear(c.getFullYear() + 1);
|
||||
c.setDate(1);
|
||||
c.setMonth(0);//当年第一天
|
||||
return this.weekId(c, false);
|
||||
}
|
||||
}
|
||||
let g: string = weekIdx > 9 ? "" : "0";
|
||||
let s: string = year + "00" + g + weekIdx;//加上00防止和月份ID冲突
|
||||
return parseInt(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算俩日期时间差,如果a比b小,返回负数
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本周一 凌晨时间
|
||||
*/
|
||||
public static getFirstDayOfWeek(d?: Date): Date {
|
||||
d = d ? d : new Date();
|
||||
let day = d.getDay() || 7;
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1 - day, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 2018-12-12
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 2018-12-12 12:12:12
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns s 2018-12-12 或者 2018-12-12 12:12:12
|
||||
*/
|
||||
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 是否显示小时
|
||||
* @return 返回一个以分隔符分割的时, 分, 秒
|
||||
*
|
||||
* 比如: time = 4351; secondToTime(time)返回字符串01:12:31;
|
||||
*/
|
||||
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 以指定分隔符分割的时间字符串
|
||||
* @param partition 分隔符
|
||||
* @return 毫秒数显示的字符串
|
||||
* @throws Error Exception
|
||||
*
|
||||
* 用法1 trace(MillisecondTransform.timeToMillisecond("00:60:00"))
|
||||
* 输出 3600000
|
||||
*
|
||||
*
|
||||
* 用法2 trace(MillisecondTransform.timeToMillisecond("00.60.00","."))
|
||||
* 输出 3600000
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 开辟一个新线程
|
||||
* 注意:它无法获得主线程中的上下文
|
||||
*/
|
||||
export class WorkerUtils {
|
||||
/** 正在执行的队列 */
|
||||
private static readonly pendingJobs = {};
|
||||
private static jobIdGen = 0;
|
||||
|
||||
/**
|
||||
* 创建一个worker
|
||||
* @param doFunc worker所能做的事情
|
||||
*/
|
||||
public static makeWorker(doFunc: Function) {
|
||||
const worker = new Worker(URL.createObjectURL(new Blob([`(${doFunc.toString()})()`])));
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
public static workerMessage(worker: Worker) {
|
||||
worker.onmessage = ({ data: { result, jobId } }) => {
|
||||
if (typeof this.pendingJobs[jobId] == 'function')
|
||||
this.pendingJobs[jobId](result);
|
||||
|
||||
delete this.pendingJobs[jobId];
|
||||
};
|
||||
|
||||
return (...message: any[]) => {
|
||||
return new Promise(resolve => {
|
||||
const jobId = this.jobIdGen++;
|
||||
this.pendingJobs[jobId] = resolve;
|
||||
worker.postMessage({ jobId, message });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 new Vector2(oneMinusT * oneMinusT).multiply(p0)
|
||||
.add(new Vector2(2 * oneMinusT * t).multiply(p1))
|
||||
.add(new Vector2(t * t).multiply(p2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 求解一个立方体曲率
|
||||
* @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);
|
||||
let oneMinusT = 1 - t;
|
||||
return new Vector2(oneMinusT * oneMinusT * oneMinusT).multiply(start)
|
||||
.add(new Vector2(3 * oneMinusT * oneMinusT * t).multiply(firstControlPoint))
|
||||
.add(new Vector2(3 * oneMinusT * t * t).multiply(secondControlPoint))
|
||||
.add(new Vector2(t * t * t).multiply(end));
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到二次贝塞尔函数的一阶导数
|
||||
* @param p0
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param t
|
||||
*/
|
||||
public static getFirstDerivative(p0: Vector2, p1: Vector2, p2: Vector2, t: number) {
|
||||
return new Vector2(2 * (1 - t)).multiply(Vector2.subtract(p1, p0))
|
||||
.add(new Vector2(2 * t).multiply(Vector2.subtract(p2, p1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到一个三次贝塞尔函数的一阶导数
|
||||
* @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 new Vector2(3 * oneMunusT * oneMunusT).multiply(Vector2.subtract(firstControlPoint, start))
|
||||
.add(new Vector2(6 * oneMunusT * t).multiply(Vector2.subtract(secondControlPoint, firstControlPoint)))
|
||||
.add(new Vector2(3 * t * t).multiply(Vector2.subtract(end, secondControlPoint)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归地细分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>();
|
||||
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.divide(Vector2.add(start, firstCtrlPoint), new Vector2(2));
|
||||
let pt23 = Vector2.divide(Vector2.add(firstCtrlPoint, secondCtrlPoint), new Vector2(2));
|
||||
let pt34 = Vector2.divide(Vector2.add(secondCtrlPoint, end), new Vector2(2));
|
||||
|
||||
// 计算新半直线的中点
|
||||
let pt123 = Vector2.divide(Vector2.add(pt12, pt23), new Vector2(2));
|
||||
let pt234 = Vector2.divide(Vector2.add(pt23, pt34), new Vector2(2));
|
||||
|
||||
// 最后再细分最后两个中点。如果我们满足我们的距离公差,这将是我们使用的最后一点。
|
||||
let pt1234 = Vector2.divide(Vector2.add(pt123, pt234), new Vector2(2));
|
||||
|
||||
// 试着用一条直线来近似整个三次曲线
|
||||
let deltaLine = Vector2.subtract(end, 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,114 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 提供了一系列立方贝塞尔点,并提供了帮助方法来访问贝塞尔
|
||||
*/
|
||||
export class BezierSpline {
|
||||
public _points: Vector2[] = [];
|
||||
public _curveCount: number = 0;
|
||||
|
||||
/**
|
||||
* 在这个过程中,t被修改为在曲线段的范围内。
|
||||
* @param t
|
||||
*/
|
||||
public pointIndexAtTime(t: Ref<number>): number {
|
||||
let i = 0;
|
||||
if (t.value >= 1) {
|
||||
t.value = 1;
|
||||
i = this._points.length - 4;
|
||||
} else {
|
||||
t.value = MathHelper.clamp01(t.value) * this._curveCount;
|
||||
i = ~~t;
|
||||
t.value -= i;
|
||||
i *= 3;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个控制点,考虑到这是否是一个共享点,如果是,则适当调整
|
||||
* @param index
|
||||
* @param point
|
||||
*/
|
||||
public setControlPoint(index: number, point: Vector2) {
|
||||
if (index % 3 == 0) {
|
||||
let delta = Vector2.subtract(point, this._points[index]);
|
||||
if (index > 0)
|
||||
this._points[index - 1].add(delta);
|
||||
|
||||
if (index + 1 < this._points.length)
|
||||
this._points[index + 1].add(delta);
|
||||
}
|
||||
|
||||
this._points[index] = point;
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到时间t的贝塞尔曲线上的点
|
||||
* @param t
|
||||
*/
|
||||
public getPointAtTime(t: number): Vector2{
|
||||
let i = this.pointIndexAtTime(new Ref(t));
|
||||
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 {
|
||||
let i = this.pointIndexAtTime(new Ref(t));
|
||||
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 Vector2.normalize(this.getVelocityAtTime(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 在贝塞尔曲线上添加一条曲线
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 帮助处理位掩码的实用程序类
|
||||
* 除了isFlagSet之外,所有方法都期望flag参数是一个非移位的标志
|
||||
* 允许您使用普通的(0、1、2、3等)来设置/取消您的标记
|
||||
*/
|
||||
export class Flags {
|
||||
/**
|
||||
* 检查位标志是否已在数值中设置
|
||||
* 检查期望标志是否已经移位
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static isFlagSet(self: number, flag: number): boolean {
|
||||
return (self & flag) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查位标志是否在数值中设置
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static isUnshiftedFlagSet(self: number, flag: number): boolean {
|
||||
flag = 1 << flag;
|
||||
return (self & flag) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数值标志位,移除所有已经设置的标志
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static setFlagExclusive(self: Ref<number>, flag: number) {
|
||||
self.value = 1 << flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标志位
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static setFlag(self: Ref<number>, flag: number) {
|
||||
self.value = (self.value | 1 << flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消标志位
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static unsetFlag(self: Ref<number>, flag: number) {
|
||||
flag = 1 << flag;
|
||||
self.value = (self.value & (~flag));
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转数值集合位
|
||||
* @param self
|
||||
*/
|
||||
public static invertFlags(self: Ref<number>) {
|
||||
self.value = ~self.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
module es {
|
||||
export class MathHelper {
|
||||
public static readonly Epsilon: number = 0.00001;
|
||||
public static readonly Rad2Deg = 57.29578;
|
||||
public static readonly Deg2Rad = 0.0174532924;
|
||||
/**
|
||||
* 表示pi除以2的值(1.57079637)
|
||||
*/
|
||||
public static readonly PiOver2 = Math.PI / 2;
|
||||
|
||||
/**
|
||||
* 将弧度转换成角度。
|
||||
* @param radians 用弧度表示的角
|
||||
*/
|
||||
public static toDegrees(radians: number) {
|
||||
return radians * 57.295779513082320876798154814105;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将角度转换为弧度
|
||||
* @param degrees
|
||||
*/
|
||||
public static toRadians(degrees: number) {
|
||||
return degrees * 0.017453292519943295769236907684886;
|
||||
}
|
||||
|
||||
/**
|
||||
* mapps值(在leftMin - leftMax范围内)到rightMin - rightMax范围内的值
|
||||
* @param value
|
||||
* @param leftMin
|
||||
* @param leftMax
|
||||
* @param rightMin
|
||||
* @param rightMax
|
||||
*/
|
||||
public static map(value: number, leftMin: number, leftMax: number, rightMin: number, rightMax: number) {
|
||||
return rightMin + (value - leftMin) * (rightMax - rightMin) / (leftMax - leftMin);
|
||||
}
|
||||
|
||||
public static lerp(value1: number, value2: number, amount: number) {
|
||||
return value1 + (value2 - value1) * amount;
|
||||
}
|
||||
|
||||
public static clamp(value: number, min: number, max: number) {
|
||||
if (value < min)
|
||||
return min;
|
||||
|
||||
if (value > max)
|
||||
return max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定圆心、半径和角度,得到圆周上的一个点。0度是3点钟。
|
||||
* @param circleCenter
|
||||
* @param radius
|
||||
* @param angleInDegrees
|
||||
*/
|
||||
public static pointOnCirlce(circleCenter: Vector2, radius: number, angleInDegrees: number) {
|
||||
let radians = MathHelper.toRadians(angleInDegrees);
|
||||
return new Vector2(Math.cos(radians) * radians + circleCenter.x,
|
||||
Math.sin(radians) * radians + circleCenter.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果值为偶数,返回true
|
||||
* @param value
|
||||
*/
|
||||
public static isEven(value: number) {
|
||||
return value % 2 == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数值限定在0-1之间
|
||||
* @param value
|
||||
*/
|
||||
public static clamp01(value: number) {
|
||||
if (value < 0)
|
||||
return 0;
|
||||
|
||||
if (value > 1)
|
||||
return 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static angleBetweenVectors(from: Vector2, to: Vector2) {
|
||||
return Math.atan2(to.y - from.y, to.x - from.x);
|
||||
}
|
||||
|
||||
public static angleToVector(angleRadians: number, length: number) {
|
||||
return new Vector2(Math.cos(angleRadians) * length, Math.sin(angleRadians) * length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加t并确保它总是大于或等于0并且小于长度
|
||||
* @param t
|
||||
* @param length
|
||||
*/
|
||||
public static incrementWithWrap(t: number, length: number) {
|
||||
t++;
|
||||
if (t == length)
|
||||
return 0;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以roundToNearest为步长,将值舍入到最接近的数字。例如:在125中找到127到最近的5个结果
|
||||
* @param value
|
||||
* @param roundToNearest
|
||||
*/
|
||||
public static roundToNearest(value: number, roundToNearest: number) {
|
||||
return Math.round(value / roundToNearest) * roundToNearest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查传递的值是否在某个阈值之下。对于小规模、精确的比较很有用
|
||||
* @param value
|
||||
* @param ep
|
||||
*/
|
||||
public static withinEpsilon(value: number, ep: number = this.Epsilon) {
|
||||
return Math.abs(value) < ep;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由上移量向上移。start可以小于或大于end。例如:开始是2,结束是10,移位是4,结果是6
|
||||
* @param start
|
||||
* @param end
|
||||
* @param shift
|
||||
*/
|
||||
public static approach(start: number, end: number, shift: number): number {
|
||||
if (start < end)
|
||||
return Math.min(start + shift, end);
|
||||
|
||||
return Math.max(start - shift, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个给定角之间的最短差值(度数)
|
||||
* @param current
|
||||
* @param target
|
||||
*/
|
||||
public static deltaAngle(current: number, target: number) {
|
||||
let num = this.repeat(target - current, 360);
|
||||
if (num > 180)
|
||||
num -= 360;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环t,使其永远不大于长度,永远不小于0
|
||||
* @param t
|
||||
* @param length
|
||||
*/
|
||||
public static repeat(t: number, length: number) {
|
||||
return t - Math.floor(t / length) * length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 代表右手4x4浮点矩阵,可以存储平移、比例和旋转信息
|
||||
*/
|
||||
export class Matrix {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 为自定义的正交视图创建一个新的投影矩阵
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的矩阵,其中包含两个矩阵的乘法。
|
||||
* @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,308 +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(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 储存在该矩阵中的位置
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个矩阵
|
||||
* @param m11
|
||||
* @param m12
|
||||
* @param m21
|
||||
* @param m22
|
||||
* @param m31
|
||||
* @param m32
|
||||
*/
|
||||
constructor(m11: number, m12: number, m21: number, m22: number, m31: number, m32: number){
|
||||
this.m11 = m11;
|
||||
this.m12 = m12;
|
||||
|
||||
this.m21 = m21;
|
||||
this.m22 = m22;
|
||||
|
||||
this.m31 = m31;
|
||||
this.m32 = m32;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的围绕Z轴的旋转矩阵2D
|
||||
* @param radians
|
||||
*/
|
||||
public static createRotation(radians: number){
|
||||
let result: Matrix2D = this.identity;
|
||||
|
||||
let val1 = Math.cos(radians);
|
||||
let val2 = Math.sin(radians);
|
||||
|
||||
result.m11 = val1;
|
||||
result.m12 = val2;
|
||||
result.m21 = -val2;
|
||||
result.m22 = val1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的缩放矩阵2D
|
||||
* @param xScale
|
||||
* @param yScale
|
||||
*/
|
||||
public static createScale(xScale: number, yScale: number){
|
||||
let result: Matrix2D = this.identity;
|
||||
result.m11 = xScale;
|
||||
result.m12 = 0;
|
||||
|
||||
result.m21 = 0;
|
||||
result.m22 = yScale;
|
||||
|
||||
result.m31 = 0;
|
||||
result.m32 = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的平移矩阵2D
|
||||
* @param xPosition
|
||||
* @param yPosition
|
||||
*/
|
||||
public static createTranslation(xPosition: number, yPosition: number) {
|
||||
let result: Matrix2D = this.identity;
|
||||
result.m11 = 1;
|
||||
result.m12 = 0;
|
||||
|
||||
result.m21 = 0;
|
||||
result.m22 = 1;
|
||||
|
||||
result.m31 = xPosition;
|
||||
result.m32 = yPosition;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 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 = Matrix2D.createTranslation(x, y);
|
||||
return MatrixHelper.mutiply(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,99 +0,0 @@
|
||||
module es {
|
||||
export class MatrixHelper {
|
||||
/**
|
||||
* 创建一个新的Matrix2D,其中包含两个矩阵的和
|
||||
* @param matrix1
|
||||
* @param matrix2
|
||||
*/
|
||||
public static add(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
|
||||
let result = Matrix2D.identity;
|
||||
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;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个Matrix2D的元素除以另一个矩阵的元素
|
||||
* @param matrix1
|
||||
* @param matrix2
|
||||
*/
|
||||
public static divide(matrix1: Matrix2D, matrix2: Matrix2D) {
|
||||
let result = Matrix2D.identity;
|
||||
|
||||
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;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Matrix2D,包含两个矩阵的乘法
|
||||
* @param matrix1
|
||||
* @param matrix2
|
||||
*/
|
||||
public static mutiply(matrix1: Matrix2D, matrix2: Matrix2D | number) {
|
||||
let result = Matrix2D.identity;
|
||||
if (matrix2 instanceof Matrix2D) {
|
||||
let m11 = (matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21);
|
||||
let m12 = (matrix2.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22);
|
||||
|
||||
let m21 = (matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21);
|
||||
let m22 = (matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22);
|
||||
|
||||
let m31 = (matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21) + matrix2.m31;
|
||||
let 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;
|
||||
} else if (typeof matrix2 == "number"){
|
||||
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;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Matrix2D,包含一个矩阵与另一个矩阵的减法。
|
||||
* @param matrix1
|
||||
* @param matrix2
|
||||
*/
|
||||
public static subtract(matrix1: Matrix2D, matrix2: Matrix2D) {
|
||||
let result = Matrix2D.identity;
|
||||
|
||||
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;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,559 +0,0 @@
|
||||
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;
|
||||
public _transformMat: 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, distance: Ref<number>): boolean {
|
||||
distance.value = 0;
|
||||
let maxValue = Number.MAX_VALUE;
|
||||
|
||||
if (Math.abs(ray.direction.x) < 1E-06) {
|
||||
if ((ray.start.x < this.x) || (ray.start.x > this.x + this.width))
|
||||
return false;
|
||||
} else {
|
||||
let 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) {
|
||||
let num14 = num8;
|
||||
num8 = num7;
|
||||
num7 = num14;
|
||||
}
|
||||
|
||||
distance.value = Math.max(num8, distance.value);
|
||||
maxValue = Math.min(num7, maxValue);
|
||||
if (distance.value > maxValue)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Math.abs(ray.direction.y) < 1E-06) {
|
||||
if ((ray.start.y < this.y) || (ray.start.y > this.y + this.height))
|
||||
return false;
|
||||
} else {
|
||||
let 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) {
|
||||
let num13 = num6;
|
||||
num6 = num5;
|
||||
num5 = num13;
|
||||
}
|
||||
|
||||
distance.value = Math.max(num6, distance.value);
|
||||
maxValue = Math.max(num5, maxValue);
|
||||
if (distance.value > maxValue)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所提供的矩形是否在此矩形的边界内
|
||||
* @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 = new Vector2();
|
||||
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: Vector2): Vector2 {
|
||||
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
|
||||
let res = new Vector2();
|
||||
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.y = -1;
|
||||
} else if (min == db) {
|
||||
res.y = this.bottom;
|
||||
edgeNormal.y = 1;
|
||||
} else if (min == dl) {
|
||||
res.x = this.left;
|
||||
edgeNormal.x = -1;
|
||||
} else {
|
||||
res.x = this.right;
|
||||
edgeNormal.x = 1;
|
||||
}
|
||||
} else {
|
||||
if (res.x == this.left) edgeNormal.x = -1;
|
||||
if (res.x == this.right) edgeNormal.x = 1;
|
||||
if (res.y == this.top) edgeNormal.y = -1;
|
||||
if (res.y == this.bottom) edgeNormal.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个完全包含两个其他矩形的新矩形
|
||||
* @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 = parentPosition.x + position.x - origin.x * scale.x;
|
||||
this.y = parentPosition.y + position.y - origin.y * scale.y;
|
||||
this.width = width * scale.x;
|
||||
this.height = height * scale.y;
|
||||
} else {
|
||||
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
|
||||
let worldPosX = parentPosition.x + position.x;
|
||||
let worldPosY = parentPosition.y + position.y;
|
||||
|
||||
// 考虑到原点,将参考点设置为世界参考
|
||||
this._transformMat = Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y);
|
||||
this._tempMat = Matrix2D.createScale(scale.x, scale.y);
|
||||
this._transformMat = this._transformMat.multiply(this._tempMat);
|
||||
this._tempMat = Matrix2D.createRotation(rotation);
|
||||
this._transformMat = this._transformMat.multiply(this._tempMat);
|
||||
this._tempMat = Matrix2D.createTranslation(worldPosX, worldPosY);
|
||||
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.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
|
||||
let maxX = Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
|
||||
let minY = Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
|
||||
let maxY = Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
|
||||
|
||||
this.location = new Vector2(minX, minY);
|
||||
this.width = maxX - minX;
|
||||
this.height = 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 (this.x ^ this.y ^ this.width ^ this.height);
|
||||
}
|
||||
|
||||
public clone(): Rectangle {
|
||||
return new Rectangle(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 它存储值,直到累计的总数大于1。一旦超过1,该值将在调用update时添加到amount中
|
||||
* 一般用法如下:
|
||||
*
|
||||
* let deltaMove = this.velocity * es.Time.deltaTime;
|
||||
* deltaMove.x = this._x.update(deltaMove.x);
|
||||
* deltaMove.y = this._y.update(deltaMove.y);
|
||||
*/
|
||||
export class SubpixelFloat {
|
||||
public remainder: number = 0;
|
||||
|
||||
/**
|
||||
* 以amount递增余数,将值截断,存储新的余数并将amount设置为当前值
|
||||
* @param amount
|
||||
*/
|
||||
public update(amount: number){
|
||||
this.remainder += amount;
|
||||
let motion = Math.floor(Math.trunc(this.remainder));
|
||||
this.remainder -= motion;
|
||||
amount = motion;
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将余数重置为0
|
||||
*/
|
||||
public reset(){
|
||||
this.remainder = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
module es {
|
||||
export class SubpixelVector2 {
|
||||
public _x: SubpixelFloat = new SubpixelFloat();
|
||||
public _y: SubpixelFloat = new SubpixelFloat();
|
||||
|
||||
/**
|
||||
* 以数量递增s/y余数,将值截断为整数,存储新的余数并将amount设置为当前值
|
||||
* @param amount
|
||||
*/
|
||||
public update(amount: Vector2) {
|
||||
amount.x = this._x.update(amount.x);
|
||||
amount.y = this._y.update(amount.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将余数重置为0
|
||||
*/
|
||||
public reset(){
|
||||
this._x.reset();
|
||||
this._y.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
module es {
|
||||
/** 2d 向量 */
|
||||
export class Vector2 implements IEquatable<Vector2> {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
|
||||
/**
|
||||
* 从两个值构造一个带有X和Y的二维向量。
|
||||
* @param x 二维空间中的x坐标
|
||||
* @param y 二维空间的y坐标
|
||||
*/
|
||||
constructor(x?: number, y?: number) {
|
||||
this.x = x ? x : 0;
|
||||
this.y = y != undefined ? y : this.x;
|
||||
}
|
||||
|
||||
public static get zero() {
|
||||
return new Vector2(0, 0);
|
||||
}
|
||||
|
||||
public static get one() {
|
||||
return new Vector2(1, 1);
|
||||
}
|
||||
|
||||
public static get unitX() {
|
||||
return new Vector2(1, 0);
|
||||
}
|
||||
|
||||
public static get unitY() {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static multiply(value1: Vector2, value2: Vector2) {
|
||||
let result: Vector2 = new Vector2(0, 0);
|
||||
result.x = value1.x * value2.x;
|
||||
result.y = value1.y * value2.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static subtract(value1: Vector2, value2: Vector2) {
|
||||
let result: Vector2 = new Vector2(0, 0);
|
||||
result.x = value1.x - value2.x;
|
||||
result.y = value1.y - value2.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2
|
||||
* 它包含来自另一个向量的标准化值。
|
||||
* @param value
|
||||
*/
|
||||
public static normalize(value: Vector2) {
|
||||
let nValue = new Vector2(value.x, value.y);
|
||||
let val = 1 / Math.sqrt((nValue.x * nValue.x) + (nValue.y * nValue.y));
|
||||
nValue.x *= val;
|
||||
nValue.y *= val;
|
||||
return nValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量的点积
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static dot(value1: Vector2, value2: Vector2): number {
|
||||
return (value1.x * value2.x) + (value1.y * value2.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量之间距离的平方
|
||||
* @param value1
|
||||
* @param value2
|
||||
*/
|
||||
public static distanceSquared(value1: Vector2, value2: Vector2) {
|
||||
let v1 = value1.x - value2.x, v2 = value1.y - value2.y;
|
||||
return (v1 * v1) + (v2 * v2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的值限制在一个范围内
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的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,该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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量之间的距离
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @returns 两个向量之间的距离
|
||||
*/
|
||||
public static distance(value1: Vector2, value2: Vector2): number {
|
||||
let v1 = value1.x - value2.x, v2 = value1.y - value2.y;
|
||||
return Math.sqrt((v1 * v1) + (v2 * v2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量之间的角度,单位是度数
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
public static angle(from: Vector2, to: Vector2): number{
|
||||
from = Vector2.normalize(from);
|
||||
to = Vector2.normalize(to);
|
||||
return Math.acos(MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * MathHelper.Rad2Deg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个包含指定向量反转的新Vector2
|
||||
* @param value
|
||||
* @returns 矢量反演的结果
|
||||
*/
|
||||
public static negate(value: Vector2) {
|
||||
value.x = -value.x;
|
||||
value.y = -value.y;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public add(value: Vector2): Vector2 {
|
||||
this.x += value.x;
|
||||
this.y += value.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public divide(value: Vector2): Vector2 {
|
||||
this.x /= value.x;
|
||||
this.y /= value.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public multiply(value: Vector2): Vector2 {
|
||||
this.x *= value.x;
|
||||
this.y *= value.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前Vector2减去一个Vector2
|
||||
* @param value 要减去的Vector2
|
||||
* @returns 当前Vector2
|
||||
*/
|
||||
public subtract(value: Vector2) {
|
||||
this.x -= value.x;
|
||||
this.y -= value.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将这个Vector2变成一个方向相同的单位向量
|
||||
*/
|
||||
public normalize() {
|
||||
let val = 1 / Math.sqrt((this.x * this.x) + (this.y * this.y));
|
||||
this.x *= val;
|
||||
this.y *= val;
|
||||
}
|
||||
|
||||
/** 返回它的长度 */
|
||||
public length() {
|
||||
return Math.sqrt((this.x * this.x) + (this.y * this.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该Vector2的平方长度
|
||||
* @returns 这个Vector2的平方长度
|
||||
*/
|
||||
public lengthSquared(): number {
|
||||
return (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 = Vector2.subtract(left, this);
|
||||
let two = Vector2.subtract(right, this);
|
||||
return Vector2Ext.angle(one, two);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较当前实例是否等于指定的对象
|
||||
* @param other 要比较的对象
|
||||
* @returns 如果实例相同true 否则false
|
||||
*/
|
||||
public equals(other: Vector2 | object): boolean {
|
||||
if (other instanceof Vector2){
|
||||
return other.x == this.x && other.y == this.y;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public clone(): Vector2 {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 移动器使用的帮助器类,用于管理触发器碰撞器交互并调用itriggerlistener
|
||||
*/
|
||||
export class ColliderTriggerHelper {
|
||||
private _entity: Entity;
|
||||
/** 存储当前帧中发生的所有活动交点对 */
|
||||
private _activeTriggerIntersections: HashSet<Pair<Collider>> = new HashSet<Pair<Collider>>();
|
||||
/** 存储前一帧的交点对,这样我们就可以在移动这一帧后检测到退出 */
|
||||
private _previousTriggerIntersections: HashSet<Pair<Collider>> = new HashSet<Pair<Collider>>();
|
||||
private _tempTriggerList: ITriggerListener[] = [];
|
||||
|
||||
constructor(entity: Entity) {
|
||||
this._entity = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* update应该在实体被移动后被调用,它将处理任何与Colllider重叠的ITriggerListeners。
|
||||
* 它将处理任何与Collider重叠的ITriggerListeners。
|
||||
*/
|
||||
public update() {
|
||||
// 对所有实体.colliders进行重叠检查,这些实体.colliders是触发器,与所有宽相碰撞器,无论是否触发器。
|
||||
// 任何重叠都会导致触发事件
|
||||
let colliders = this._entity.getComponents(Collider);
|
||||
for (let i = 0; i < colliders.length; i++) {
|
||||
let collider = colliders[i];
|
||||
|
||||
let neighbors = Physics.boxcastBroadphase(collider.bounds, collider.collidesWithLayers);
|
||||
for (let j = 0; j < neighbors.size; j++) {
|
||||
let neighbor = neighbors[j];
|
||||
// 我们至少需要一个碰撞器作为触发器
|
||||
if (!collider.isTrigger && !neighbor.isTrigger)
|
||||
continue;
|
||||
|
||||
if (collider.overlaps(neighbor)) {
|
||||
let pair = new Pair<Collider>(collider, neighbor);
|
||||
|
||||
// 如果我们的某一个集合中已经有了这个对子(前一个或当前的触发交叉点),就不要调用输入事件了
|
||||
let shouldReportTriggerEvent = !this._activeTriggerIntersections.contains(pair) &&
|
||||
!this._previousTriggerIntersections.contains(pair);
|
||||
|
||||
if (shouldReportTriggerEvent)
|
||||
this.notifyTriggerListeners(pair, true);
|
||||
|
||||
this._activeTriggerIntersections.add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListPool.free(colliders);
|
||||
|
||||
this.checkForExitedColliders();
|
||||
}
|
||||
|
||||
private checkForExitedColliders() {
|
||||
// 删除所有与此帧交互的触发器,留下我们退出的触发器
|
||||
this._previousTriggerIntersections.exceptWith(this._activeTriggerIntersections.toArray());
|
||||
|
||||
for (let i = 0; i < this._previousTriggerIntersections.getCount(); i++) {
|
||||
this.notifyTriggerListeners(this._previousTriggerIntersections[i], false)
|
||||
}
|
||||
|
||||
this._previousTriggerIntersections.clear();
|
||||
|
||||
// 添加所有当前激活的触发器
|
||||
this._previousTriggerIntersections.unionWith(this._activeTriggerIntersections.toArray());
|
||||
|
||||
// 清空活动集,为下一帧做准备
|
||||
this._activeTriggerIntersections.clear();
|
||||
}
|
||||
|
||||
private notifyTriggerListeners(collisionPair: Pair<Collider>, isEntering: boolean) {
|
||||
TriggerListenerHelper.getITriggerListener(collisionPair.first.entity, this._tempTriggerList);
|
||||
for (let i = 0; i < this._tempTriggerList.length; i++) {
|
||||
if (isEntering) {
|
||||
this._tempTriggerList[i].onTriggerEnter(collisionPair.second, collisionPair.first);
|
||||
} else {
|
||||
this._tempTriggerList[i].onTriggerExit(collisionPair.second, collisionPair.first);
|
||||
}
|
||||
|
||||
this._tempTriggerList.length = 0;
|
||||
|
||||
if (collisionPair.second.entity) {
|
||||
TriggerListenerHelper.getITriggerListener(collisionPair.second.entity, this._tempTriggerList);
|
||||
for (let i = 0; i < this._tempTriggerList.length; i++) {
|
||||
if (isEntering) {
|
||||
this._tempTriggerList[i].onTriggerEnter(collisionPair.first, collisionPair.second);
|
||||
} else {
|
||||
this._tempTriggerList[i].onTriggerExit(collisionPair.first, collisionPair.second);
|
||||
}
|
||||
}
|
||||
|
||||
this._tempTriggerList.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +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 {
|
||||
let b = Vector2.subtract(a2, a1);
|
||||
let d = Vector2.subtract(b2, b1);
|
||||
let bDotDPerp = b.x * d.y - b.y * d.x;
|
||||
|
||||
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
|
||||
if (bDotDPerp == 0)
|
||||
return false;
|
||||
|
||||
let c = Vector2.subtract(b1, a1);
|
||||
let t = (c.x * d.y - c.y * d.x) / bDotDPerp;
|
||||
if (t < 0 || t > 1)
|
||||
return false;
|
||||
|
||||
let 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 = new Vector2()): boolean {
|
||||
intersection.x = 0;
|
||||
intersection.y = 0;
|
||||
|
||||
let b = Vector2.subtract(a2, a1);
|
||||
let d = Vector2.subtract(b2, b1);
|
||||
let bDotDPerp = b.x * d.y - b.y * d.x;
|
||||
|
||||
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
|
||||
if (bDotDPerp == 0)
|
||||
return false;
|
||||
|
||||
let c = Vector2.subtract(b1, a1);
|
||||
let t = (c.x * d.y - c.y * d.x) / bDotDPerp;
|
||||
if (t < 0 || t > 1)
|
||||
return false;
|
||||
|
||||
let u = (c.x * b.y - c.y * b.x) / bDotDPerp;
|
||||
if (u < 0 || u > 1)
|
||||
return false;
|
||||
|
||||
let temp = Vector2.add(a1, new Vector2(t * b.x, t * b.y));
|
||||
intersection.x = temp.x;
|
||||
intersection.y = temp.y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static closestPointOnLine(lineA: Vector2, lineB: Vector2, closestTo: Vector2) {
|
||||
let v = Vector2.subtract(lineB, lineA);
|
||||
let w = Vector2.subtract(closestTo, lineA);
|
||||
let t = Vector2.dot(w, v) / Vector2.dot(v, v);
|
||||
t = MathHelper.clamp(t, 0, 1);
|
||||
|
||||
return Vector2.add(lineA, new Vector2(v.x * t, v.y * t));
|
||||
}
|
||||
|
||||
public static circleToCircle(circleCenter1: Vector2, circleRadius1: number, circleCenter2: Vector2, circleRadius2: number): boolean {
|
||||
return Vector2.distanceSquared(circleCenter1, circleCenter2) < (circleRadius1 + circleRadius2) * (circleRadius1 + circleRadius2);
|
||||
}
|
||||
|
||||
public static circleToLine(circleCenter: Vector2, radius: number, lineFrom: Vector2, lineTo: Vector2): boolean {
|
||||
return Vector2.distanceSquared(circleCenter, this.closestPointOnLine(lineFrom, lineTo, circleCenter)) < radius * radius;
|
||||
}
|
||||
|
||||
public static circleToPoint(circleCenter: Vector2, radius: number, point: Vector2): boolean {
|
||||
return Vector2.distanceSquared(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;
|
||||
let 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;
|
||||
}
|
||||
|
||||
public static rectToLine(rect: Rectangle, lineFrom: Vector2, lineTo: Vector2) {
|
||||
let fromSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineFrom);
|
||||
let toSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineTo);
|
||||
|
||||
if (fromSector == PointSectors.center || toSector == PointSectors.center) {
|
||||
return true;
|
||||
} else if ((fromSector & toSector) != 0) {
|
||||
return false;
|
||||
} else {
|
||||
let both = fromSector | toSector;
|
||||
// 线对边进行检查
|
||||
let edgeFrom: Vector2;
|
||||
let edgeTo: Vector2;
|
||||
|
||||
if ((both & PointSectors.top) != 0) {
|
||||
edgeFrom = new Vector2(rect.x, rect.y);
|
||||
edgeTo = new Vector2(rect.x + rect.width, rect.y);
|
||||
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((both & PointSectors.bottom) != 0) {
|
||||
edgeFrom = new Vector2(rect.x, rect.y + rect.height);
|
||||
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
|
||||
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((both & PointSectors.left) != 0) {
|
||||
edgeFrom = new Vector2(rect.x, rect.y);
|
||||
edgeTo = new Vector2(rect.x, rect.y + rect.height);
|
||||
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((both & 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.lineToLine(edgeFrom, edgeTo, 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,194 +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;
|
||||
/**
|
||||
* 我们保留它以避免在每次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, randius: number, results: any[], layerMask = -1) {
|
||||
if (results.length == 0) {
|
||||
console.warn("传入了一个空的结果数组。不会返回任何结果");
|
||||
return;
|
||||
}
|
||||
|
||||
return this._spatialHash.overlapCircle(center, randius, 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.clone();
|
||||
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.clone();
|
||||
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 = Physics.allLayers): RaycastHit{
|
||||
this._hitArray[0].reset();
|
||||
this.linecastAll(start, end, this._hitArray, layerMask);
|
||||
return this._hitArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过空间散列强制执行一行,并用该行命中的任何碰撞器填充hits数组
|
||||
* @param start
|
||||
* @param end
|
||||
* @param hits
|
||||
* @param layerMask
|
||||
*/
|
||||
public static linecastAll(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number = Physics.allLayers){
|
||||
if (hits.length == 0){
|
||||
console.warn("传入了一个空的hits数组。没有点击会被返回");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this._spatialHash.linecast(start, end, hits, layerMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有对撞机落在一个矩形区域中
|
||||
* @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,16 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 不是真正的射线(射线只有开始和方向),作为一条线和射线。
|
||||
*/
|
||||
export class Ray2D {
|
||||
public start: Vector2;
|
||||
public end: Vector2;
|
||||
public direction: Vector2;
|
||||
|
||||
constructor(position: Vector2, end: Vector2){
|
||||
this.start = position;
|
||||
this.end = end;
|
||||
this.direction = Vector2.subtract(this.end, this.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +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 setValues(collider: Collider, fraction: number, distance: number, point: Vector2){
|
||||
this.collider = collider;
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
}
|
||||
|
||||
public setValuesNonCollider(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 toString(){
|
||||
return `[RaycastHit] fraction: ${this.fraction}, distance: ${this.distance}, normal: ${this.normal}, centroid: ${this.centroid}, point: ${this.point}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +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 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: CollisionResult): boolean {
|
||||
// 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测
|
||||
if (other instanceof Box && (other as Box).isUnrotated) {
|
||||
return ShapeCollisions.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: es.Vector2, result: es.CollisionResult): boolean {
|
||||
if (this.isUnrotated)
|
||||
return ShapeCollisions.pointToBox(point, this, result);
|
||||
|
||||
return super.pointCollidesWithShape(point, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user