升级项目框架,移除大部分无用的物理和tween系统

This commit is contained in:
YHH
2025-06-07 20:32:43 +08:00
parent 00cc3a11c6
commit 2e38284d6e
181 changed files with 11964 additions and 43305 deletions
+1
View File
@@ -1,4 +1,5 @@
/source/node_modules
/source/bin
/demo/bin-debug
/demo/bin-release
/.idea
+269 -91
View File
@@ -1,117 +1,295 @@
ecs-framework 的目标是成为功能强大的框架。它为您构建游戏提供了坚实的基础。它包括的许多功能包括:
# ECS Framework
- 完整的场景/实体/组件系统
- SpatialHash是一种空间散列数据结构,用于加速2D物理引擎的碰撞检测,它能够将物体分割为多个小区域并快速查询每个区域内包含的物体,从而大幅度提高碰撞检测的效率。
- AABB,圆和多边形碰撞/触发检测
- 高效的协程,可在多个帧或动画定时中分解大型任务(Core.startCoroutine
- 通过Astar和广度优先搜索提供寻路支持,以查找图块地图或您自己的自定义格式 ( 参见 https://github.com/esengine/ecs-astar )
- tween系统。任何number / Vector / 矩形/字段或属性都可以tween。
- 针对核心事件的优化的事件发射器(发射器类),您也可以将其添加到自己的任何类中
- 延迟和重复任务的调度程序(核心调度方法)
[![npm version](https://badge.fury.io/js/%40esengine%2Fecs-framework.svg)](https://badge.fury.io/js/%40esengine%2Fecs-framework)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
# 快速开始
- [运行框架](docs/getting_start.md)
- [创建实体与组件](docs/create_entity_component.md)
- [创建系统](docs/system.md)
- [全局时间Time](docs/time.md)
- [协程Coroutine](docs/coroutine.md)
- [Physics](docs/physics.md)
- [Emitter](docs/emitter.md)
- [Collision](docs/collision.md)
一个轻量级的 TypeScript ECSEntity-Component-System)框架,专为小游戏开发设计,适用于 Laya、Cocos 等游戏引擎。
## 交流群
点击链接加入群聊【ecs游戏框架交流】:https://jq.qq.com/?_wv=1027&k=29w1Nud6
## ✨ 特性
- 🚀 **轻量级 ECS 架构** - 基于实体组件系统,提供清晰的代码结构
- 📡 **事件系统** - 内置 Emitter 事件发射器,支持类型安全的事件管理
-**定时器系统** - 完整的定时器管理,支持延迟和重复任务
- 🔍 **查询系统** - 基于位掩码的高性能实体查询
- 🛠️ **性能监控** - 内置性能监控工具,帮助优化游戏性能
- 🎯 **对象池** - 内存管理优化,减少垃圾回收压力
- 📊 **数学库** - 完整的 2D 数学运算支持
## Scene/Entity/Component
Scene表示游戏场景,是所有实体和组件的容器;Entity表示游戏场景中的实体,是组件的容器;Component表示游戏实体中的组件,包含实体的具体行为逻辑。
## 📦 安装
### Scene
这是一个ECSEntity-Component-System)框架的场景类,用于管理游戏中的实体和处理器。它具有以下特点:
```bash
npm install @esengine/ecs-framework
```
- 维护了一个实体列表和一个实体处理器列表,以便在游戏中轻松添加、更新和删除实体。
- 具有实体、组件和处理器的基本结构,可帮助您组织代码并实现分离的关注点。
- 可以添加和删除场景组件,这是一个特殊类型的组件,可用于实现场景范围的逻辑。
- 通过使用实体处理器,可以轻松地处理实体的更新和渲染。
- 可以搜索场景中的实体、组件和处理器,并根据需要添加或删除它们。
- 可以为实体分配唯一的标识符,以便在处理实体时轻松地跟踪它们。
## 🚀 快速开始
### Entity
Entity 是ECS中的一个基础概念,它代表了游戏中的实体。每个 Entity 都有唯一的 ID 和名称,可以在一个 Scene 中被创建、添加、删除和管理。
### 1. 初始化框架
在一个 Entity 中,可以添加多个 Component 来实现不同的功能。例如,Transform Component 用来表示实体的位置、旋转和缩放等变换属性,其他自定义的 Component 则可以实现各种具体的游戏逻辑。
```typescript
import { Core, CoreEvents } from '@esengine/ecs-framework';
在 Entity 中,可以通过以下方法来管理 Component:
// 创建 Core 实例
const core = Core.create(true); // true 表示开启调试模式
- createComponent(componentType: new (...args) => T): T 用来创建并返回一个指定类型的 Component 实例。
- addComponent<T extends Component>(component: T): T 用来添加一个已有的 Component 实例。
- getComponent<T extends Component>(type: new (...args) => T): T 用来获取指定类型的 Component 实例。
- getComponentInScene<T extends Component>(type: new (...args) => T): T 用来获取指定类型的 Component 实例,但会在整个场景中搜索。
- tryGetComponent<T extends Component>(type: new (...args) => T, outComponent: Ref<T>): boolean 用来尝试获取指定类型的 Component 实例,并将结果存储在传入的 outComponent 参数中。
- hasComponent<T extends Component>(type: new (...args) => T): boolean 用来检查 Entity 是否有指定类型的 Component 实例。
- getOrCreateComponent<T extends Component>(type: new (...args) => T): T 如果 Entity 没有指定类型的 Component 实例,将会创建一个并返回。
此外,还可以通过 removeComponent() 方法来移除一个指定的 Component 实例,或者 removeAllComponents() 方法来移除 Entity 中的所有 Component 实例。
// 在游戏循环中更新框架
function gameLoop() {
// 发送帧更新事件
Core.emitter.emit(CoreEvents.frameUpdated);
}
```
最后,在 Entity 中还可以通过 Tween 类来实现各种动画效果,例如 tweenPositionTo()、tweenScaleTo() 和 tweenRotationDegreesTo() 等方法。
### 2. 创建场景
以下是Entity类中一些关键和重要的属性:
```typescript
import { Scene, Vector2, EntitySystem } from '@esengine/ecs-framework';
- scene:实体所属的场景对象。
- name:实体的名称。
- id:实体的唯一标识符。
- transform:实体的变换组件。
- components:实体的组件列表。
- updateInterval:实体更新间隔,用于控制实体的更新频率。
- componentBits:用于标记实体拥有哪些组件。
这些属性是Entity类中非常重要的,它们可以用来操作和控制实体的行为和状态。其中,transform和components属性是实体最为关键的组成部分,前者用于操作实体的位置、旋转和缩放等变换信息,后者用于添加、删除、查询和更新实体的组件信息。通过这些属性,我们可以非常方便地构建出自己的游戏对象,并实现一些基本的功能,如移动、碰撞、动画、音效等。
class GameScene extends Scene {
public initialize() {
// 创建玩家实体
const player = this.createEntity("Player");
### Component
这是一个用于构建实体组件系统的基本组件类,所有其他组件都应该从这个抽象类派生。每个组件都可以与一个实体相关联,可以通过实体来访问其它组件。
// 设置位置
player.position = new Vector2(100, 100);
重要属性:
// 添加自定义组件
const movement = player.addComponent(new MovementComponent());
- id: 组件的唯一标识符。
- entity: 附加此组件的实体。
重要方法:
// 添加系统
this.addEntityProcessor(new MovementSystem());
}
- addComponent<T extends Component>(component: T): T:将指定的组件添加到此组件所在的实体中。
- getComponent<T extends Component>(type: new (...args: any[]) => T): T:获取指定类型的组件。
- getComponents(typeName: any, componentList?: any[]): any[]:获取指定类型的组件数组。
- hasComponent(type: new (...args: any[]) => Component): boolean:判断实体是否包含指定类型的组件。
- removeComponent(component?: Component): void:从此组件所在的实体中删除指定的组件,如果未指定组件,则删除此组件本身。
此外,组件还具有一些生命周期方法,例如 initialize()、onAddedToEntity()、onRemovedFromEntity()、onEntityTransformChanged()、onEnabled()、onDisabled() 等,这些方法可以根据需要在派生类中进行重写,以便在实体上添加或移除组件时执行相应的操作。
public onStart() {
console.log("游戏场景已启动");
}
}
// 设置当前场景
Core.scene = new GameScene();
```
## Debug
这是一个静态类 Debug,它包含了一些方法用于打印调试信息。其中包含的方法如下:
### 3. 创建组件
- warnIf(condition: boolean, format: string, ...args: any[]): 如果 condition 为 true,则打印警告信息。
- warn(format: string, ...args: any[]): 打印警告信息。
- error(format: string, ...args: any[]): 打印错误信息。
- log(type: LogType, format: string, ...args: any[]): 打印指定类型的信息,可选的类型有 error、warn、log、info 和 trace。
该类的优点是它提供了一种统一的调试信息输出方式,可以帮助开发者更方便地输出调试信息,以便在调试时更快地定位问题。缺点是它的功能比较单一,只能输出调试信息,不能对调试信息进行更加复杂的处理。
```typescript
import { Component, Vector2, Time } from '@esengine/ecs-framework';
## Flags
这是一个静态工具类 Flags,提供了一些位标志操作的方法:
class MovementComponent extends Component {
public speed: number = 100;
public direction: Vector2 = Vector2.zero;
- isFlagSet:检查位标志是否已在数值中设置(该标志未移位)
- isUnshiftedFlagSet:检查位标志是否在数值中设置(该标志已移位)
- setFlagExclusive:设置数值标志位,移除所有已经设置的标志
- setFlag:设置标志位
- unsetFlag:取消标志位
- invertFlags:反转数值集合位
- binaryStringRepresentation:打印 number 的二进制表示,方便调试 number 标志。
public update() {
if (this.direction.length > 0) {
const movement = this.direction.multiply(this.speed * Time.deltaTime);
this.entity.position = this.entity.position.add(movement);
}
}
}
```
### 如何参与项目
#### Node.js版本
v10.20.1
#### 操作步骤
1. 进入source目录
2. 安装package包: `npm install`
3. 打包成js: `gulp build`
> 如遇到gulp未找到则先执行 `npm install gulp -g`
### 4. 创建系统
## 扩展库
```typescript
import { EntitySystem, Entity } from '@esengine/ecs-framework';
#### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar)
#### [基于ecs-framework开发的AIBehaviourTree、UtilityAI)系统](https://github.com/esengine/BehaviourTree-ai)
class MovementSystem extends EntitySystem {
protected process(entities: Entity[]) {
for (const entity of entities) {
const movement = entity.getComponent(MovementComponent);
if (movement) {
movement.update();
}
}
}
}
```
## 📚 核心概念
### Entity(实体)
实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性,可以添加组件来扩展功能。
```typescript
import { Vector2 } from '@esengine/ecs-framework';
const entity = scene.createEntity("MyEntity");
entity.position = new Vector2(100, 200);
entity.rotation = Math.PI / 4;
entity.scale = new Vector2(2, 2);
```
### Component(组件)
组件包含数据和行为,定义了实体的特性。
```typescript
import { Component } from '@esengine/ecs-framework';
class HealthComponent extends Component {
public maxHealth: number = 100;
public currentHealth: number = 100;
public takeDamage(damage: number) {
this.currentHealth = Math.max(0, this.currentHealth - damage);
if (this.currentHealth <= 0) {
this.entity.destroy();
}
}
}
```
### System(系统)
系统处理实体集合,实现游戏逻辑。
```typescript
import { EntitySystem, Entity } from '@esengine/ecs-framework';
class HealthSystem extends EntitySystem {
protected process(entities: Entity[]) {
for (const entity of entities) {
const health = entity.getComponent(HealthComponent);
if (health && health.currentHealth <= 0) {
entity.destroy();
}
}
}
}
```
## 🎮 高级功能
### 事件系统
```typescript
import { Core, CoreEvents } from '@esengine/ecs-framework';
// 监听事件
Core.emitter.addObserver(CoreEvents.frameUpdated, this.onFrameUpdate, this);
// 发射自定义事件
Core.emitter.emit("playerDied", { player: entity, score: 1000 });
// 移除监听
Core.emitter.removeObserver(CoreEvents.frameUpdated, this.onFrameUpdate);
```
### 定时器系统
```typescript
import { Core } from '@esengine/ecs-framework';
// 延迟执行
Core.schedule(2.0, false, this, (timer) => {
console.log("2秒后执行");
});
// 重复执行
Core.schedule(1.0, true, this, (timer) => {
console.log("每秒执行一次");
});
```
### 实体查询
```typescript
// 按名称查找
const player = scene.findEntity("Player");
// 按标签查找
const enemies = scene.findEntitiesByTag(1);
// 按ID查找
const entity = scene.findEntityById(123);
```
### 性能监控
```typescript
import { PerformanceMonitor } from '@esengine/ecs-framework';
// 获取性能数据
const monitor = PerformanceMonitor.instance;
console.log("平均FPS:", monitor.averageFPS);
console.log("内存使用:", monitor.memoryUsage);
```
## 🛠️ 开发工具
### 对象池
```typescript
// 创建对象池
class BulletPool extends es.Pool<Bullet> {
protected createObject(): Bullet {
return new Bullet();
}
}
const bulletPool = new BulletPool();
// 获取对象
const bullet = bulletPool.obtain();
// 释放对象
bulletPool.free(bullet);
```
### 实体调试
```typescript
// 获取实体调试信息
const debugInfo = entity.getDebugInfo();
console.log("实体信息:", debugInfo);
// 获取场景统计
const stats = scene.getStats();
console.log("场景统计:", stats);
```
## 📖 文档
- [快速入门](docs/getting-started.md) - 从零开始学习框架使用
- [核心概念](docs/core-concepts.md) - 深入了解 ECS 架构和设计原理
- [查询系统使用指南](docs/query-system-usage.md) - 学习高性能查询系统的详细用法
## 🔗 扩展库
- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、广度优先、Dijkstra、GOAP 算法
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI 系统
## 🤝 贡献
欢迎提交 Issue 和 Pull Request
### 开发环境设置
```bash
# 克隆项目
git clone https://github.com/esengine/ecs-framework.git
# 进入源码目录
cd ecs-framework/source
# 安装依赖
npm install
# 构建项目
npm run build
# 运行测试
npm test
```
### 构建要求
- Node.js >= 14.0.0
- TypeScript >= 4.0.0
## 📄 许可证
本项目采用 [MIT](LICENSE) 许可证。
## 💬 交流群
加入 QQ 群讨论:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
---
**ECS Framework** - 让游戏开发更简单、更高效!
-70
View File
@@ -1,70 +0,0 @@
# Collision
碰撞检测在大多数游戏中都很常见。框架内使用了一些更先进的碰撞/重叠检查方法,如Minkowski、分离轴定理和古老的三角法
## 线与线相交 [lineToLine]
- 返回是否相交
```typescript
const a1 = new es.Vector2(0, 0);
const a2 = new es.Vector2(100, 100);
const b1 = new es.Vector2(-100, 0);
const b2 = new es.Vector2(100, 200);
const result = es.Collisions.lineToLine(a1, a2, b1, b2);
```
- 返回是否相交并获得相交的点
```typescript
const a1 = new es.Vector2(0, 0);
const a2 = new es.Vector2(100, 100);
const b1 = new es.Vector2(-100, 0);
const b2 = new es.Vector2(100, 200);
// 相交的点坐标
const intersection = new es.Vector2();
const result = es.Collisions.lineToLineIntersection(a1, a2, b1, b2, intersection);
```
## 圆和圆相交 [circleToCircle]
```typescript
const center1 = new es.Vector2(0, 0);
const radius1 = 50;
const center2 = new es.Vector2(30, 30);
const radius2 = 50;
const result = es.Collisions.circleToCircle(center1, radius1, center2, radius2);
```
## 圆和线相交 [circleToLine]
```typescript
const center1 = new es.Vector2(0, 0);
const radius1 = 50;
const a1 = new es.Vector2(0, 0);
const a2 = new es.Vector2(100, 100);
const result = es.Collisions.circleToLine(center1, radius1, a1, a2);
```
## 点是否在圆内 [circleToPoint]
```typescript
const center1 = new es.Vector2(0, 0);
const radius1 = 50;
const point = new es.Vector2(0, 0);
const result = es.Collisions.circleToPoint(center1, radius1, point);
```
## 圆是否和矩形相交 [rectToCircle]
```typescript
const rect = new es.Rectangle(0, 0, 100, 100);
const center = new es.Vector2(30, 30);
const radius = 50;
const result = es.Collisions.rectToCircle(rect, center, radius);
```
## 矩形与线是否相交 [rectToLine]
```typescript
const rect = new es.Rectangle(0, 0, 100, 100);
const a1 = new es.Vector2(0, 0);
const a2 = new es.Vector2(100, 100);
const result = es.Collisions.rectToLine(rect, a1, a2);
```
## 点是否在矩形内 [rectToPoint]
```typescript
const point = new es.Vector2(100, 100);
const result = es.Collisions.rectToPoint(0, 0, 100, 100, point);
```
+496
View File
@@ -0,0 +1,496 @@
# 核心概念
ECS Framework 基于 Entity-Component-System 架构模式,这是一种高度模块化和可扩展的游戏开发架构。本文档将详细介绍框架的核心概念。
## ECS 架构概述
ECS 架构将传统的面向对象设计分解为三个核心部分:
- **Entity(实体)** - 游戏世界中的对象,包含基本属性如位置、旋转、缩放
- **Component(组件)** - 包含数据和行为的功能模块
- **System(系统)** - 处理实体集合的逻辑处理单元
## Core(核心)
Core 是框架的核心管理类,负责游戏的生命周期管理。
### 创建和配置
```typescript
import { Core } from './Core';
// 创建核心实例(调试模式)
const core = Core.create(true);
// 创建核心实例(发布模式)
const core = Core.create(false);
```
### 事件系统
```typescript
import { CoreEvents } from './ECS/CoreEvents';
// 监听核心事件
Core.emitter.addObserver(CoreEvents.frameUpdated, this.onUpdate, this);
// 发送帧更新事件
Core.emitter.emit(CoreEvents.frameUpdated);
// 发送自定义事件
Core.emitter.emit("customEvent", { data: "value" });
```
### 定时器系统
```typescript
// 延迟执行
Core.schedule(2.0, false, this, (timer) => {
console.log("2秒后执行");
});
// 重复执行
Core.schedule(1.0, true, this, (timer) => {
console.log("每秒执行一次");
});
```
## Scene(场景)
场景是游戏世界的容器,管理实体和系统的生命周期。
### 创建和使用场景
```typescript
import { Scene } from './ECS/Scene';
// 创建场景
const scene = new Scene();
scene.name = "GameScene";
// 设置为当前场景
Core.scene = scene;
// 场景生命周期
scene.begin(); // 开始场景
scene.update(); // 更新场景
scene.end(); // 结束场景
```
## Entity(实体)
实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性。
### 实体的基本属性
```typescript
import { Vector2 } from './Math/Vector2';
const entity = scene.createEntity("MyEntity");
// 位置
entity.position = new Vector2(100, 200);
entity.position = entity.position.add(new Vector2(10, 0));
// 旋转(弧度)
entity.rotation = Math.PI / 4;
// 缩放
entity.scale = new Vector2(2, 2);
// 标签(用于分类)
entity.tag = 1;
// 启用状态
entity.enabled = true;
// 活跃状态
entity.active = true;
// 更新顺序
entity.updateOrder = 10;
```
### 实体层级关系
```typescript
// 添加子实体
const parent = scene.createEntity("Parent");
const child = scene.createEntity("Child");
parent.addChild(child);
// 获取父实体
const parentEntity = child.parent;
// 获取所有子实体
const children = parent.children;
// 查找子实体
const foundChild = parent.findChild("Child");
// 按标签查找子实体
const taggedChildren = parent.findChildrenByTag(1);
// 移除子实体
parent.removeChild(child);
// 移除所有子实体
parent.removeAllChildren();
```
### 实体生命周期
```typescript
// 检查实体是否被销毁
if (!entity.isDestroyed) {
// 实体仍然有效
}
// 销毁实体
entity.destroy();
// 获取实体调试信息
const debugInfo = entity.getDebugInfo();
console.log(debugInfo);
```
## Component(组件)
组件包含数据和行为,定义了实体的特性和能力。
### 创建组件
```typescript
import { Component } from './ECS/Component';
class HealthComponent extends Component {
public maxHealth: number = 100;
public currentHealth: number = 100;
public takeDamage(damage: number) {
this.currentHealth -= damage;
if (this.currentHealth <= 0) {
this.entity.destroy();
}
}
public heal(amount: number) {
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
}
}
```
### 组件生命周期
```typescript
class MyComponent extends Component {
public onAddedToEntity() {
// 组件被添加到实体时调用
console.log("组件已添加到实体:", this.entity.name);
}
public onRemovedFromEntity() {
// 组件从实体移除时调用
console.log("组件已从实体移除");
}
public onEnabled() {
// 组件启用时调用
console.log("组件已启用");
}
public onDisabled() {
// 组件禁用时调用
console.log("组件已禁用");
}
public update() {
// 每帧更新(如果组件启用)
console.log("组件更新");
}
}
```
### 组件管理
```typescript
// 添加组件
const health = entity.addComponent(new HealthComponent());
// 创建并添加组件
const movement = entity.createComponent(MovementComponent, 200); // 传递构造参数
// 获取组件
const healthComp = entity.getComponent(HealthComponent);
// 检查组件是否存在
if (entity.hasComponent(HealthComponent)) {
// 处理逻辑
}
// 获取或创建组件
const weapon = entity.getOrCreateComponent(WeaponComponent);
// 获取多个同类型组件
const allHealthComps = entity.getComponents(HealthComponent);
// 移除组件
entity.removeComponent(healthComp);
// 按类型移除组件
entity.removeComponentByType(HealthComponent);
// 移除所有组件
entity.removeAllComponents();
```
## Scene(场景)
场景是实体和系统的容器,管理游戏世界的状态。
### 场景生命周期
```typescript
class GameScene extends es.Scene {
public initialize() {
// 场景初始化,创建实体和系统
this.setupEntities();
this.setupSystems();
}
public onStart() {
// 场景开始运行时调用
console.log("场景开始");
}
public unload() {
// 场景卸载时调用
console.log("场景卸载");
}
private setupEntities() {
const player = this.createEntity("Player");
player.addComponent(new PlayerComponent());
}
private setupSystems() {
this.addEntityProcessor(new MovementSystem());
}
}
```
### 实体管理
```typescript
// 创建实体
const entity = scene.createEntity("MyEntity");
// 添加现有实体
scene.addEntity(entity);
// 查找实体
const player = scene.findEntity("Player");
const entityById = scene.findEntityById(123);
const entitiesByTag = scene.findEntitiesByTag(1);
// 销毁所有实体
scene.destroyAllEntities();
// 获取场景统计信息
const stats = scene.getStats();
console.log("实体数量:", stats.entityCount);
console.log("系统数量:", stats.processorCount);
```
## System(系统)
系统处理实体集合,实现游戏的核心逻辑。
### EntitySystem
最常用的系统类型,处理实体集合:
```typescript
class MovementSystem extends es.EntitySystem {
protected process(entities: es.Entity[]) {
for (const entity of entities) {
const movement = entity.getComponent(MovementComponent);
if (movement) {
movement.update();
}
}
}
}
```
### ProcessingSystem
定期处理的系统:
```typescript
class HealthRegenerationSystem extends es.ProcessingSystem {
protected process(entities: es.Entity[]) {
for (const entity of entities) {
const health = entity.getComponent(HealthComponent);
if (health && health.currentHealth < health.maxHealth) {
health.currentHealth += 10 * es.Time.deltaTime;
}
}
}
}
```
### IntervalSystem
按时间间隔执行的系统:
```typescript
class SpawnSystem extends es.IntervalSystem {
constructor() {
super(3.0); // 每3秒执行一次
}
protected processSystem() {
// 生成敌人
const enemy = this.scene.createEntity("Enemy");
enemy.addComponent(new EnemyComponent());
}
}
```
### PassiveSystem
被动系统,不自动处理实体:
```typescript
class CollisionSystem extends es.PassiveSystem {
public checkCollisions() {
// 手动调用的碰撞检测逻辑
}
}
```
## Time(时间)
时间管理工具类,提供游戏时间相关功能:
```typescript
// 获取时间信息
console.log("帧时间:", es.Time.deltaTime);
console.log("总时间:", es.Time.totalTime);
console.log("帧数:", es.Time.frameCount);
console.log("时间缩放:", es.Time.timeScale);
// 设置时间缩放(慢动作效果)
es.Time.timeScale = 0.5;
// 检查时间间隔
if (es.Time.checkEvery(1.0, lastCheckTime)) {
// 每秒执行一次
}
```
## Vector2(二维向量)
二维向量类,提供数学运算:
```typescript
// 创建向量
const vec1 = new es.Vector2(10, 20);
const vec2 = es.Vector2.zero;
const vec3 = es.Vector2.one;
// 向量运算
const sum = vec1.add(vec2);
const diff = vec1.subtract(vec2);
const scaled = vec1.multiply(2);
const normalized = vec1.normalize();
// 向量属性
console.log("长度:", vec1.length);
console.log("长度平方:", vec1.lengthSquared);
// 静态方法
const distance = es.Vector2.distance(vec1, vec2);
const lerped = es.Vector2.lerp(vec1, vec2, 0.5);
const fromAngle = es.Vector2.fromAngle(Math.PI / 4);
```
## 性能监控
框架内置性能监控工具:
```typescript
// 获取性能监控实例
const monitor = es.PerformanceMonitor.instance;
// 查看性能数据
console.log("平均FPS:", monitor.averageFPS);
console.log("最小FPS:", monitor.minFPS);
console.log("最大FPS:", monitor.maxFPS);
console.log("内存使用:", monitor.memoryUsage);
// 重置性能数据
monitor.reset();
```
## 对象池
内存管理优化工具:
```typescript
// 创建对象池
class BulletPool extends es.Pool<Bullet> {
protected createObject(): Bullet {
return new Bullet();
}
}
const bulletPool = new BulletPool();
// 使用对象池
const bullet = bulletPool.obtain();
// 使用bullet...
bulletPool.free(bullet);
// 清空对象池
bulletPool.clear();
```
## 最佳实践
### 1. 实体设计
- 实体只包含基本属性,功能通过组件实现
- 合理使用实体层级关系
- 及时销毁不需要的实体
### 2. 组件设计
- 组件保持单一职责
- 使用生命周期方法进行初始化和清理
- 避免组件间直接依赖
### 3. 系统设计
- 系统专注于特定逻辑处理
- 合理设置系统更新顺序
- 使用被动系统处理特殊逻辑
### 4. 性能优化
- 使用对象池减少内存分配
- 监控性能数据
- 合理使用时间缩放
## 总结
ECS Framework 提供了完整的实体组件系统架构:
- **Core** 管理游戏生命周期和全局功能
- **Entity** 作为游戏对象的基础容器
- **Component** 实现具体的功能模块
- **System** 处理游戏逻辑
- **Scene** 管理游戏世界状态
通过合理使用这些核心概念,可以构建出结构清晰、易于维护的游戏代码。
-189
View File
@@ -1,189 +0,0 @@
# Coroutine
## 协程介绍
框架的协程系统是基于js的一个简单而强大的迭代器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的代码
### 倒计时器
这是一个简单的脚本组件,只做了倒计时,并且在到达0的时候log一个信息。
```typescript
export class AComponent extends es.Component implements es.IUpdatable {
public timer = 3;
update() {
this.timer -= es.Time.deltaTime;
if(this.timer <= 0) {
console.Log("Timer has finished!");
}
}
}
```
还不错,代码简短实用,但问题是,如果我们需要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?刚开始的时候,我们的代码也许会是这样的:
```typescript
export class AComponent extends es.Component implements es.IUpdatable
{
public firstTimer = 3;
public secondTimer = 2;
public thirdTimer = 1;
update() {
this.firstTimer -= es.Time.deltaTime;
if(this.firstTimer <= 0)
console.Log("First timer has finished!");
this.secondTimer -= es.Time.deltaTime;
if(this.secondTimer <= 0)
console.Log("Second timer has finished!");
this.thirdTimer -= es.Time.deltaTime;
if(this.thirdTimer <= 0)
console.Log("Third timer has finished!");
}
}
```
尽管不是太糟糕,但是我个人不是很喜欢自己的代码中充斥着这些计时器变量,它们看上去很乱,而且当我需要重新开始计时的时候还得记得去重置它们(这活我经常忘记做)。
如果我只用一个for循环来做这些,看上去是否会好很多?
```typescript
for(let timer = 3; timer >= 0; timer -= es.Time.deltaTime) {
//Just do nothing...
}
console.Log("This happens after 5 seconds!");
```
现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个跌倒变量。
好的,你可能现在明白我的意思:协程可以做的正是这一点!
## 码入你的协程!
现在,这里提供了上面例子运用协程的版本!我建议你从这里开始跟着我来写一个简单的脚本组件,这样你可以在你自己的程序中看到它是如何工作的。
```typescript
export class AComponent extends es.Component implements es.IUpdatable
{
onAddedToEntity() {
es.Core.startCoroutine(this.countdown());
}
*countdown() {
for(let timer = 3; timer >= 0; timer -= es.Time.deltaTime)
yield null;
console.Log("This message appears after 3 seconds!");
}
}
```
这看上去有点不一样,没关系,接下来我会解释这里到底发生了什么。
```typescript
es.Core.startCoroutine(this.countdown());
```
这一行用来开始我们的countdown程序,注意,我并没有给它传入参数,但是这个方法调用了它自己(这是通过传递countdown的yield返回值来实现的)。
### Yield
为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,框架必须通过某种方式来存储这个方法的状态,这是通过迭代器中使用yield语句得到的返回值,当你`yield`一个方法时,你相当于说了,**现在停止这个方法,然后在下一帧中从这里重新开始!**。
> 注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。当然,同样的你可以继续yield其他协程,我会在下一个教程中讲到这些。
## 一些例子
协程在刚开始接触的时候是非常难以理解的,无论是新手还是经验丰富的程序员我都见过他们对于协程语句一筹莫展的时候。因此我认为通过例子来理解它是最好的方法,这里有一些简单的协程例子:
### 多次输出Hello
记住,yield是 **停止执行方法,并且在下一帧从这里重新开始**,这意味着你可以这样做:
```typescript
//这将打招呼 5 次,每帧一次,持续 5 帧
*sayHelloFiveTimes() {
yield null;
console.Log("Hello");
yield null;
console.Log("Hello");
yield null;
console.Log("Hello");
yield null;
console.Log("Hello");
yield null;
console.Log("Hello");
}
//这将做与上述功能完全相同的事情!
*sayHello5Times() {
for(let i = 0; i < 5; i++) {
console.Log("Hello");
yield null;
}
}
```
### 每一帧输出“Hello”,无限循环。。。
通过在一个while循环中使用yield,你可以得到一个无限循环的协程,这几乎就跟一个update()循环等同
```typescript
//一旦启动,这将一直运行直到手动停止
*sayHelloEveryFrame(){
while(true) {
console.Log("Hello");
yield null;
}
}
```
### 计时
不过跟update()不一样的是,你可以在协程中做一些更有趣的事
```typescript
*countSeconds(){
let seconds = 0;
while(true)
{
// 1秒后执行下一帧
yield 1;
seconds++;
console.Log("自协程启动以来已经过去了" + seconds + "秒钟.");
}
}
```
这个方法突出了协程一个非常酷的地方:方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即使是在不同的帧中。还记得这个教程开始时那些烦人的计时器变量吗?通过协程,我们再也不需要担心它们了,只需要把变量直接放到方法里面!
### 开始和终止协程
之前,我们已经学过了通过 es.Core.startCoroutine()方法来开始一个协程,就像这样:
```typescript
const coroutine = es.Core.startCoroutine(this.countdown());
```
我们可以像这样停止协程
```typescript
coroutine.stop();
```
或者你可以再迭代器内返回`yield "break"`方式中止协程
```typescript
*countSeconds(){
let seconds = 0;
while(true)
{
for(let timer = 0; timer < 1; timer += es.Time.deltaTime)
yield null;
seconds++;
console.Log("自协程启动以来已经过去了" + seconds + "秒钟.");
// 如果大于10秒,终止协程
if (second > 10)
yield "break";
}
}
```
-121
View File
@@ -1,121 +0,0 @@
# 创建实体
实体必须依赖于场景,不能单独存在。创建实体方法由场景提供。
## 方式一
```typescript
// 通过全局快捷获取场景创建实体
const playerEntity = es.Core.scene.createEntity("player");
```
## 方式二
```typescript
export class MainScene extends es.Scene {
onStart() {
// 通过场景内创建
const playerEntity = this.createEntity("player");
}
}
```
### Transform
框架中提供的实体不同于其他框架实体,它更偏向于游戏使用,实体内含有`Transform`属性。可用于快速访问位置,旋转,缩放等。如果需要应用于游戏引擎,请再组件重写`onTransformChanged`监听这些属性的变化。
> 实体内包含对transform里位置、旋转、缩放的快捷tween方法。`tweenPositionTo`/`tweenLocalPositionTo`/`tweenScaleTo`/`tweenLocalScaleTo`/`tweenRotationDegreesTo`/`tweenLocalRotationDegreesTo`
### tag / setTag
实体还提供`tag`属性及`setTag`方法来快速设置实体的标记,可再场景中使用`findEntitiesWithTag`快速查询拥有该标记的实体或使用`findEntityWithTag`来查找第一个拥有该标记的实体,你可以把它当作组来使用。
### detachFromScene / attachToScene
当你不想实体与场景被销毁时一同被销毁。可先 `detachFromScene`,等待合适的时机再调用 `attachToScene` 放入新的场景。
# 创建组件
组件一般配合实体使用。组件需要继承 `es.Component` 来标识为组件,如果想让组件拥有每帧更新能力则额外继承`es.IUpdatable` 接口。在实现的`update`方法当中进行更新逻辑。
```typescript
// es.IUpdatable接口为可选接口,如果不需要更新能力则不必继承
export class AComponent extends es.Component implements es.IUpdatable {
update() {
// 更新逻辑
}
}
```
## 加入组件
组件必须挂载于实体上,不能单独存在,如果需要单独于场景的组件则参考 [es.SceneComponent](scene_component.md) 组件。
- 方式一:将现有的AComponent加入实体
```typescript
const aCom = playerEntity.addComponent(new AComponent());
```
- 方式二:在实体上直接创建组件
```typescript
const aCom = playerEntity.createComponent(AComponent);
```
## 获取组件
- 方式一: 根据类型获取找到满足条件的第一个组件
```typescript
// 不能保证已经加入场景
const aCom = playerEntity.getComponent(AComponent);
```
```typescript
// 保证已经加入场景
const aCom = playerEntity.getComponentInScene(AComponent);
```
- 方式二: 尝试找到一个组件,返回是否找到组件标志,第二参数需要一个引用组件用于存储已找到的组件
```typescript
const outCom = new Ref<AComponent>();
const find = playerEntity.tryGetComponent(AComponent, outCom);
if (find) {
const aCom = outCom.value;
}
```
- 方式三:获取该类型的组件,如果未找到则创建一个并返回
```typescript
const aCom = playerEntity.getOrCreateComponent(AComponent);
```
- 方式四:根据第二参数中的列表找到该类型的所有组件并返回
```typescript
const findArray: Component[] = [
new AComponent(),
new BComponent(),
new CComponent()
];
// findArray可不传,则在实体上寻找满足第一个条件的所有组件
const coms = playerEntity.getComponents(AComponent, findArray);
```
- 组件是否存在
```typescript
const find = playerEntity.hasComponent(AComponent);
```
## 移除组件
- 方式一: 移除已实例组件
```typescript
playerEntity.removeComponent(aCom);
```
- 方式二:移除满足类型的第一个组件
```typescript
playerEntity.removeComponentForType(AComponent);
```
- 方式三: 移除所有组件
```typescript
playerEntity.removeAllComponents();
```
-39
View File
@@ -1,39 +0,0 @@
# Emitter
Core提供了一个在某些关键时刻触发事件的发射器。 通过Core.emitter.addObserver和Core.emitter.removeObserver进行访问。 CoreEvents枚举定义了所有可用事件。
发射器类也可以在自己的类中使用。 您可以通过number,enum或任何结构键输入事件。
## 自定义事件发生器
- string为key的事件发生器
```typescript
export enum CustomEvent {
enum1,
enum2
}
export class MainScene extends es.Scene {
// string为key的事件发生器
private str_emitter = new es.Emitter<string>();
// number为key的事件发生器
private num_emitter = new es.Emitter<number>();
// enum为key的事件发生器
private custom_emitter = new es.Emitter<CustomEvent>();
onStart() {
// 监听触发器
this.str_emitter.addObserver("test", this.onStrEmit, this);
// 触发监听器
this.str_emitter.emit("test");
// 移除事件触发器
this.str_emitter.removeObserver("test", this.onStrEmit);
}
// args为emit传入的参数。不传则为空
onStrEmit(...args: any[]) {
console.log("test");
}
}
```
+625
View File
@@ -0,0 +1,625 @@
# 快速入门
本指南将帮助您快速上手 ECS Framework,这是一个轻量级的实体组件系统框架,专为小游戏设计。
## 项目结构
```
ecs-framework/
├── source/
│ ├── src/ # 源代码
│ │ ├── ECS/ # ECS核心系统
│ │ ├── Math/ # 数学运算
│ │ ├── Types/ # 类型定义
│ │ └── Utils/ # 工具类
│ ├── scripts/ # 构建脚本
│ └── tsconfig.json # TypeScript配置
└── docs/ # 文档
```
## 安装和构建
### 从源码构建
```bash
# 克隆项目
git clone https://github.com/esengine/ecs-framework.git
# 进入源码目录
cd ecs-framework/source
# 编译TypeScript
npx tsc
```
### 直接使用
您可以直接将源码复制到项目中使用,或者引用编译后的JavaScript文件。
## 基础设置
### 1. 导入框架
```typescript
// 导入核心类
import { Core } from './Core';
import { Entity } from './ECS/Entity';
import { Component } from './ECS/Component';
import { Scene } from './ECS/Scene';
import { QuerySystem } from './ECS/Core/QuerySystem';
import { Emitter } from './Utils/Emitter';
import { TimerManager } from './Utils/Timers/TimerManager';
```
### 2. 创建基础管理器
```typescript
class GameManager {
private core: Core;
private scene: Scene;
private querySystem: QuerySystem;
private emitter: Emitter;
private timerManager: TimerManager;
constructor() {
// 创建核心实例
this.core = Core.create(true);
// 创建场景
this.scene = new Scene();
this.scene.name = "GameScene";
// 获取场景的查询系统
this.querySystem = this.scene.querySystem;
// 获取核心的事件系统和定时器
this.emitter = Core.emitter;
this.timerManager = this.core._timerManager;
// 设置当前场景
Core.scene = this.scene;
}
public update(deltaTime: number): void {
// 更新定时器
this.timerManager.update(deltaTime);
// 更新场景
this.scene.update();
// 处理系统逻辑
this.updateSystems(deltaTime);
}
private updateSystems(deltaTime: number): void {
// 在这里添加您的系统更新逻辑
}
}
```
### 3. 游戏循环
```typescript
const gameManager = new GameManager();
let lastTime = performance.now();
function gameLoop() {
const currentTime = performance.now();
const deltaTime = (currentTime - lastTime) / 1000; // 转换为秒
lastTime = currentTime;
gameManager.update(deltaTime);
requestAnimationFrame(gameLoop);
}
// 启动游戏循环
gameLoop();
```
## 创建实体和组件
### 1. 定义组件
```typescript
// 位置组件
class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
// 速度组件
class VelocityComponent extends Component {
public x: number = 0;
public y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
// 生命值组件
class HealthComponent extends Component {
public maxHealth: number = 100;
public currentHealth: number = 100;
constructor(maxHealth: number = 100) {
super();
this.maxHealth = maxHealth;
this.currentHealth = maxHealth;
}
public takeDamage(damage: number): void {
this.currentHealth = Math.max(0, this.currentHealth - damage);
}
public heal(amount: number): void {
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
}
public isDead(): boolean {
return this.currentHealth <= 0;
}
}
```
### 2. 创建实体
```typescript
class GameManager {
// ... 之前的代码 ...
public createPlayer(): Entity {
const player = this.scene.createEntity("Player");
// 添加组件
player.addComponent(new PositionComponent(400, 300));
player.addComponent(new VelocityComponent(0, 0));
player.addComponent(new HealthComponent(100));
// 设置标签和更新顺序
player.tag = 1; // 玩家标签
player.updateOrder = 0;
return player;
}
public createEnemy(x: number, y: number): Entity {
const enemy = this.scene.createEntity("Enemy");
enemy.addComponent(new PositionComponent(x, y));
enemy.addComponent(new VelocityComponent(50, 0));
enemy.addComponent(new HealthComponent(50));
enemy.tag = 2; // 敌人标签
enemy.updateOrder = 1;
return enemy;
}
}
## 使
```typescript
class GameManager {
// ... 之前的代码 ...
private updateSystems(deltaTime: number): void {
this.updateMovementSystem(deltaTime);
this.updateHealthSystem(deltaTime);
this.updateCollisionSystem();
}
private updateMovementSystem(deltaTime: number): void {
// 查询所有具有位置和速度组件的实体
const movableEntities = this.querySystem.queryTwoComponents(
PositionComponent,
VelocityComponent
);
movableEntities.forEach(({ entity, component1: position, component2: velocity }) => {
// 更新位置
position.x += velocity.x * deltaTime;
position.y += velocity.y * deltaTime;
// 边界检查
if (position.x < 0 || position.x > 800) {
velocity.x = -velocity.x;
}
if (position.y < 0 || position.y > 600) {
velocity.y = -velocity.y;
}
});
}
private updateHealthSystem(deltaTime: number): void {
// 查询所有具有生命值组件的实体
const healthEntities = this.querySystem.queryComponentTyped(HealthComponent);
const deadEntities: Entity[] = [];
healthEntities.forEach(({ entity, component: health }) => {
// 检查死亡
if (health.isDead()) {
deadEntities.push(entity);
}
});
// 移除死亡实体
deadEntities.forEach(entity => {
entity.destroy();
});
}
private updateCollisionSystem(): void {
// 获取玩家
const players = this.scene.findEntitiesByTag(1); // 玩家标签
const enemies = this.scene.findEntitiesByTag(2); // 敌人标签
players.forEach(player => {
const playerPos = player.getComponent(PositionComponent);
const playerHealth = player.getComponent(HealthComponent);
if (!playerPos || !playerHealth) return;
enemies.forEach(enemy => {
const enemyPos = enemy.getComponent(PositionComponent);
if (!enemyPos) return;
// 简单的距离检测
const distance = Math.sqrt(
Math.pow(playerPos.x - enemyPos.x, 2) +
Math.pow(playerPos.y - enemyPos.y, 2)
);
if (distance < 50) { // 碰撞距离
playerHealth.takeDamage(10);
console.log(`当前生命值: ${playerHealth.currentHealth}`);
}
});
});
}
}
```
## 使用事件系统
框架内置了事件系统,用于组件间通信:
```typescript
// 定义事件类型
enum GameEvents {
PLAYER_DIED = 'playerDied',
ENEMY_SPAWNED = 'enemySpawned',
SCORE_CHANGED = 'scoreChanged'
}
class GameManager {
// ... 之前的代码 ...
constructor() {
// ... 之前的代码 ...
// 监听事件
this.emitter.on(GameEvents.PLAYER_DIED, this.onPlayerDied.bind(this));
this.emitter.on(GameEvents.ENEMY_SPAWNED, this.onEnemySpawned.bind(this));
}
private onPlayerDied(player: Entity): void {
console.log('游戏结束!');
// 重置游戏或显示游戏结束界面
}
private onEnemySpawned(enemy: Entity): void {
console.log('新敌人出现!');
}
private updateHealthSystem(deltaTime: number): void {
const healthEntities = this.querySystem.queryComponentTyped(HealthComponent);
healthEntities.forEach(({ entity, component: health }) => {
if (health.isDead()) {
// 发送死亡事件
if (entity.tag === 1) { // 玩家
this.emitter.emit(GameEvents.PLAYER_DIED, entity);
}
entity.destroy();
}
});
}
}
```
## 使用定时器
框架提供了强大的定时器系统:
```typescript
class GameManager {
// ... 之前的代码 ...
public startGame(): void {
// 创建玩家
this.createPlayer();
// 每2秒生成一个敌人
Core.schedule(2.0, true, this, (timer) => {
const x = Math.random() * 800;
const y = Math.random() * 600;
const enemy = this.createEnemy(x, y);
this.emitter.emit(GameEvents.ENEMY_SPAWNED, enemy);
});
// 5秒后增加敌人生成速度
Core.schedule(5.0, false, this, (timer) => {
console.log('游戏难度提升!');
// 可以在这里修改敌人生成间隔
});
}
}
##
```typescript
// 导入框架
import { Core } from './Core';
import { Entity } from './ECS/Entity';
import { Component } from './ECS/Component';
import { Scene } from './ECS/Scene';
import { QuerySystem } from './ECS/Core/QuerySystem';
import { Emitter } from './Utils/Emitter';
// 定义组件
class PositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class VelocityComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class HealthComponent extends Component {
constructor(public maxHealth: number = 100) {
super();
this.currentHealth = maxHealth;
}
public currentHealth: number;
public takeDamage(damage: number): void {
this.currentHealth = Math.max(0, this.currentHealth - damage);
}
public isDead(): boolean {
return this.currentHealth <= 0;
}
}
// 游戏事件
enum GameEvents {
PLAYER_DIED = 'playerDied',
ENEMY_SPAWNED = 'enemySpawned'
}
// 完整的游戏管理器
class SimpleGame {
private core: Core;
private scene: Scene;
private querySystem: QuerySystem;
private emitter: Emitter;
private isRunning: boolean = false;
constructor() {
this.core = Core.create(true);
this.scene = new Scene();
this.scene.name = "SimpleGame";
this.querySystem = this.scene.querySystem;
this.emitter = Core.emitter;
// 设置场景
Core.scene = this.scene;
// 监听事件
this.emitter.on(GameEvents.PLAYER_DIED, () => {
console.log('游戏结束!');
this.isRunning = false;
});
}
public start(): void {
console.log('游戏开始!');
this.isRunning = true;
// 创建玩家
this.createPlayer();
// 定期生成敌人
Core.schedule(2.0, true, this, (timer) => {
if (this.isRunning) {
this.createEnemy();
}
});
// 启动游戏循环
this.gameLoop();
}
private createPlayer(): Entity {
const player = this.scene.createEntity("Player");
player.addComponent(new PositionComponent(400, 300));
player.addComponent(new VelocityComponent(100, 0));
player.addComponent(new HealthComponent(100));
player.tag = 1; // 玩家标签
return player;
}
private createEnemy(): Entity {
const enemy = this.scene.createEntity("Enemy");
const x = Math.random() * 800;
const y = Math.random() * 600;
enemy.addComponent(new PositionComponent(x, y));
enemy.addComponent(new VelocityComponent(-50, 0));
enemy.addComponent(new HealthComponent(50));
enemy.tag = 2; // 敌人标签
this.emitter.emit(GameEvents.ENEMY_SPAWNED, enemy);
return enemy;
}
private update(deltaTime: number): void {
// 更新场景
this.scene.update();
// 更新游戏系统
this.updateMovement(deltaTime);
this.updateCollision();
this.updateHealth();
}
private updateMovement(deltaTime: number): void {
const movableEntities = this.querySystem.queryTwoComponents(
PositionComponent,
VelocityComponent
);
movableEntities.forEach(({ entity, component1: pos, component2: vel }) => {
pos.x += vel.x * deltaTime;
pos.y += vel.y * deltaTime;
// 边界检查
if (pos.x < 0 || pos.x > 800) vel.x = -vel.x;
if (pos.y < 0 || pos.y > 600) vel.y = -vel.y;
});
}
private updateCollision(): void {
const players = this.scene.findEntitiesByTag(1);
const enemies = this.scene.findEntitiesByTag(2);
players.forEach(player => {
const playerPos = player.getComponent(PositionComponent);
const playerHealth = player.getComponent(HealthComponent);
if (!playerPos || !playerHealth) return;
enemies.forEach(enemy => {
const enemyPos = enemy.getComponent(PositionComponent);
if (!enemyPos) return;
const distance = Math.sqrt(
Math.pow(playerPos.x - enemyPos.x, 2) +
Math.pow(playerPos.y - enemyPos.y, 2)
);
if (distance < 50) {
playerHealth.takeDamage(10);
console.log(`玩家生命值: ${playerHealth.currentHealth}`);
}
});
});
}
private updateHealth(): void {
const healthEntities = this.querySystem.queryComponentTyped(HealthComponent);
const deadEntities: Entity[] = [];
healthEntities.forEach(({ entity, component: health }) => {
if (health.isDead()) {
deadEntities.push(entity);
if (entity.tag === 1) { // 玩家死亡
this.emitter.emit(GameEvents.PLAYER_DIED, entity);
}
}
});
// 移除死亡实体
deadEntities.forEach(entity => {
entity.destroy();
});
}
private gameLoop(): void {
let lastTime = performance.now();
const loop = () => {
if (!this.isRunning) return;
const currentTime = performance.now();
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
this.update(deltaTime);
requestAnimationFrame(loop);
};
loop();
}
}
// 启动游戏
const game = new SimpleGame();
game.start();
```
## 下一步
现在您已经掌握了 ECS Framework 的基础用法,可以继续学习:
- [核心概念](core-concepts.md) - 深入了解 ECS 架构和设计原理
- [查询系统使用指南](query-system-usage.md) - 学习高性能查询系统的详细用法
## 常见问题
### Q: 如何在不同游戏引擎中集成?
A: ECS Framework 是引擎无关的,您只需要:
1. 将框架源码复制到项目中
2. 在游戏引擎的主循环中调用 `scene.update()`
3. 根据需要集成渲染、输入等引擎特定功能
### Q: 如何处理输入?
A: 框架本身不提供输入处理,建议:
1. 创建一个输入组件来存储输入状态
2. 在游戏循环中更新输入状态
3. 在相关组件中读取输入状态并处理
### Q: 如何调试?
A: 框架提供了多种调试功能:
- 使用 `entity.getDebugInfo()` 查看实体信息
- 使用 `querySystem.getPerformanceReport()` 查看查询性能
- 使用 `querySystem.getStats()` 查看详细统计信息
### Q: 性能如何优化?
A: 框架已经内置了多种性能优化:
- 使用位掩码进行快速组件匹配
- 多级索引系统加速查询
- 智能缓存减少重复计算
- 批量操作减少开销
建议定期调用 `querySystem.optimizeIndexes()` 来自动优化配置。
-80
View File
@@ -1,80 +0,0 @@
# 如何开始
## 初始化框架
```typescript
// 创建调试模式下的`Core`实例
const core = es.Core.create();
// 创建非调试模式下的`Core`实例
const core = es.Core.create(false);
```
## 分发帧事件
```typescript
// dt 是一个可选参数,如果传入了 es.Time.deltaTime或者不传入参数,则代表使用框架的内置的时间差来更新游戏状态;
// 如果传入了游戏引擎自带的 deltaTime,则代表使用该值来更新游戏状态。
// 在 es.Core.update 方法中,会根据 dt 的值来计算时间戳信息,并更新全局管理器和当前场景的状态
es.Core.emitter.emit(es.CoreEvents.frameUpdated, dt);
```
> 尽可能使用引擎的dt,以免再游戏暂停继续时由于dt导致的跳帧问题
> **您还需要一个默认的场景以使得游戏可以进行使用ecs框架以及物理类或tween系统**
## 创建场景类
场景类需要继承框架中的 `es.Scene`
```typescript
/** 示例场景 */
export class MainScene extends Scene {
constructor() {
super();
}
/**
* 初始化场景,添加实体和组件
*
* 这个方法会在场景被创建时被调用。我们在这个方法中创建了一个实体,
* 并向它添加了一个SpriteRender组件和一个TransformMove组件。
*/
public initialize() {
// 创建一个实体
let entity = this.createEntity("Player");
// 添加一个SpriteRender组件,用于显示实体的图像
let spriteRender = entity.addComponent(new SpriteRender());
spriteRender.sprite = new es.Sprite(new es.Texture("player.png"));
// 添加一个TransformMove组件,用于移动实体
let transformMove = entity.addComponent(new TransformMove());
transformMove.speed = 50;
}
/**
* 场景开始运行时执行的操作
*
* 这个方法会在场景开始运行时被调用。我们在这个方法中输出一条消息表示场景已经开始运行。
*/
public onStart() {
console.log("MainScene has started!");
}
/**
* 场景被销毁时执行的操作
*
* 这个方法会在场景被销毁时被调用。我们在这个方法中输出一条消息表示场景已经被卸载。
*/
public unload() {
console.log("MainScene has been unloaded!");
}
}
```
要想激活该场景需要通过核心类 `Core` 来设置当前 `MainScene` 为使用的场景
```typescript
es.Core.scene = new MainScene();
```
-73
View File
@@ -1,73 +0,0 @@
## 关于 Physics/Collision
框架中的物理不是一个真实的物理模拟。它只提供了游戏物理。您可以执行一些操作,如检测碰撞器、重叠检查、碰撞检查、扫描测试等。不是一个完整的刚体模拟。
### Colliders 物理系统的根本
没有Collider,在物理系统中什么也不会发生。 碰撞器存在于实体类中,有几种类型:BoxColliderCircleCollider和PolygonCollider。 您可以像这样添加Collider:`entity.addComponent(new BoxCollider())`. 将碰撞器添加到Entity时,它们会自动添加到SpatialHash中。
### SpatialHash:你永远不会用到它,但它仍然对你很重要
SpatialHash类是一个隐藏类,该类为您的游戏全局管理 `collider`。静态物理类是SpatialHash的公共包装器。 SpatialHash没有设置大小限制,用于快速进行碰撞/线投射/重叠检查。例如,如果你有一个英雄在世界各地移动,而不必检查每个对撞机(可能是数百个)是否发生碰撞,则只需向SpatialHash询问英雄附近的所有collider即可。这大大缩小了您的碰撞检查范围。
SpatialHash有一个可配置的方面,它可以极大地影响其性能:单元大小。 SpatialHash将空间分成一个网格,选择适当的网格大小可以将可能发生的碰撞查询减到最少。默认情况下,网格大小为100像素。您可以通过在创建场景之前设置`Physics.SpatialHashCellSize`来更改此设置。选择比您的平均玩家/敌人人数稍大的尺寸通常效果最佳。
### Physics 类
物理类是物理的核心类。 您可以设置一些属性,例如前面提到的spatialHashCellSizeraycastsHitTriggers和raycastsStartInColliders。
- linecast:从开始到结束投射一条线,并返回与layerMask相匹配的碰撞器的第一次命中
- overlapRectangle:检查是否有任何collider在矩形区域内
- overlapCircle:检查是否有任何collider在圆形区域内
- boxcastBroadphase:返回边界与collider.bounds相交的所有碰撞器。 请注意,这是一个broadphase检查,因此它只检查边界,不执行单个碰撞器到碰撞器的检查!
会注意到上面提到的layerMask。 layerMask允许您确定与哪些碰撞器碰撞。 每个collider都可以设置其物理层,以便在查询物理系统时可以选择仅检索与传入的layerMask匹配的对撞机。 所有物理方法都接受默认为所有图层的图层蒙版参数。 使用此选项可以过滤冲突检查,并通过不执行不必要的冲突检查来使性能保持最佳状态。
### 使用物理系统
射线检测对于检查敌人的视线、探测实体的空间环境、快速移动的子弹等各种事情都非常有用。下面是一个从头到尾投射线条的示例,如果击中某个物体,它只会记录数据:
```ts
const hit = es.Physics.linecast( start, end );
if( hit.collider != null )
console.log( `ray hit ${hit}, entity: {hit.collider.entity}`);
```
我们使用了一些更先进的碰撞/重叠检查方法,如Minkowski和、分离轴定理和古老的三角法。这些都被包装在Collider类上的简单易用的方法中。让我们看一些例子。
第一个例子是处理碰撞的最简单方法。deltaMovement是您希望移动实体的量,通常是velocity*Time.deltaTime. collidesWithAny方法将检查所有碰撞并调整deltaMovement以解决任何碰撞。
```ts
// 碰撞结果将包含一些非常有用的信息,例如被撞的collider,表面命中的法线和最小平移矢量(MTV)。 MTV可用于将碰撞实体直接移动到命中的碰撞器附近。
let collisionResult = null;
// 进行检查以查看entity.getComponent(Collider)(实体上的第一个碰撞器)是否与场景中的任何其他碰撞器发生碰撞。请注意,如果您有多个碰撞器,则可以获取并遍历它们,而不是仅检查第一个碰撞器。
if( entity.getComponent(es.Collider).collidesWithAny( deltaMovement, collisionResult ) )
{
// 记录CollisionResult。 您可能需要使用它来添加一些粒子效果或与您的游戏相关的任何其他内容。
console.log( `collision result: ${collisionResult}` );
}
// 将实体移到新位置。 已经调整了deltaMovement为我们解决冲突。
entity.position = entity.position.add(deltaMovement);
```
如果您需要对碰撞发生时的情况进行更多控制,则也可以手动检查是否与其他collider发生碰撞。 请注意,执行此操作时,deltaMovement不会为您调整。 解决冲突时,您需要考虑最小平移矢量。
```ts
let collisionResult = null;
// 进行检查以查看entity.getComponent<Collider>是否与一些其他Collider发生碰撞
if( entity.getComponent(es.Collider).collidesWith( someOtherCollider, deltaMovement, collisionResult ) )
{
// 将实体移动到与命中Collider相邻的位置,然后记录CollisionResult
entity.position = entity.position.add(deltaMovement.sub(collisionResult.minimumTranslationVector));
console.log( `collision result: ${collisionResult}` );
}
```
我们可以使用前面提到的Physics.boxcastBroadphase方法或更具体地讲,将自身排除在查询之外的版本,使上述示例更进一步。 该方法将为我们提供场景中所有在我们附近的collider,然后我们可以使用这些对撞机进行实际的碰撞检查。
```ts
// 在我们自身以外的位置获取可能与之重叠的任何东西
let neighborColliders = es.Physics.boxcastBroadphaseExcludingSelf( entity.getComponent(es.Collider) );
// 遍历并检查每个对撞机是否重叠
for( let collider of neighborColliders )
{
if( entity.getComponent(es.Collider).overlaps( collider ) )
console.log( `我们正在重叠一个collider : ${collider}` );
}
```
+325
View File
@@ -0,0 +1,325 @@
# QuerySystem 使用指南
QuerySystem 是 ECS Framework 中的高性能实体查询系统,支持多级索引、智能缓存和类型安全的查询操作。
## 基本用法
### 1. 获取查询系统
```typescript
import { Scene } from './ECS/Scene';
import { Entity } from './ECS/Entity';
// 创建场景,查询系统会自动创建
const scene = new Scene();
const querySystem = scene.querySystem;
// 或者从Core获取当前场景的查询系统
import { Core } from './Core';
const currentQuerySystem = Core.scene?.querySystem;
```
### 2. 基本查询操作
```typescript
// 查询包含所有指定组件的实体
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
console.log(`找到 ${result.count} 个实体`);
// 查询包含任意指定组件的实体
const anyResult = querySystem.queryAny(HealthComponent, ManaComponent);
// 查询不包含指定组件的实体
const noneResult = querySystem.queryNone(DeadComponent);
```
### 3. 类型安全查询
```typescript
// 类型安全的查询,返回实体和对应的组件
const typedResult = querySystem.queryAllTyped(PositionComponent, VelocityComponent);
for (let i = 0; i < typedResult.entities.length; i++) {
const entity = typedResult.entities[i];
const [position, velocity] = typedResult.components[i];
// position 和 velocity 都是类型安全的
}
// 查询单个组件类型
const healthEntities = querySystem.queryComponentTyped(HealthComponent);
healthEntities.forEach(({ entity, component }) => {
console.log(`实体 ${entity.name} 的生命值: ${component.value}`);
});
// 查询两个组件类型
const movableEntities = querySystem.queryTwoComponents(PositionComponent, VelocityComponent);
movableEntities.forEach(({ entity, component1: position, component2: velocity }) => {
// 更新位置
position.x += velocity.x;
position.y += velocity.y;
});
```
### 4. 使用查询构建器
```typescript
// 创建复杂查询
const query = querySystem.createQuery()
.withAll(PositionComponent, RenderComponent)
.without(HiddenComponent)
.withTag(1) // 特定标签
.orderByName()
.limit(10);
const result = query.execute();
// 链式操作
const visibleEnemies = querySystem.createQuery()
.withAll(EnemyComponent, PositionComponent)
.without(DeadComponent, HiddenComponent)
.filter(entity => entity.name.startsWith('Boss'))
.orderBy((a, b) => a.id - b.id);
// 迭代结果
visibleEnemies.forEach((entity, index) => {
console.log(`敌人 ${index}: ${entity.name}`);
});
```
### 5. 高级查询功能
```typescript
// 复合查询
const complexResult = querySystem.queryComplex(
{
type: QueryConditionType.ALL,
componentTypes: [PositionComponent, VelocityComponent],
mask: /* 位掩码 */
},
{
type: QueryConditionType.NONE,
componentTypes: [DeadComponent],
mask: /* 位掩码 */
}
);
// 批量查询
const batchResults = querySystem.batchQuery([
{ type: QueryConditionType.ALL, componentTypes: [HealthComponent], mask: /* 位掩码 */ },
{ type: QueryConditionType.ALL, componentTypes: [ManaComponent], mask: /* 位掩码 */ }
]);
// 并行查询
const parallelResults = await querySystem.parallelQuery([
{ type: QueryConditionType.ALL, componentTypes: [PositionComponent], mask: /* 位掩码 */ },
{ type: QueryConditionType.ALL, componentTypes: [VelocityComponent], mask: /* 位掩码 */ }
]);
```
## 场景级别的实体查询
除了使用QuerySystem,您还可以直接使用Scene提供的便捷查询方法:
### 基本场景查询
```typescript
// 按名称查找实体
const player = scene.findEntity("Player");
const playerAlt = scene.getEntityByName("Player"); // 别名方法
// 按ID查找实体
const entity = scene.findEntityById(123);
// 按标签查找实体
const enemies = scene.findEntitiesByTag(2);
const enemiesAlt = scene.getEntitiesByTag(2); // 别名方法
// 获取所有实体
const allEntities = scene.entities.buffer;
```
## 性能优化
### 1. 缓存管理
```typescript
// 设置缓存配置
querySystem.setCacheConfig(200, 2000); // 最大200个缓存项,2秒超时
// 清空缓存
querySystem.clearCache();
// 预热常用查询
const commonQueries = [
{ type: QueryConditionType.ALL, componentTypes: [PositionComponent], mask: /* 位掩码 */ },
{ type: QueryConditionType.ALL, componentTypes: [VelocityComponent], mask: /* 位掩码 */ }
];
querySystem.warmUpCache(commonQueries);
```
### 2. 索引优化
```typescript
// 自动优化索引配置
querySystem.optimizeIndexes();
// 获取性能统计
const stats = querySystem.getStats();
console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`);
console.log(`实体数量: ${stats.entityCount}`);
// 获取详细性能报告
const report = querySystem.getPerformanceReport();
console.log(report);
```
### 3. 查询监听和快照
```typescript
// 监听查询结果变更
const unwatch = querySystem.watchQuery(
{ type: QueryConditionType.ALL, componentTypes: [EnemyComponent], mask: /* 位掩码 */ },
(entities, changeType) => {
console.log(`敌人实体${changeType}: ${entities.length}`);
}
);
// 取消监听
unwatch();
// 创建查询快照
const snapshot1 = querySystem.createSnapshot(
{ type: QueryConditionType.ALL, componentTypes: [PlayerComponent], mask: /* 位掩码 */ }
);
// 稍后创建另一个快照
const snapshot2 = querySystem.createSnapshot(
{ type: QueryConditionType.ALL, componentTypes: [PlayerComponent], mask: /* 位掩码 */ }
);
// 比较快照
const diff = querySystem.compareSnapshots(snapshot1, snapshot2);
console.log(`新增: ${diff.added.length}, 移除: ${diff.removed.length}`);
```
## 实际使用示例
### 移动系统示例
```typescript
import { EntitySystem } from './ECS/Systems/EntitySystem';
class MovementSystem extends EntitySystem {
public update(): void {
// 查询所有可移动的实体
const movableEntities = this.scene.querySystem.queryTwoComponents(
PositionComponent,
VelocityComponent
);
movableEntities.forEach(({ entity, component1: position, component2: velocity }) => {
// 更新位置
position.x += velocity.x * Time.deltaTime;
position.y += velocity.y * Time.deltaTime;
// 边界检查
if (position.x < 0 || position.x > 800) {
velocity.x = -velocity.x;
}
if (position.y < 0 || position.y > 600) {
velocity.y = -velocity.y;
}
});
}
}
```
### 碰撞检测示例
```typescript
class CollisionSystem extends EntitySystem {
public update(): void {
// 获取所有具有碰撞器的实体
const collidableEntities = this.scene.querySystem.queryTwoComponents(
PositionComponent,
ColliderComponent
);
// 检测碰撞
for (let i = 0; i < collidableEntities.length; i++) {
for (let j = i + 1; j < collidableEntities.length; j++) {
const entityA = collidableEntities[i];
const entityB = collidableEntities[j];
if (this.checkCollision(entityA, entityB)) {
this.handleCollision(entityA.entity, entityB.entity);
}
}
}
}
private checkCollision(entityA: any, entityB: any): boolean {
// 简单的距离检测
const posA = entityA.component1;
const posB = entityB.component1;
const distance = Math.sqrt(
Math.pow(posA.x - posB.x, 2) + Math.pow(posA.y - posB.y, 2)
);
return distance < 50;
}
private handleCollision(entityA: Entity, entityB: Entity): void {
console.log(`碰撞检测: ${entityA.name}${entityB.name}`);
}
}
```
### 生命值管理示例
```typescript
class HealthSystem extends EntitySystem {
public update(): void {
// 查询所有具有生命值的实体
const healthEntities = this.scene.querySystem.queryComponentTyped(HealthComponent);
const deadEntities: Entity[] = [];
healthEntities.forEach(({ entity, component: health }) => {
// 检查死亡
if (health.currentHealth <= 0) {
deadEntities.push(entity);
}
});
// 移除死亡实体
deadEntities.forEach(entity => {
console.log(`实体 ${entity.name} 已死亡`);
entity.destroy();
});
}
}
```
## 最佳实践
### 1. 查询优化
- 尽量使用类型安全的查询方法
- 避免在每帧都创建新的查询
- 合理使用缓存机制
### 2. 性能监控
- 定期检查查询性能报告
- 监控缓存命中率
- 优化频繁使用的查询
### 3. 内存管理
- 及时清理不需要的查询监听器
- 合理设置缓存大小
- 避免创建过多的查询快照
### 4. 代码组织
- 将复杂查询封装到专门的方法中
- 使用查询构建器创建可读性更好的查询
- 在系统中合理组织查询逻辑
-2
View File
@@ -1,2 +0,0 @@
## 渲染框架
为了方便快速与引擎对接使用,框架内置了一套渲染框架,它仅仅只有接口,你需要实现框架所需要的内容才可以进行渲染。
-71
View File
@@ -1,71 +0,0 @@
# scene_component
这是一个场景组件的基类,如果您需要一个不在实体上的组件则继承它 `es.SceneComponent`。场景组件默认包含`update`/`onEnabled`/`onDisabled`/`onRemovedFromScene`,你可以对他们进行重载。
```typescript
export class ASceneComponent extends es.SceneComponent {
/**
* 在启用此SceneComponent时调用
*/
onEnabled() {
}
/**
* 当禁用此SceneComponent时调用
*/
onDisabled() {
}
/**
* 当该SceneComponent从场景中移除时调用
*/
onRemovedFromScene() {
}
update() {
}
}
```
- 场景组件需要添加至场景上, 通过场景中的 `addSceneComponent` 方法加入。
```typescript
export class MainScene extends es.Scene {
onStart() {
const aSceneCom = this.addSceneComponent(new ASceneComponent());
}
}
```
- 如果想要获取该场景组件则通过`getSceneComponent`方法获取
```typescript
export class MainScene extends es.Scene {
onStart() {
const aSceneCom = this.getSceneComponent(ASceneComponent);
}
}
```
- 如果获取时发现没有可以自动创建则通过 `getOrCreateSceneComponent` 方法
```typescript
export class MainScene extends es.Scene {
onStart() {
const aSceneCom = this.getOrCreateSceneComponent(ASceneComponent);
}
}
```
- 删除场景组件
```typescript
export class MainScene extends es.Scene {
onStart() {
this.removeSceneComponent(aSceneCom);
}
}
```
-67
View File
@@ -1,67 +0,0 @@
# system
系统是ecs的核心。你的游戏逻辑应该在这里进行处理,所有的实体及组件也会在这里进行集中处理。用于处理实体的系统叫做 `es.EntityProcessingSystem`。 你需要继承他并实现`processEntity(entity: Entity)`方法。
```typescript
export class ASystem extends es.EntityProcessingSystem {
processEntity(entity: Entity){
}
}
```
系统也依赖于场景,如果想要系统被激活则需要使用场景中`addEntityProcessor`方法。系统被实例化需要传入一个`es.Matcher` 参数。
```typescript
export class MainScene extends es.Scene {
onStart() {
this.addEntityProcessor(new ASystem(es.Matcher.empty().all(AComponent)));
}
}
```
## Matcher
matcher是系统的匹配器,用于匹配满足条件的实体传入系统进行处理。如果想要一个空的匹配器则直接 `es.Matcher.empty()`
- all
同时拥有多个组件
```typescript
es.Matcher.empty().all(AComponent, BComponent);
```
- one
拥有任意一个组件
```typescript
es.Matcher.empty().one(AComponent, BComponent);
```
- exclude
拥有某些组件,并且不包含某些组件
```typescript
// 不包含CComponent或者DComponent
es.Matcher.empty().all(AComponent, BComponent).exclude(CComponent, DComponent);
// 不同时包含CComponent和DComponent
es.Matcher.empty().all(AComponent, BComponent).exclude(CComponent).exclude(DComponent);
```
## 获取系统
```typescript
export class MainScene extends es.Scene {
onStart() {
const aSystem = this.getEntityProcessor(ASystem);
}
}
```
## 移除系统
```typescript
export class MainScene extends es.Scene {
onStart() {
this.removeEntityProcessor(aSystem);
}
}
```
-23
View File
@@ -1,23 +0,0 @@
# Time
游戏中会经常使用到关于时间类。框架内提供了关于时间的多个属性
## 游戏运行的总时间
`es.Time.totalTime`
## deltaTime的未缩放版本。不受时间尺度的影响
`es.Time.unscaledDeltaTime`
## 前一帧到当前帧的时间增量(按时间刻度进行缩放)
`es.Time.deltaTime`
## 时间刻度缩放
`es.Time.timeScale`
## DeltaTime可以为的最大值
`es.Time.maxDeltaTime` 默认为Number.MAX_VALUE
## 已传递的帧总数
`es.Time.frameCount`
## 自场景加载以来的总时间
`es.Time.timeSinceSceneLoad`
+13
View File
@@ -0,0 +1,13 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
},
"modules": "auto"
}
]
]
}
+34
View File
@@ -0,0 +1,34 @@
# 编译输出目录
bin/
# 依赖
node_modules/
# 日志文件
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 环境文件
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# 编辑器配置
.vscode/
.idea/
*.swp
*.swo
# 操作系统文件
.DS_Store
Thumbs.db
# 测试覆盖率
coverage/
# 临时文件
*.tmp
*.temp
+57
View File
@@ -0,0 +1,57 @@
# 源码文件(只发布编译后的文件)
src/
tsconfig.json
gulpfile.js
build.config.js
.babelrc
# 开发工具配置
.vscode/
.idea/
.wing/
# 依赖和缓存
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 测试文件
*.test.js
*.test.ts
*.spec.js
*.spec.ts
test/
tests/
__tests__/
coverage/
# 构建工具
.nyc_output
.cache
# 环境文件
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# 临时文件
*.tmp
*.temp
.DS_Store
Thumbs.db
# 日志文件
logs/
*.log
# 编辑器文件
*.swp
*.swo
*~
# 其他
.git/
.gitignore
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-6954
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-1
View File
File diff suppressed because one or more lines are too long
+97
View File
@@ -0,0 +1,97 @@
/**
* ECS框架构建配置
* 针对Laya、Cocos等游戏引擎优化
*/
const path = require('path');
module.exports = {
// 输入配置
input: {
entry: './src/index.ts',
tsconfig: './tsconfig.json'
},
// 输出配置
output: {
dir: './bin',
formats: ['es', 'cjs', 'umd'], // ES模块、CommonJS、UMD
filename: {
es: 'ecs-framework.esm.js',
cjs: 'ecs-framework.cjs.js',
umd: 'ecs-framework.umd.js'
},
minify: true,
sourcemap: true
},
// 游戏引擎特定配置
engines: {
laya: {
// Laya引擎特定优化
target: 'es5',
polyfills: ['Promise', 'Object.assign'],
globals: ['Laya']
},
cocos: {
// Cocos引擎特定优化
target: 'es6',
polyfills: [],
globals: ['cc']
}
},
// 小游戏平台优化
platforms: {
wechat: {
// 微信小游戏优化
maxSize: '4MB',
treeshaking: true,
compression: 'gzip'
},
alipay: {
// 支付宝小游戏优化
maxSize: '4MB',
treeshaking: true,
compression: 'gzip'
},
bytedance: {
// 字节跳动小游戏优化
maxSize: '4MB',
treeshaking: true,
compression: 'gzip'
}
},
// 性能优化配置
optimization: {
// 启用Tree Shaking
treeshaking: true,
// 代码分割
codeSplitting: false, // 小游戏通常不需要代码分割
// 压缩配置
minify: {
removeComments: true,
removeConsole: false, // 保留console用于调试
removeDebugger: true
},
// 内联小文件
inlineThreshold: 1024
},
// 开发配置
development: {
sourcemap: true,
hotReload: false, // 小游戏不支持热重载
debugMode: true
},
// 生产配置
production: {
sourcemap: false,
minify: true,
optimization: true,
bundleAnalyzer: true
}
};
+134 -1
View File
@@ -5,7 +5,15 @@ const terser = require('gulp-terser');
const inject = require('gulp-inject-string');
const ts = require('gulp-typescript');
const merge = require('merge2');
const tsProject = ts.createProject('tsconfig.json');
const typescript = require('gulp-typescript');
const concat = require('gulp-concat');
const replace = require('gulp-string-replace');
// TypeScript项目配置
const tsProject = ts.createProject('tsconfig.json', {
module: 'es2020',
target: 'es2018'
});
function buildJs() {
return tsProject.src()
@@ -24,6 +32,131 @@ function buildDts() {
.pipe(gulp.dest('./bin'));
}
// 构建ES模块版本
gulp.task('build-esm', function() {
return gulp.src(['src/**/*.ts'])
.pipe(tsProject())
.pipe(concat('ecs-framework.esm.js'))
.pipe(inject.prepend('// ECS Framework - ES Module Version\n'))
.pipe(gulp.dest('bin/'));
});
// 构建UMD版本(兼容各种游戏引擎)
gulp.task('build-umd', function() {
const umdProject = ts.createProject('tsconfig.json', {
module: 'umd',
target: 'es5'
});
return gulp.src(['src/**/*.ts'])
.pipe(umdProject())
.pipe(concat('ecs-framework.umd.js'))
.pipe(inject.prepend(`// ECS Framework - UMD Version
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.ECS = {}));
}(this, (function (exports) { 'use strict';
`))
.pipe(inject.append('\n})));'))
.pipe(gulp.dest('bin/'));
});
// 构建Laya引擎专用版本
gulp.task('build-laya', function() {
const layaProject = ts.createProject('tsconfig.json', {
module: 'none',
target: 'es5'
});
return gulp.src(['src/**/*.ts'])
.pipe(layaProject())
.pipe(concat('ecs-framework.laya.js'))
.pipe(replace(/export\s+/g, ''))
.pipe(inject.prepend(`// ECS Framework - Laya Engine Version
var ECS = ECS || {};
(function(ECS) {
`))
.pipe(inject.append('\n})(ECS);'))
.pipe(gulp.dest('bin/'));
});
// 构建Cocos引擎专用版本
gulp.task('build-cocos', function() {
const cocosProject = ts.createProject('tsconfig.json', {
module: 'es2020',
target: 'es2018'
});
return gulp.src(['src/**/*.ts'])
.pipe(cocosProject())
.pipe(concat('ecs-framework.cocos.js'))
.pipe(inject.prepend('// ECS Framework - Cocos Engine Version\n'))
.pipe(gulp.dest('bin/'));
});
// 压缩所有构建文件
gulp.task('minify', function() {
return gulp.src(['bin/*.js', '!bin/*.min.js'])
.pipe(terser({
compress: {
drop_console: false, // 保留console用于调试
drop_debugger: true,
pure_funcs: ['console.log'] // 可选择性移除console.log
},
mangle: {
keep_fnames: true // 保留函数名用于调试
},
format: {
comments: false
}
}))
.pipe(concat(function(file) {
return file.basename.replace('.js', '.min.js');
}))
.pipe(gulp.dest('bin/'));
});
// 生成类型定义文件
gulp.task('build-types', function() {
const dtsProject = ts.createProject('tsconfig.json', {
declaration: true,
emitDeclarationOnly: true
});
return gulp.src(['src/**/*.ts'])
.pipe(dtsProject())
.pipe(concat('ecs-framework.d.ts'))
.pipe(gulp.dest('bin/'));
});
// 清理构建目录
gulp.task('clean', function() {
const del = require('del');
return del(['bin/**/*']);
});
// 开发构建(快速,包含源码映射)
gulp.task('build-dev', gulp.series('clean', 'build-esm', 'build-types'));
// 生产构建(完整,包含所有版本和压缩)
gulp.task('build', gulp.series(
'clean',
gulp.parallel('build-esm', 'build-umd', 'build-laya', 'build-cocos'),
'build-types',
'minify'
));
// 监听文件变化
gulp.task('watch', function() {
gulp.watch('src/**/*.ts', gulp.series('build-dev'));
});
// 默认任务
gulp.task('default', gulp.series('build'));
exports.buildJs = buildJs;
exports.buildDts = buildDts;
exports.build = series(buildJs, buildDts);
+389 -4
View File
@@ -1,12 +1,12 @@
{
"name": "@esengine/egret-framework",
"version": "1.0.1",
"name": "@esengine/ecs-framework",
"version": "2.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@esengine/egret-framework",
"version": "1.0.1",
"name": "@esengine/ecs-framework",
"version": "2.0.4",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.27.4",
@@ -21,6 +21,7 @@
"gulp-typescript": "^6.0.0-alpha.1",
"gulp-uglify": "^3.0.2",
"merge2": "^1.4.1",
"rimraf": "^5.0.0",
"tsify": "^5.0.4",
"typedoc": "^0.28.5",
"typescript": "^5.8.3",
@@ -1563,6 +1564,109 @@
"node": ">=10.13.0"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -1627,6 +1731,17 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.6.0.tgz",
@@ -2815,6 +2930,37 @@
"sha.js": "^2.4.8"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/crypto-browserify": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz",
@@ -3057,6 +3203,13 @@
"node": ">= 10.13.0"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.165",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz",
@@ -3427,6 +3580,23 @@
"node": ">=0.10.0"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/from": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
@@ -4815,6 +4985,22 @@
"node": ">=0.10.0"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5148,6 +5334,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -5352,6 +5548,13 @@
"shell-quote": "^1.4.2"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -5459,6 +5662,16 @@
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -5499,6 +5712,30 @@
"node": ">=0.10.0"
}
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/pause-stream": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
@@ -5990,6 +6227,69 @@
"node": ">=0.10.0"
}
},
"node_modules/rimraf": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
@@ -6112,6 +6412,29 @@
"fast-safe-stringify": "^2.0.7"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
@@ -6201,6 +6524,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@@ -6425,6 +6761,22 @@
"node": ">=8"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -6438,6 +6790,20 @@
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -7286,6 +7652,25 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+36 -10
View File
@@ -1,14 +1,39 @@
{
"name": "@esengine/egret-framework",
"version": "1.0.1",
"description": "用于egret 包含众多高性能方法以供使用",
"main": "index.js",
"directories": {
"lib": "lib"
},
"name": "@esengine/ecs-framework",
"version": "2.0.4",
"description": "用于Laya、Cocos等游戏引擎的高性能ECS框架",
"main": "bin/index.js",
"types": "bin/index.d.ts",
"files": [
"bin/**/*",
"README.md",
"LICENSE"
],
"keywords": [
"ecs",
"entity-component-system",
"game-engine",
"typescript",
"laya",
"cocos",
"egret"
],
"scripts": {
"test": "mocha --recursive --reporter tap --growl",
"eslint": "eslint src --ext .ts"
"build": "tsc",
"build:watch": "tsc --watch",
"clean": "rimraf bin",
"rebuild": "npm run clean && npm run build",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix",
"prepublishOnly": "npm run rebuild",
"publish:patch": "npm run rebuild && npm version patch && npm publish",
"publish:minor": "npm run rebuild && npm version minor && npm publish",
"publish:major": "npm run rebuild && npm version major && npm publish",
"pack:check": "npm run rebuild && npm pack --dry-run",
"check": "node scripts/check-publish.js"
},
"author": "yhh",
"license": "MIT",
@@ -25,6 +50,7 @@
"gulp-typescript": "^6.0.0-alpha.1",
"gulp-uglify": "^3.0.2",
"merge2": "^1.4.1",
"rimraf": "^5.0.0",
"tsify": "^5.0.4",
"typedoc": "^0.28.5",
"typescript": "^5.8.3",
@@ -32,7 +58,7 @@
"watchify": "^4.0.0"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com/359807859@qq.com"
"access": "public"
},
"repository": {
"type": "git",
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
console.log('🔍 发布前检查...\n');
// 检查必要文件
const requiredFiles = [
'package.json',
'README.md',
'LICENSE',
'bin/index.js',
'bin/index.d.ts'
];
let allFilesExist = true;
requiredFiles.forEach(file => {
if (fs.existsSync(file)) {
console.log(`${file} 存在`);
} else {
console.log(`${file} 不存在`);
allFilesExist = false;
}
});
// 检查package.json配置
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
console.log('\n📦 Package.json 检查:');
console.log(`✅ 包名: ${packageJson.name}`);
console.log(`✅ 版本: ${packageJson.version}`);
console.log(`✅ 主入口: ${packageJson.main}`);
console.log(`✅ 类型定义: ${packageJson.types}`);
// 检查bin目录
if (fs.existsSync('bin')) {
const binFiles = fs.readdirSync('bin', { recursive: true });
const jsFiles = binFiles.filter(f => f.endsWith('.js')).length;
const dtsFiles = binFiles.filter(f => f.endsWith('.d.ts')).length;
console.log(`\n🏗️ 编译文件检查:`);
console.log(`✅ JavaScript 文件: ${jsFiles}`);
console.log(`✅ 类型定义文件: ${dtsFiles}`);
} else {
console.log('\n❌ bin 目录不存在,请先运行 npm run build');
allFilesExist = false;
}
// 检查git状态
const { execSync } = require('child_process');
try {
const gitStatus = execSync('git status --porcelain', { encoding: 'utf8' });
if (gitStatus.trim()) {
console.log('\n⚠️ Git 状态检查:');
console.log('有未提交的更改,建议先提交代码');
} else {
console.log('\n✅ Git 状态: 工作目录干净');
}
} catch (e) {
console.log('\n⚠️ 无法检查git状态');
}
console.log('\n' + '='.repeat(50));
if (allFilesExist) {
console.log('🎉 所有检查通过!可以发布了');
console.log('\n发布命令:');
console.log(' npm run publish:patch # 补丁版本');
console.log(' npm run publish:minor # 次要版本');
console.log(' npm run publish:major # 主要版本');
} else {
console.log('❌ 检查失败,请修复问题后再发布');
process.exit(1);
}
+316 -241
View File
@@ -1,262 +1,337 @@
module es {
import { Emitter } from './Utils/Emitter';
import { CoreEvents } from './ECS/CoreEvents';
import { GlobalManager } from './Utils/GlobalManager';
import { TimerManager } from './Utils/Timers/TimerManager';
import { ITimer } from './Utils/Timers/ITimer';
import { Time } from './Utils/Time';
import { PerformanceMonitor } from './Utils/PerformanceMonitor';
import { PoolManager } from './Utils/Pool';
import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI';
import { Scene } from './ECS/Scene';
/**
* 游戏引擎核心类
*
* 负责管理游戏的生命周期、场景切换、全局管理器和定时器系统。
* 提供统一的游戏循环和事件分发机制。
*
* @example
* ```typescript
* // 创建核心实例
* const core = Core.create(true);
*
* // 设置场景
* Core.scene = new MyScene();
*
* // 调度定时器
* Core.schedule(1.0, false, null, (timer) => {
* console.log("1秒后执行");
* });
* ```
*/
export class Core {
/**
* 全局核心类
* 核心事件发射器
*
* 用于发布和订阅核心级别的事件,如帧更新、场景切换等。
*/
export class Core {
/**
* 核心发射器。只发出核心级别的事件
*/
public static emitter: Emitter<CoreEvents>;
public static paused = false;
/**
* 是否启用调试渲染
*/
public static debugRenderEndabled = false;
/**
* 简化对内部类的全局内容实例的访问
*/
private static _instance: Core;
/**
* 用于确定是否应该使用EntitySystems
*/
public static entitySystemsEnabled: boolean;
/**
* 是否正在debug模式
* 仅允许在create时进行更改
*/
public readonly debug: boolean;
public _nextScene: Scene;
public _sceneTransition: SceneTransition;
public static emitter: Emitter<CoreEvents>;
/**
* 用于凝聚GraphicsDeviceReset事件
*/
public _graphicsDeviceChangeTimer: ITimer;
/**
* 全局访问系统
*/
public _globalManagers: GlobalManager[] = [];
public _coroutineManager: CoroutineManager = new CoroutineManager();
public _timerManager: TimerManager = new TimerManager();
/**
* 游戏暂停状态
*
* 当设置为true时,游戏循环将暂停执行。
*/
public static paused = false;
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
Core._instance = this;
Core.emitter = new Emitter<CoreEvents>();
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
/**
* 全局核心实例
*/
private static _instance: Core;
Core.registerGlobalManager(this._coroutineManager);
Core.registerGlobalManager(new TweenManager());
Core.registerGlobalManager(this._timerManager);
Core.entitySystemsEnabled = enableEntitySystems;
/**
* 实体系统启用状态
*
* 控制是否启用ECS实体系统功能。
*/
public static entitySystemsEnabled: boolean;
this.debug = debug;
this.initialize();
}
/**
* 调试模式标志
*
* 在调试模式下会启用额外的性能监控和错误检查。
*/
public readonly debug: boolean;
/**
* 提供对单例/游戏实例的访问
* @constructor
*/
public static get Instance() {
return this._instance;
}
/**
* 待切换的场景
*
* 存储下一帧要切换到的场景实例。
*/
public _nextScene: Scene | null = null;
public _frameCounterElapsedTime: number = 0;
public _frameCounter: number = 0;
public _totalMemory: number = 0;
public _titleMemory: (totalMemory: number, frameCounter: number) => void;
public _scene: Scene;
/**
* 全局管理器集合
*
* 存储所有注册的全局管理器实例。
*/
public _globalManagers: GlobalManager[] = [];
/**
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
*/
public static get scene() {
if (!this._instance)
return null;
return this._instance._scene;
}
/**
* 定时器管理器
*
* 负责管理所有的游戏定时器。
*/
public _timerManager: TimerManager;
/**
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
* @param value
*/
public static set scene(value: Scene) {
Insist.isNotNull(value, "场景不能为空");
/**
* 性能监控器
*
* 监控游戏性能并提供优化建议。
*/
public _performanceMonitor: PerformanceMonitor;
if (this._instance._scene == null) {
this._instance._scene = value;
this._instance.onSceneChanged();
this._instance._scene.begin();
} else {
this._instance._nextScene = value;
}
}
/**
* 对象池管理器
*
* 管理所有对象池的生命周期。
*/
public _poolManager: PoolManager;
/**
* `Core`类的静态方法,用于创建`Core`的实例。
* @param debug {boolean} 是否为调试模式,默认为`true`
* @returns {Core} `Core`的实例
*/
public static create(debug: boolean = true): Core {
// 如果实例还未被创建,则创建一个新的实例并保存在`_instance`静态属性中
if (this._instance == null) {
this._instance = new es.Core(debug);
}
// 返回`_instance`静态属性中保存的实例
return this._instance;
}
/**
* ECS流式API
*
* 提供便捷的ECS操作接口。
*/
public _ecsAPI?: ECSFluentAPI;
/**
* 添加一个全局管理器对象,它的更新方法将调用场景前的每一帧。
* @param manager
*/
public static registerGlobalManager(manager: es.GlobalManager) {
this._instance._globalManagers.push(manager);
manager.enabled = true;
}
/**
* 当前活动场景
*/
public _scene?: Scene;
/**
* 删除全局管理器对象
* @param manager
*/
public static unregisterGlobalManager(manager: GlobalManager) {
new es.List(this._instance._globalManagers).remove(manager);
manager.enabled = false;
}
/**
* 创建核心实例
*
* @param debug - 是否启用调试模式,默认为true
* @param enableEntitySystems - 是否启用实体系统,默认为true
*/
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
Core._instance = this;
Core.emitter = new Emitter<CoreEvents>();
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
/**
* 获取指定类型的全局管理器实例
* @param type 管理器类型的构造函数
* @returns 指定类型的全局管理器实例,如果找不到则返回 null
*/
public static getGlobalManager<T extends GlobalManager>(type: new (...args) => T): T {
for (let i = 0, s = Core._instance._globalManagers.length; i < s; ++i) {
let manager = Core._instance._globalManagers[i];
if (manager instanceof type)
return manager;
}
// 初始化管理器
this._timerManager = new TimerManager();
Core.registerGlobalManager(this._timerManager);
// 初始化性能监控器
this._performanceMonitor = PerformanceMonitor.instance;
// 初始化对象池管理器
this._poolManager = PoolManager.getInstance();
Core.entitySystemsEnabled = enableEntitySystems;
this.debug = debug;
this.initialize();
}
/**
* 获取核心实例
*
* @returns 全局核心实例
*/
public static get Instance() {
return this._instance;
}
/**
* 获取当前活动的场景
*
* @returns 当前场景实例,如果没有则返回null
*/
public static get scene(): Scene | null {
if (!this._instance)
return null;
return this._instance._scene || null;
}
/**
* 设置当前活动的场景
*
* 如果当前没有场景,会立即切换;否则会在下一帧切换。
*
* @param value - 要设置的场景实例
* @throws {Error} 当场景为空时抛出错误
*/
public static set scene(value: Scene | null) {
if (!value) return;
if (!value) {
throw new Error("场景不能为空");
}
/**
* 临时运行SceneTransition,允许一个场景平滑过渡到另一个场景,并具有自定义效果
* @param sceneTransition
*/
public static startSceneTransition<T extends SceneTransition>(sceneTransition: T) {
Insist.isNull(this._instance._sceneTransition, "在前一个场景转换完成之前,无法启动新的场景转换");
this._instance._sceneTransition = sceneTransition;
return sceneTransition;
}
/**
* 启动一个coroutine。Coroutine可以将number延时几秒或延时到其他startCoroutine.Yielding
* null将使coroutine在下一帧被执行。
* @param enumerator
*/
public static startCoroutine(enumerator): ICoroutine {
return this._instance._coroutineManager.startCoroutine(enumerator);
}
/**
* 调度一个一次性或重复的计时器,该计时器将调用已传递的动作
* @param timeInSeconds
* @param repeats
* @param context
* @param onTime
*/
public static schedule(timeInSeconds: number, repeats: boolean = false, context: any = null, onTime: (timer: ITimer) => void) {
return this._instance._timerManager.schedule(timeInSeconds, repeats, context, onTime);
}
public startDebugDraw() {
// 如果debug标志未开启,则直接返回
if (!this.debug) return;
// 计算帧率和内存使用情况
this._frameCounter++; // 帧计数器递增
this._frameCounterElapsedTime += Time.deltaTime; // 帧计数器累加时间
if (this._frameCounterElapsedTime >= 1) { // 如果时间已经超过1秒,则计算帧率和内存使用情况
let memoryInfo = window.performance["memory"]; // 获取内存使用情况
if (memoryInfo != null) { // 如果内存使用情况存在
// 计算内存使用情况并保留2位小数
this._totalMemory = Number((memoryInfo.totalJSHeapSize / 1048576).toFixed(2));
}
if (this._titleMemory) { // 如果回调函数存在,则执行回调函数,更新标题栏显示
this._titleMemory(this._totalMemory, this._frameCounter);
}
this._frameCounter = 0; // 重置帧计数器
this._frameCounterElapsedTime -= 1; // 减去1秒时间
}
}
/**
* 在一个场景结束后,下一个场景开始之前调用
*/
public onSceneChanged() {
Core.emitter.emit(CoreEvents.sceneChanged);
Time.sceneChanged();
}
protected initialize() {
}
/**
* `Core` 类的受保护的 `update` 方法,用于更新游戏状态。
* @param currentTime 当前时间戳,单位为毫秒,默认值为-1。
*/
protected update(currentTime: number = -1): void {
// 如果引擎处于暂停状态,则直接返回,不做任何操作
if (Core.paused) {
return;
}
// 更新时间戳信息
Time.update(currentTime, currentTime !== -1);
// 更新全局管理器和当前场景
if (this._scene != null) {
// 依次更新所有启用的全局管理器
for (const globalManager of this._globalManagers) {
if (globalManager.enabled) {
globalManager.update();
}
}
// 如果当前没有场景切换正在进行,或者正在进行的场景切换不需要加载新场景
if (this._sceneTransition == null || !this._sceneTransition._loadsNewScene) {
this._scene.update();
}
}
// 处理场景切换
if (this._nextScene != null) {
// 结束当前场景
this._scene.end();
// 加载并初始化新场景
this._scene = this._nextScene;
this._nextScene = null;
this.onSceneChanged();
this._scene.begin();
}
// 绘制调试信息
this.startDebugDraw();
this.draw();
}
protected draw() {
if (this._sceneTransition != null)
this._sceneTransition.preRender();
if (this._sceneTransition != null && !this._sceneTransition.hasPreviousSceneRender) {
if (this._scene != null) {
Core.startCoroutine(this._sceneTransition.onBeginTransition());
}
this._sceneTransition.render();
}
if (this._instance._scene == null) {
this._instance._scene = value;
this._instance.onSceneChanged();
this._instance._scene.begin();
} else {
this._instance._nextScene = value;
}
}
/**
* 创建Core实例
*
* 如果实例已存在,则返回现有实例。
*
* @param debug - 是否为调试模式,默认为true
* @returns Core实例
*/
public static create(debug: boolean = true): Core {
if (this._instance == null) {
this._instance = new Core(debug);
}
return this._instance;
}
/**
* 注册全局管理器
*
* 将管理器添加到全局管理器列表中,并启用它。
*
* @param manager - 要注册的全局管理器
*/
public static registerGlobalManager(manager: GlobalManager) {
this._instance._globalManagers.push(manager);
manager.enabled = true;
}
/**
* 注销全局管理器
*
* 从全局管理器列表中移除管理器,并禁用它。
*
* @param manager - 要注销的全局管理器
*/
public static unregisterGlobalManager(manager: GlobalManager) {
this._instance._globalManagers.splice(this._instance._globalManagers.indexOf(manager), 1);
manager.enabled = false;
}
/**
* 获取指定类型的全局管理器
*
* @param type - 管理器类型构造函数
* @returns 管理器实例,如果未找到则返回null
*/
public static getGlobalManager<T extends GlobalManager>(type: new (...args: any[]) => T): T | null {
for (const manager of this._instance._globalManagers) {
if (manager instanceof type)
return manager as T;
}
return null;
}
/**
* 调度定时器
*
* 创建一个定时器,在指定时间后执行回调函数。
*
* @param timeInSeconds - 延迟时间(秒)
* @param repeats - 是否重复执行,默认为false
* @param context - 回调函数的上下文,默认为null
* @param onTime - 定时器触发时的回调函数
* @returns 创建的定时器实例
*/
public static schedule(timeInSeconds: number, repeats: boolean = false, context: any = null, onTime: (timer: ITimer) => void) {
return this._instance._timerManager.schedule(timeInSeconds, repeats, context, onTime);
}
/**
* 获取ECS流式API
*
* @returns ECS API实例,如果未初始化则返回null
*/
public static get ecsAPI(): ECSFluentAPI | null {
return this._instance?._ecsAPI || null;
}
/**
* 场景切换回调
*
* 在场景切换时调用,用于重置时间系统等。
*/
public onSceneChanged() {
Time.sceneChanged();
// 初始化ECS API(如果场景支持)
if (this._scene && typeof (this._scene as any).querySystem !== 'undefined') {
const scene = this._scene as any;
this._ecsAPI = createECSAPI(scene, scene.querySystem, scene.eventSystem);
}
}
/**
* 初始化核心系统
*
* 执行核心系统的初始化逻辑。
*/
protected initialize() {
// 核心系统初始化
}
/**
* 游戏主循环更新
*
* 每帧调用,负责更新时间系统、全局管理器和当前场景。
*
* @param currentTime - 当前时间戳,默认为-1(使用系统时间)
*/
protected update(currentTime: number = -1): void {
if (Core.paused) return;
// 开始性能监控
const frameStartTime = this._performanceMonitor.startMonitoring('Core.update');
Time.update(currentTime);
// 更新FPS监控(如果性能监控器支持)
if (typeof (this._performanceMonitor as any).updateFPS === 'function') {
(this._performanceMonitor as any).updateFPS(Time.deltaTime);
}
// 更新全局管理器
const managersStartTime = this._performanceMonitor.startMonitoring('GlobalManagers.update');
for (const globalManager of this._globalManagers) {
if (globalManager.enabled)
globalManager.update();
}
this._performanceMonitor.endMonitoring('GlobalManagers.update', managersStartTime, this._globalManagers.length);
// 更新对象池管理器
this._poolManager.update();
// 处理场景切换
if (this._nextScene != null) {
if (this._scene != null)
this._scene.end();
this._scene = this._nextScene;
this._nextScene = null;
this.onSceneChanged();
this._scene.begin();
}
// 更新当前场景
if (this._scene != null && this._scene.update) {
const sceneStartTime = this._performanceMonitor.startMonitoring('Scene.update');
this._scene.update();
const entityCount = (this._scene as any).entities?.count || 0;
this._performanceMonitor.endMonitoring('Scene.update', sceneStartTime, entityCount);
}
// 结束性能监控
this._performanceMonitor.endMonitoring('Core.update', frameStartTime);
}
}
-5
View File
@@ -1,5 +0,0 @@
module es {
export class DebugConsole {
public static Instance: DebugConsole;
}
}
-68
View File
@@ -1,68 +0,0 @@
module es {
export enum LogType {
error,
warn,
log,
info,
trace
}
export class Debug {
/**
* 如果条件为true,则在控制台中以警告方式打印消息。
* @param condition 是否应该打印消息的条件
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static warnIf(condition: boolean, format: string, ...args: any[]) {
if (condition)
this.log(LogType.warn, format, args);
}
/**
* 在控制台中以警告方式打印消息。
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static warn(format: string, ...args: any[]) {
this.log(LogType.warn, format, args);
}
/**
* 在控制台中以错误方式打印消息。
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static error(format: string, ...args: any[]) {
this.log(LogType.error, format, args);
}
/**
* 在控制台中以标准日志方式打印消息。
* @param type 要打印的日志类型
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static log(type: LogType, format: string, ...args: any[]) {
switch (type) {
case LogType.error:
console.error(`${type}: ${StringUtils.format(format, args)}`);
break;
case LogType.warn:
console.warn(`${type}: ${StringUtils.format(format, args)}`);
break;
case LogType.log:
console.log(`${type}: ${StringUtils.format(format, args)}`);
break;
case LogType.info:
console.info(`${type}: ${StringUtils.format(format, args)}`);
break;
case LogType.trace:
console.trace(`${type}: ${StringUtils.format(format, args)}`);
break;
default:
throw new Error('argument out of range');
}
}
}
}
-20
View File
@@ -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;
}
}
-59
View File
@@ -1,59 +0,0 @@
module es {
export class Insist {
public static fail(message: string = null, ...args: any[]) {
if (!console.assert)
return;
if (message == null) {
console.assert(false);
} else {
console.assert(false, StringUtils.format(message, args));
}
}
public static isTrue(condition: boolean, message: string = null, ...args: any[]) {
if (!condition) {
if (message == null) {
this.fail();
} else {
this.fail(message, args);
}
}
}
public static isFalse(condition: boolean, message: string = null, ...args: any[]) {
if (message == null) {
this.isTrue(!condition);
} else {
this.isTrue(!condition, message, args);
}
}
public static isNull(obj, message: string = null, ...args: any[]) {
if (message == null) {
this.isTrue(obj == null);
} else {
this.isTrue(obj == null, message, args);
}
}
public static isNotNull(obj, message: string = null, ...args: any[]) {
if (message == null) {
this.isTrue(obj != null);
} else {
this.isTrue(obj != null, message, args);
}
}
public static areEqual(first, second, message: string, ...args: any[]) {
if (first != second)
this.fail(message, args);
}
public static areNotEqual(first, second, message: string, ...args: any[]) {
if (first == second)
this.fail(message, args);
}
}
}
+141 -154
View File
@@ -1,169 +1,156 @@
module es {
/**
* 游戏组件基类
*
* ECS架构中的组件(Component),用于实现具体的游戏功能。
* 组件包含数据和行为,可以被添加到实体上以扩展实体的功能。
*
* @example
* ```typescript
* class HealthComponent extends Component {
* public health: number = 100;
*
* public takeDamage(damage: number): void {
* this.health -= damage;
* if (this.health <= 0) {
* this.entity.destroy();
* }
* }
* }
* ```
*/
export abstract class Component {
/**
* 执行顺序
* - onAddedToEntity
* - OnEnabled
* 组件ID生成器
*
* 删除执行顺序
* - onRemovedFromEntity
* 用于为每个组件分配唯一的ID。
*/
export abstract class Component {
public static _idGenerator: number = 0;
/**
* 此组件的唯一标识
*/
public readonly id: number;
/**
* 此组件附加的实体
*/
public entity: Entity;
public static _idGenerator: number = 0;
constructor() {
this.id = Component._idGenerator++;
}
/**
* 组件唯一标识符
*
* 在整个游戏生命周期中唯一的数字ID。
*/
public readonly id: number;
/**
* 快速访问 this.entity.transform
*/
public get transform(): Transform {
return this.entity.transform;
}
/**
* 组件所属的实体
*
* 指向拥有此组件的实体实例。
*/
public entity!: Entity;
private _enabled: boolean = true;
/**
* 组件启用状态
*
* 控制组件是否参与更新循环。
*/
private _enabled: boolean = true;
/**
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
*/
public get enabled() {
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
}
/**
* 更新顺序
*
* 决定组件在更新循环中的执行顺序。
*/
private _updateOrder: number = 0;
/**
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
* @param value
*/
public set enabled(value: boolean) {
this.setEnabled(value);
}
/**
* 创建组件实例
*
* 自动分配唯一ID给组件。
*/
constructor() {
this.id = Component._idGenerator++;
}
private _updateOrder = 0;
/**
* 获取组件启用状态
*
* 组件的实际启用状态取决于自身状态和所属实体的状态。
*
* @returns 如果组件和所属实体都启用则返回true
*/
public get enabled(): boolean {
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
}
/** 更新此实体上组件的顺序 */
public get updateOrder() {
return this._updateOrder;
}
/** 更新此实体上组件的顺序 */
public set updateOrder(value: number) {
this.setUpdateOrder(value);
}
/**
* 当此组件已分配其实体,但尚未添加到实体的活动组件列表时调用。有用的东西,如物理组件,需要访问转换来修改碰撞体的属性。
*/
public initialize() {
}
/**
* 在提交所有挂起的组件更改后,将该组件添加到场景时调用。此时,设置了实体字段和实体。场景也设定好了。
*/
public onAddedToEntity() {
}
/**
* 当此组件从其实体中移除时调用。在这里做所有的清理工作。
*/
public onRemovedFromEntity() {
}
/**
* 当实体的位置改变时调用。这允许组件知道它们由于父实体的移动而移动了。
* @param comp
*/
public onEntityTransformChanged(comp: ComponentTransform) {
}
/**
*当父实体或此组件启用时调用
*/
public onEnabled() {
}
/**
* 禁用父实体或此组件时调用
*/
public onDisabled() {
}
public setEnabled(isEnabled: boolean) {
if (this._enabled != isEnabled) {
this._enabled = isEnabled;
if (this._enabled) {
this.onEnabled();
} else {
this.onDisabled();
}
}
return this;
}
public setUpdateOrder(updateOrder: number) {
if (this._updateOrder != updateOrder) {
this._updateOrder = updateOrder;
}
return this;
}
/**
* 添加组件
* @param component 要添加的组件实例
* @returns 返回添加的组件实例
*/
public addComponent<T extends Component>(component: T): T {
return this.entity.addComponent<T>(component);
}
/**
* 获取组件
* @param type 组件类型
* @returns 返回获取到的组件实例
*/
public getComponent<T extends Component>(type: new (...args: any[]) => T): T {
return this.entity.getComponent<T>(type);
}
/**
* 获取一组指定类型的组件
* @param typeName 组件类型名
* @param componentList 可选参数,存储组件实例的数组
* @returns 返回指定类型的组件实例数组
*/
public getComponents(typeName: any, componentList?: any[]): any[] {
return this.entity.getComponents(typeName, componentList);
}
/**
* 判断实体是否包含指定类型的组件
* @param type 组件类型
* @returns 如果实体包含指定类型的组件,返回 true,否则返回 false。
*/
public hasComponent(type: new (...args: any[]) => Component): boolean {
return this.entity.hasComponent(type);
}
/**
* 删除组件
* @param component 可选参数,要删除的组件实例。如果未指定该参数,则删除当前实例上的组件。
*/
public removeComponent(component?: Component): void {
if (component) {
this.entity.removeComponent(component);
/**
* 设置组件启用状态
*
* 当状态改变时会触发相应的生命周期回调。
*
* @param value - 新的启用状态
*/
public set enabled(value: boolean) {
if (this._enabled !== value) {
this._enabled = value;
if (this._enabled) {
this.onEnabled();
} else {
this.entity.removeComponent(this);
this.onDisabled();
}
}
}
/**
* 获取更新顺序
*
* @returns 组件的更新顺序值
*/
public get updateOrder(): number {
return this._updateOrder;
}
/**
* 设置更新顺序
*
* @param value - 新的更新顺序值
*/
public set updateOrder(value: number) {
this._updateOrder = value;
}
/**
* 组件添加到实体时的回调
*
* 当组件被添加到实体时调用,可以在此方法中进行初始化操作。
*/
public onAddedToEntity(): void {
}
/**
* 组件从实体移除时的回调
*
* 当组件从实体中移除时调用,可以在此方法中进行清理操作。
*/
public onRemovedFromEntity(): void {
}
/**
* 组件启用时的回调
*
* 当组件被启用时调用。
*/
public onEnabled(): void {
}
/**
* 组件禁用时的回调
*
* 当组件被禁用时调用。
*/
public onDisabled(): void {
}
/**
* 更新组件
*
* 每帧调用,用于更新组件的逻辑。
* 子类应该重写此方法来实现具体的更新逻辑。
*/
public update(): void {
}
}
// 避免循环引用,在文件末尾导入Entity
import type { Entity } from './Entity';
-30
View File
@@ -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_ + ")";
}
}
}
+26 -20
View File
@@ -1,21 +1,27 @@
module es {
/**
* 接口,当添加到一个Component时,只要Component和实体被启用,就会在每个框架中调用更新方法
*/
export interface IUpdatable {
enabled: boolean;
updateOrder: number;
update();
}
/**
* 用于比较组件更新排序
*/
export class IUpdatableComparer implements IComparer<IUpdatable> {
public compare(a: IUpdatable, b: IUpdatable) {
return a.updateOrder - b.updateOrder;
}
}
export var isIUpdatable = (props: any): props is IUpdatable => typeof (props as IUpdatable)['update'] !== 'undefined';
/**
* 可更新接口
* 当添加到组件时,只要组件和实体被启用,就会在每帧调用update方法
*/
export interface IUpdatable {
enabled: boolean;
updateOrder: number;
update(): void;
}
/**
* 用于比较组件更新排序的比较器
*/
export class IUpdatableComparer {
public compare(a: IUpdatable, b: IUpdatable): number {
return a.updateOrder - b.updateOrder;
}
}
/**
* 检查对象是否实现了IUpdatable接口
* @param props 要检查的对象
* @returns 如果实现了IUpdatable接口返回true,否则返回false
*/
export function isIUpdatable(props: any): props is IUpdatable {
return typeof (props as IUpdatable)['update'] !== 'undefined';
}
@@ -1,259 +0,0 @@
module es {
/**
* 请注意,这不是一个完整的、多迭代的物理系统!它可以用于简单的、街机风格的物理。
* 这可以用于简单的,街机风格的物理学
*/
export class ArcadeRigidbody extends Component implements IUpdatable {
/** 这个刚体的质量。质量为0,则是一个不可移动的物体 */
public get mass() {
return this._mass;
}
public set mass(value: number) {
this.setMass(value);
}
/**
* 0-1范围,其中0为无反弹,1为完全反射。
*/
public get elasticity() {
return this._elasticity;
}
public set elasticiy(value: number) {
this.setElasticity(value);
}
/**
* 0 - 1范围。0表示没有摩擦力,1表示物体会停止在原地
*/
public get friction() {
return this._friction;
}
public set friction(value: number) {
this.setFriction(value);
}
/**
* 0-9的范围。当发生碰撞时,沿碰撞面做直线运动时,如果其平方幅度小于glue摩擦力,则将碰撞设置为上限
*/
public get glue() {
return this._glue;
}
public set glue(value: number) {
this.setGlue(value);
}
/**
* 如果为真,则每一帧都会考虑到Physics.gravity
*/
public shouldUseGravity: boolean = true;
/**
* 该刚体的速度
*/
public velocity: Vector2 = Vector2.zero;
/**
* 质量为0的刚体被认为是不可移动的。改变速度和碰撞对它们没有影响
*/
public get isImmovable() {
return this._mass < 0.0001;
}
public _mass = 10;
public _elasticity = 0.5;
public _friction = 0.5;
public _glue = 0.01;
public _inverseMass: number = 0;
public _collider: Collider;
constructor() {
super();
this._inverseMass = 1 / this._mass;
}
/**
* 这个刚体的质量。质量为0,则是一个不可移动的物体
* @param mass
*/
public setMass(mass: number): ArcadeRigidbody {
this._mass = MathHelper.clamp(mass, 0, Number.MAX_VALUE);
if (this._mass > 0.0001)
this._inverseMass = 1 / this._mass;
else
this._inverseMass = 0;
return this;
}
/**
* 0-1范围,其中0为无反弹,1为完全反射。
* @param value
*/
public setElasticity(value: number): ArcadeRigidbody {
this._elasticity = MathHelper.clamp01(value);
return this;
}
/**
* 0 - 1范围。0表示没有摩擦力,1表示物体会停止在原地
* @param value
*/
public setFriction(value: number): ArcadeRigidbody {
this._friction = MathHelper.clamp01(value);
return this;
}
/**
* 0-9的范围。当发生碰撞时,沿碰撞面做直线运动时,如果其平方幅度小于glue摩擦力,则将碰撞设置为上限
* @param value
*/
public setGlue(value: number): ArcadeRigidbody {
this._glue = MathHelper.clamp(value, 0, 10);
return this;
}
public setVelocity(velocity: Vector2): ArcadeRigidbody {
this.velocity = velocity;
return this;
}
/**
* 用刚体的质量给刚体加上一个瞬间的力脉冲。力是一个加速度,单位是每秒像素每秒。将力乘以100000,使数值使用更合理
* @param force
*/
public addImpulse(force: Vector2) {
if (!this.isImmovable) {
this.velocity.addEqual(force.scale(100000 * (this._inverseMass * (Time.deltaTime * Time.deltaTime))));
}
}
public onAddedToEntity() {
this._collider = null;
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
this._collider = component;
break;
}
}
Debug.warnIf(this._collider == null, "ArcadeRigidbody 没有 Collider。ArcadeRigidbody需要一个Collider!");
}
public update() {
if (this.isImmovable || this._collider == null) {
this.velocity = Vector2.zero;
return;
}
if (this.shouldUseGravity)
this.velocity.addEqual(Physics.gravity.scale(Time.deltaTime));
this.entity.position = this.entity.position.add(this.velocity.scale(Time.deltaTime));
// 捞取我们在新的位置上可能会碰撞到的任何东西
let neighbors = Physics.boxcastBroadphaseExcludingSelf(this._collider, this._collider.bounds, this._collider.collidesWithLayers.value);
if (neighbors.length > 0) {
for (let i = 0; i < neighbors.length; i ++) {
const neighbor = neighbors[i];
if (!neighbor)
continue;
// 如果邻近的对撞机是同一个实体,则忽略它
if (neighbor.entity.equals(this.entity)) {
continue;
}
const collisionResult = new Out<CollisionResult>();
if (this._collider.collidesWithNonMotion(neighbor, collisionResult)) {
// 如果附近有一个ArcadeRigidbody,我们就会处理完整的碰撞响应。如果没有,我们会根据附近是不可移动的来计算事情
let neighborRigidbody = neighbor.entity.getComponent(ArcadeRigidbody);
if (neighborRigidbody != null) {
this.processOverlap(neighborRigidbody, collisionResult.value.minimumTranslationVector);
this.processCollision(neighborRigidbody, collisionResult.value.minimumTranslationVector);
} else {
// 没有ArcadeRigidbody,所以我们假设它是不动的,只移动我们自己的
this.entity.position = this.entity.position.sub(collisionResult.value.minimumTranslationVector);
const relativeVelocity = this.calculateResponseVelocity(this.velocity, collisionResult.value.minimumTranslationVector);
this.velocity.addEqual(relativeVelocity);
}
}
}
}
}
/**
* 将两个重叠的刚体分开。也处理其中一个不可移动的情况
* @param other
* @param minimumTranslationVector
*/
public processOverlap(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
if (this.isImmovable) {
other.entity.position = other.entity.position.add(minimumTranslationVector);
} else if (other.isImmovable) {
this.entity.position = this.entity.position.sub(minimumTranslationVector);
} else {
this.entity.position = this.entity.position.sub(minimumTranslationVector.scale(0.5));
other.entity.position = other.entity.position.add(minimumTranslationVector.scale(0.5));
}
}
/**
* 处理两个非重叠的刚体的碰撞。新的速度将根据情况分配给每个刚体
* @param other
* @param minimumTranslationVector
*/
public processCollision(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
// 我们计算两个相撞物体的响应。
// 计算的基础是沿碰撞表面法线反射的物体的相对速度。
// 然后,响应的一部分会根据质量加到每个物体上
let relativeVelocity = this.velocity.sub(other.velocity);
relativeVelocity = this.calculateResponseVelocity(relativeVelocity, minimumTranslationVector);
// 现在,我们使用质量来线性缩放两个刚体上的响应
const totalinverseMass = this._inverseMass + other._inverseMass;
const ourResponseFraction = this._inverseMass / totalinverseMass;
const otherResponseFraction = other._inverseMass / totalinverseMass;
this.velocity = this.velocity.add(relativeVelocity.scale(ourResponseFraction));
other.velocity = other.velocity.sub(relativeVelocity.scale(otherResponseFraction));
}
/**
* 给定两个物体和MTV之间的相对速度,本方法修改相对速度,使其成为碰撞响应
* @param relativeVelocity
* @param minimumTranslationVector
* @param responseVelocity
*/
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2) {
// 首先,我们得到反方向的归一化MTV:表面法线
let inverseMTV = minimumTranslationVector.scale(-1);
let normal = inverseMTV.normalize();
// 速度是沿碰撞法线和碰撞平面分解的。
// 弹性将影响沿法线的响应(法线速度分量),摩擦力将影响速度的切向分量(切向速度分量)
let n = relativeVelocity.dot(normal);
let normalVelocityComponent = normal.scale(n);
let tangentialVelocityComponent = relativeVelocity.sub(normalVelocityComponent);
if (n > 0)
normalVelocityComponent = Vector2.zero;
// 如果切向分量的平方幅度小于glue,那么我们就把摩擦力提升到最大
let coefficientOfFriction = this._friction;
if (tangentialVelocityComponent.lengthSquared() < this._glue)
coefficientOfFriction = 1.01;
// 弹性影响速度的法向分量,摩擦力影响速度的切向分量
return normalVelocityComponent
.scale(1 + this._elasticity)
.sub(tangentialVelocityComponent.scale(coefficientOfFriction))
.scale(-1);
}
}
}
@@ -1,587 +0,0 @@
module es {
class CharacterRaycastOrigins {
public topLeft: Vector2;
public bottomRight: Vector2;
public bottomLeft: Vector2;
public constructor() {
this.topLeft = Vector2.zero;
this.bottomRight = Vector2.zero;
this.bottomLeft = Vector2.zero;
}
}
class CharacterCollisionState2D {
public right: boolean = false;
public left: boolean = false;
public above: boolean = false;
public below: boolean = false;
public becameGroundedThisFrame: boolean = false;
public wasGroundedLastFrame: boolean = false;
public movingDownSlope: boolean = false;
public slopeAngle: number = 0;
public hasCollision(): boolean {
return this.below || this.right || this.left || this.above;
}
public reset(): void {
this.right = this.left = false;
this.above = this.below = false;
this.becameGroundedThisFrame = this.movingDownSlope = false;
this.slopeAngle = 0;
}
public toString(): string {
return `[CharacterCollisionState2D] r: ${this.right}, l: ${this.left}, a: ${this.above}, b: ${this.below}, movingDownSlope: ${this.movingDownSlope}, angle: ${this.slopeAngle}, wasGroundedLastFrame: ${this.wasGroundedLastFrame}, becameGroundedThisFrame: ${this.becameGroundedThisFrame}`;
}
}
export class CharacterController implements ITriggerListener {
public onControllerCollidedEvent: ObservableT<RaycastHit>;
public onTriggerEnterEvent: ObservableT<Collider>;
public onTriggerExitEvent: ObservableT<Collider>;
/**
* 如果为 true,则在垂直移动单帧时将忽略平台的一种方式
*/
public ignoreOneWayPlatformsTime: number;
public supportSlopedOneWayPlatforms: boolean;
public ignoredColliders: Set<Collider> = new Set();
/**
* 定义距离碰撞射线的边缘有多远。
* 如果使用 0 范围进行投射,则通常会导致不需要的光线击中(例如,直接从表面水平投射的足部碰撞器可能会导致击中)
*/
public get skinWidth() {
return this._skinWidth;
}
public set skinWidth(value: number) {
this._skinWidth = value;
this.recalculateDistanceBetweenRays();
}
/**
* CC2D 可以爬升的最大坡度角
*/
public slopeLimit: number = 30;
/**
* 构成跳跃的帧之间垂直运动变化的阈值
*/
public jumpingThreshold: number = -7;
/**
* 基于斜率乘以速度的曲线(负 = 下坡和正 = 上坡)
*/
public slopeSpeedMultiplier: AnimCurve;
public totalHorizontalRays: number = 5;
public totalVerticalRays: number = 3;
public collisionState: CharacterCollisionState2D = new CharacterCollisionState2D();
public velocity: Vector2 = new Vector2(0, 0);
public get isGrounded(): boolean {
return this.collisionState.below;
}
public get raycastHitsThisFrame(): RaycastHit[] {
return this._raycastHitsThisFrame;
}
public constructor(
player: Entity,
skinWidth?: number,
platformMask: number = -1,
onewayPlatformMask: number = -1,
triggerMask: number = -1
) {
this.onTriggerEnterEvent = new ObservableT();
this.onTriggerExitEvent = new ObservableT();
this.onControllerCollidedEvent = new ObservableT();
this.platformMask = platformMask;
this.oneWayPlatformMask = onewayPlatformMask;
this.triggerMask = triggerMask;
// 将我们的单向平台添加到我们的普通平台掩码中,以便我们可以从上方降落
this.platformMask |= this.oneWayPlatformMask;
this._player = player;
let collider = null;
for (let i = 0; i < this._player.components.buffer.length; i++) {
let component = this._player.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
collider.isTrigger = false;
if (collider instanceof BoxCollider) {
this._collider = collider as BoxCollider;
} else {
throw new Error('player collider must be box');
}
// 在这里,我们触发了具有主体的 setter 的属性
this.skinWidth = skinWidth || collider.width * 0.05;
this._slopeLimitTangent = Math.tan(75 * MathHelper.Deg2Rad);
this._triggerHelper = new ColliderTriggerHelper(this._player);
// 我们想设置我们的 CC2D 忽略所有碰撞层,除了我们的 triggerMask
for (let i = 0; i < 32; i++) {
// 查看我们的 triggerMask 是否包含此层,如果不包含则忽略它
if ((this.triggerMask & (1 << i)) === 0) {
Flags.unsetFlag(this._collider.collidesWithLayers, i);
}
}
}
public onTriggerEnter(other: Collider, local: Collider): void {
this.onTriggerEnterEvent.notify(other);
}
public onTriggerExit(other: Collider, local: Collider): void {
this.onTriggerExitEvent.notify(other);
}
/**
* 尝试将角色移动到位置 + deltaMovement。 任何挡路的碰撞器都会在遇到时导致运动停止
* @param deltaMovement
* @param deltaTime
*/
public move(deltaMovement: Vector2, deltaTime: number): void {
this.collisionState.wasGroundedLastFrame = this.collisionState.below;
this.collisionState.reset();
this._raycastHitsThisFrame = [];
this._isGoingUpSlope = false;
this.primeRaycastOrigins();
if (deltaMovement.y > 0 && this.collisionState.wasGroundedLastFrame) {
deltaMovement = this.handleVerticalSlope(deltaMovement);
}
if (deltaMovement.x !== 0) {
deltaMovement = this.moveHorizontally(deltaMovement);
}
if (deltaMovement.y !== 0) {
deltaMovement = this.moveVertically(deltaMovement);
}
this._player.setPosition(
this._player.position.x + deltaMovement.x,
this._player.position.y + deltaMovement.y
);
if (deltaTime > 0) {
this.velocity.x = deltaMovement.x / deltaTime;
this.velocity.y = deltaMovement.y / deltaTime;
}
if (
!this.collisionState.wasGroundedLastFrame &&
this.collisionState.below
) {
this.collisionState.becameGroundedThisFrame = true;
}
if (this._isGoingUpSlope) {
this.velocity.y = 0;
}
if (!this._isWarpingToGround) {
this._triggerHelper.update();
}
for (let i = 0; i < this._raycastHitsThisFrame.length; i++) {
this.onControllerCollidedEvent.notify(this._raycastHitsThisFrame[i]);
}
if (this.ignoreOneWayPlatformsTime > 0) {
this.ignoreOneWayPlatformsTime -= deltaTime;
}
}
/**
* 直接向下移动直到接地
* @param maxDistance
*/
public warpToGrounded(maxDistance: number = 1000): void {
this.ignoreOneWayPlatformsTime = 0;
this._isWarpingToGround = true;
let delta = 0;
do {
delta += 1;
this.move(new Vector2(0, 1), 0.02);
if (delta > maxDistance) {
break;
}
} while (!this.isGrounded);
this._isWarpingToGround = false;
}
/**
* 这应该在您必须在运行时修改 BoxCollider2D 的任何时候调用。
* 它将重新计算用于碰撞检测的光线之间的距离。
* 它也用于 skinWidth setter,以防在运行时更改。
*/
public recalculateDistanceBetweenRays(): void {
const colliderUsableHeight =
this._collider.height * Math.abs(this._player.scale.y) -
2 * this._skinWidth;
this._verticalDistanceBetweenRays =
colliderUsableHeight / (this.totalHorizontalRays - 1);
const colliderUsableWidth =
this._collider.width * Math.abs(this._player.scale.x) -
2 * this._skinWidth;
this._horizontalDistanceBetweenRays =
colliderUsableWidth / (this.totalVerticalRays - 1);
}
/**
* 将 raycastOrigins 重置为由 skinWidth 插入的框碰撞器的当前范围。
* 插入它是为了避免从直接接触另一个碰撞器的位置投射光线,从而导致不稳定的法线数据。
*/
private primeRaycastOrigins(): void {
const rect = this._collider.bounds;
this._raycastOrigins.topLeft = new Vector2(
rect.x + this._skinWidth,
rect.y + this._skinWidth
);
this._raycastOrigins.bottomRight = new Vector2(
rect.right - this._skinWidth,
rect.bottom - this._skinWidth
);
this._raycastOrigins.bottomLeft = new Vector2(
rect.x + this._skinWidth,
rect.bottom - this._skinWidth
);
}
/**
* 我们必须在这方面使用一些技巧。
* 光线必须从我们的碰撞器(skinWidth)内部的一小段距离投射,以避免零距离光线会得到错误的法线。
* 由于这个小偏移,我们必须增加光线距离 skinWidth 然后记住在实际移动玩家之前从 deltaMovement 中删除 skinWidth
* @param deltaMovement
* @returns
*/
private moveHorizontally(deltaMovement: Vector2): Vector2 {
const isGoingRight = deltaMovement.x > 0;
let rayDistance =
Math.abs(deltaMovement.x) +
this._skinWidth * this.rayOriginSkinMutiplier;
const rayDirection: Vector2 = isGoingRight ? Vector2.right : Vector2.left;
const initialRayOriginY = this._raycastOrigins.bottomLeft.y;
const initialRayOriginX = isGoingRight
? this._raycastOrigins.bottomRight.x -
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
: this._raycastOrigins.bottomLeft.x +
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
for (let i = 0; i < this.totalHorizontalRays; i++) {
const ray = new Vector2(
initialRayOriginX,
initialRayOriginY - i * this._verticalDistanceBetweenRays
);
// 如果我们接地,我们将只在第一条射线(底部)上包含 oneWayPlatforms。
// 允许我们走上倾斜的 oneWayPlatforms
if (
i === 0 &&
this.supportSlopedOneWayPlatforms &&
this.collisionState.wasGroundedLastFrame
) {
this._raycastHit = Physics.linecast(
ray,
ray.add(rayDirection.scaleEqual(rayDistance)),
this.platformMask,
this.ignoredColliders
);
} else {
this._raycastHit = Physics.linecast(
ray,
ray.add(rayDirection.scaleEqual(rayDistance)),
this.platformMask & ~this.oneWayPlatformMask,
this.ignoredColliders
);
}
if (this._raycastHit.collider) {
if (
i === 0 &&
this.handleHorizontalSlope(
deltaMovement,
Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up)
)
) {
this._raycastHitsThisFrame.push(this._raycastHit);
break;
}
deltaMovement.x = this._raycastHit.point.x - ray.x;
rayDistance = Math.abs(deltaMovement.x);
if (isGoingRight) {
deltaMovement.x -= this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.right = true;
} else {
deltaMovement.x += this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.left = true;
}
this._raycastHitsThisFrame.push(this._raycastHit);
if (
rayDistance <
this._skinWidth * this.rayOriginSkinMutiplier +
this.kSkinWidthFloatFudgeFactor
) {
break;
}
}
}
return deltaMovement;
}
private moveVertically(deltaMovement: Vector2): Vector2 {
const isGoingUp = deltaMovement.y < 0;
let rayDistance =
Math.abs(deltaMovement.y) +
this._skinWidth * this.rayOriginSkinMutiplier;
const rayDirection = isGoingUp ? Vector2.up : Vector2.down;
let initialRayOriginX = this._raycastOrigins.topLeft.x;
const initialRayOriginY = isGoingUp
? this._raycastOrigins.topLeft.y +
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
: this._raycastOrigins.bottomLeft.y -
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
initialRayOriginX += deltaMovement.x;
let mask = this.platformMask;
if (isGoingUp || this.ignoreOneWayPlatformsTime > 0) {
mask &= ~this.oneWayPlatformMask;
}
for (let i = 0; i < this.totalVerticalRays; i++) {
const rayStart = new Vector2(
initialRayOriginX + i * this._horizontalDistanceBetweenRays,
initialRayOriginY
);
this._raycastHit = Physics.linecast(
rayStart,
rayStart.add(rayDirection.scaleEqual(rayDistance)),
mask,
this.ignoredColliders
);
if (this._raycastHit.collider) {
deltaMovement.y = this._raycastHit.point.y - rayStart.y;
rayDistance = Math.abs(deltaMovement.y);
if (isGoingUp) {
deltaMovement.y += this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.above = true;
} else {
deltaMovement.y -= this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.below = true;
}
this._raycastHitsThisFrame.push(this._raycastHit);
if (!isGoingUp && deltaMovement.y < -0.00001) {
this._isGoingUpSlope = true;
}
if (
rayDistance <
this._skinWidth * this.rayOriginSkinMutiplier +
this.kSkinWidthFloatFudgeFactor
) {
break;
}
}
}
return deltaMovement;
}
/**
* 检查 BoxCollider2D 下的中心点是否存在坡度。
* 如果找到一个,则调整 deltaMovement 以便玩家保持接地,并考虑slopeSpeedModifier 以加快移动速度。
* @param deltaMovement
* @returns
*/
private handleVerticalSlope(deltaMovement: Vector2): Vector2 {
const centerOfCollider =
(this._raycastOrigins.bottomLeft.x +
this._raycastOrigins.bottomRight.x) *
0.5;
const rayDirection = Vector2.down;
const slopeCheckRayDistance =
this._slopeLimitTangent *
(this._raycastOrigins.bottomRight.x - centerOfCollider);
const slopeRay = new Vector2(
centerOfCollider,
this._raycastOrigins.bottomLeft.y
);
this._raycastHit = Physics.linecast(
slopeRay,
slopeRay.add(rayDirection.scaleEqual(slopeCheckRayDistance)),
this.platformMask,
this.ignoredColliders
);
if (this._raycastHit.collider) {
const angle = Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up);
if (angle === 0) {
return deltaMovement;
}
const isMovingDownSlope =
Math.sign(this._raycastHit.normal.x) === Math.sign(deltaMovement.x);
if (isMovingDownSlope) {
const slopeModifier = this.slopeSpeedMultiplier
? this.slopeSpeedMultiplier.lerp(-angle)
: 1;
deltaMovement.y +=
this._raycastHit.point.y - slopeRay.y - this.skinWidth;
deltaMovement.x *= slopeModifier;
this.collisionState.movingDownSlope = true;
this.collisionState.slopeAngle = angle;
}
}
return deltaMovement;
}
/**
* 如果我们要上坡,则处理调整 deltaMovement
* @param deltaMovement
* @param angle
* @returns
*/
private handleHorizontalSlope(
deltaMovement: Vector2,
angle: number
): boolean {
if (Math.round(angle) === 90) {
return false;
}
if (angle < this.slopeLimit) {
if (deltaMovement.y > this.jumpingThreshold) {
const slopeModifier = this.slopeSpeedMultiplier
? this.slopeSpeedMultiplier.lerp(angle)
: 1;
deltaMovement.x *= slopeModifier;
deltaMovement.y = Math.abs(
Math.tan(angle * MathHelper.Deg2Rad) * deltaMovement.x
);
const isGoingRight = deltaMovement.x > 0;
const ray = isGoingRight
? this._raycastOrigins.bottomRight
: this._raycastOrigins.bottomLeft;
let raycastHit = null;
if (
this.supportSlopedOneWayPlatforms &&
this.collisionState.wasGroundedLastFrame
) {
raycastHit = Physics.linecast(
ray,
ray.add(deltaMovement),
this.platformMask,
this.ignoredColliders
);
} else {
raycastHit = Physics.linecast(
ray,
ray.add(deltaMovement),
this.platformMask & ~this.oneWayPlatformMask,
this.ignoredColliders
);
}
if (raycastHit.collider) {
deltaMovement.x = raycastHit.point.x - ray.x;
deltaMovement.y = raycastHit.point.y - ray.y;
if (isGoingRight) {
deltaMovement.x -= this._skinWidth;
} else {
deltaMovement.x += this._skinWidth;
}
}
this._isGoingUpSlope = true;
this.collisionState.below = true;
}
} else {
deltaMovement.x = 0;
}
return true;
}
private _player: Entity;
private _collider: BoxCollider;
private _skinWidth: number = 0.02;
private _triggerHelper: ColliderTriggerHelper;
/**
* 这用于计算为检查坡度而投射的向下光线。
* 我们使用有点随意的值 75 度来计算检查斜率的射线的长度。
*/
private _slopeLimitTangent: number;
private readonly kSkinWidthFloatFudgeFactor: number = 0.001;
/**
* 我们的光线投射原点角的支架(TR、TL、BR、BL)
*/
private _raycastOrigins: CharacterRaycastOrigins = new CharacterRaycastOrigins();
/**
* 存储我们在移动过程中命中的光线投射
*/
private _raycastHit: RaycastHit = new RaycastHit();
/**
* 存储此帧发生的任何光线投射命中。
* 我们必须存储它们,以防我们遇到水平和垂直移动的碰撞,以便我们可以在设置所有碰撞状态后发送事件
*/
private _raycastHitsThisFrame: RaycastHit[];
// 水平/垂直移动数据
private _verticalDistanceBetweenRays: number;
private _horizontalDistanceBetweenRays: number;
/**
* 我们使用这个标志来标记我们正在爬坡的情况,我们修改了 delta.y 以允许爬升。
* 原因是,如果我们到达斜坡的尽头,我们可以进行调整以保持接地
*/
private _isGoingUpSlope: boolean = false;
private _isWarpingToGround: boolean = true;
private platformMask: number = -1;
private triggerMask: number = -1;
private oneWayPlatformMask: number = -1;
private readonly rayOriginSkinMutiplier = 4;
}
}
@@ -1,89 +0,0 @@
///<reference path="./Collider.ts" />
module es {
export class BoxCollider extends Collider {
/**
* 创建一个BoxCollider,并使用x/y组件作为局部Offset
* @param x
* @param y
* @param width
* @param height
*/
constructor(x: number = 0, y: number = 0, width: number = 1, height: number = 1) {
super();
this._localOffset = new Vector2(x + width / 2, y + height / 2);
this.shape = new Box(width, height);
}
public get width() {
return (this.shape as Box).width;
}
public set width(value: number) {
this.setWidth(value);
}
public get height() {
return (this.shape as Box).height;
}
public set height(value: number) {
this.setHeight(value);
}
/**
* 设置BoxCollider的大小
* @param width
* @param height
*/
public setSize(width: number, height: number) {
let box = this.shape as Box;
if (width != box.width || height != box.height) {
// 更新框,改变边界,如果我们需要更新物理系统中的边界
box.updateBox(width, height);
this._isPositionDirty = true;
if (this.entity && this._isParentEntityAddedToScene)
Physics.updateCollider(this);
}
return this;
}
/**
* 设置BoxCollider的宽度
* @param width
*/
public setWidth(width: number): BoxCollider {
let box = this.shape as Box;
if (width != box.width) {
// 更新框,改变边界,如果我们需要更新物理系统中的边界
box.updateBox(width, box.height);
this._isPositionDirty = true;
if (this.entity != null && this._isParentEntityAddedToScene)
Physics.updateCollider(this);
}
return this;
}
/**
* 设置BoxCollider的高度
* @param height
*/
public setHeight(height: number) {
let box = this.shape as Box;
if (height != box.height) {
// 更新框,改变边界,如果我们需要更新物理系统中的边界
box.updateBox(box.width, height);
this._isPositionDirty = true;
if (this.entity && this._isParentEntityAddedToScene)
Physics.updateCollider(this);
}
}
public toString() {
return `[BoxCollider: bounds: ${this.bounds}]`;
}
}
}
@@ -1,46 +0,0 @@
module es {
export class CircleCollider extends Collider {
/**
* 创建一个具有半径的CircleCollider。
* 请注意,当指定半径时,如果在实体上使用RenderableComponent,您将需要设置原点来对齐CircleCollider。
* 例如,如果RenderableComponent有一个0,0的原点,并且创建了一个半径为1.5f * renderable.width的CircleCollider,你可以通过设置originNormalied为中心除以缩放尺寸来偏移原点
*
* @param radius
*/
constructor(radius: number = 1) {
super();
this.shape = new Circle(radius);
}
public get radius(): number {
return (this.shape as Circle).radius;
}
public set radius(value: number) {
this.setRadius(value);
}
/**
* 设置圆的半径
* @param radius
*/
public setRadius(radius: number): CircleCollider {
let circle = this.shape as Circle;
if (radius != circle.radius) {
circle.radius = radius;
circle._originalRadius = radius;
this._isPositionDirty = true;
if (this.entity != null && this._isParentEntityAddedToScene)
Physics.updateCollider(this);
}
return this;
}
public toString() {
return `[CircleCollider: bounds: ${this.bounds}, radius: ${(this.shape as Circle).radius}]`
}
}
}
@@ -1,275 +0,0 @@
module es {
export class Collider extends Component {
public static readonly lateSortOrder = 999;
public castSortOrder: number = 0;
/**
* 对撞机的基本形状
*/
public shape: Shape;
/**
* 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件
*/
public isTrigger: boolean = false;
/**
* 在处理冲突时,physicsLayer可以用作过滤器。Flags类有帮助位掩码的方法
*/
public physicsLayer = new Ref(1 << 0);
/**
* 碰撞器在使用移动器移动时应该碰撞的层
* 默认为所有层
*/
public collidesWithLayers: Ref<number> = new Ref(Physics.allLayers);
/**
* 如果为true,碰撞器将根据附加的变换缩放和旋转
*/
public shouldColliderScaleAndRotateWithTransform = true;
/**
* 这个对撞机在物理系统注册时的边界。
* 存储这个允许我们始终能够安全地从物理系统中移除对撞机,即使它在试图移除它之前已经被移动了。
*/
public registeredPhysicsBounds: Rectangle = new Rectangle();
public _localOffsetLength: number;
public _isPositionDirty: boolean = true;
public _isRotationDirty: boolean = true;
/**
* 标记来跟踪我们的实体是否被添加到场景中
*/
protected _isParentEntityAddedToScene;
/**
* 标记来记录我们是否注册了物理系统
*/
protected _isColliderRegistered;
/**
* 表示碰撞器的绝对位置
*/
public get absolutePosition(): Vector2 {
return Vector2.add(this.entity.transform.position, this._localOffset);
}
/**
* 封装变换。如果碰撞器没和实体一起旋转 则返回transform.rotation
*/
public get rotation(): number {
if (this.shouldColliderScaleAndRotateWithTransform && this.entity != null)
return this.entity.transform.rotation;
return 0;
}
public get bounds(): Rectangle {
if (this._isPositionDirty || this._isRotationDirty) {
this.shape.recalculateBounds(this);
this._isPositionDirty = this._isRotationDirty = false;
}
return this.shape.bounds;
}
protected _localOffset: Vector2 = Vector2.zero;
/**
* 将localOffset添加到实体。获取碰撞器几何图形的最终位置。
* 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转
*/
public get localOffset(): Vector2 {
return this._localOffset;
}
/**
* 将localOffset添加到实体。获取碰撞器几何图形的最终位置。
* 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转
* @param value
*/
public set localOffset(value: Vector2) {
this.setLocalOffset(value);
}
/**
* 将localOffset添加到实体。获取碰撞器的最终位置。
* 这允许您向一个实体添加多个碰撞器并分别定位它们。
* @param offset
*/
public setLocalOffset(offset: Vector2): Collider {
if (!this._localOffset.equals(offset)) {
this.unregisterColliderWithPhysicsSystem();
this._localOffset.setTo(offset.x, offset.y);
this._localOffsetLength = this._localOffset.magnitude();
this._isPositionDirty = true;
this.registerColliderWithPhysicsSystem();
}
return this;
}
/**
* 如果为true,碰撞器将根据附加的变换缩放和旋转
* @param shouldColliderScaleAndRotationWithTransform
*/
public setShouldColliderScaleAndRotateWithTransform(shouldColliderScaleAndRotationWithTransform: boolean): Collider {
this.shouldColliderScaleAndRotateWithTransform = shouldColliderScaleAndRotationWithTransform;
this._isPositionDirty = this._isRotationDirty = true;
return this;
}
public onAddedToEntity() {
this._isParentEntityAddedToScene = true;
this.registerColliderWithPhysicsSystem();
}
public onRemovedFromEntity() {
this.unregisterColliderWithPhysicsSystem();
this._isParentEntityAddedToScene = false;
}
public onEntityTransformChanged(comp: ComponentTransform) {
switch (comp) {
case ComponentTransform.position:
this._isPositionDirty = true;
break;
case ComponentTransform.scale:
this._isPositionDirty = true;
break;
case ComponentTransform.rotation:
this._isRotationDirty = true;
break;
}
if (this._isColliderRegistered)
Physics.updateCollider(this);
}
public onEnabled() {
this.registerColliderWithPhysicsSystem();
this._isPositionDirty = this._isRotationDirty = true;
}
public onDisabled() {
this.unregisterColliderWithPhysicsSystem();
}
/**
* 父实体会在不同的时间调用它(当添加到场景,启用,等等)
*/
public registerColliderWithPhysicsSystem() {
// 如果在将我们添加到实体之前更改了origin等属性,则实体可以为null
if (this._isParentEntityAddedToScene && !this._isColliderRegistered) {
Physics.addCollider(this);
this._isColliderRegistered = true;
}
}
/**
* 父实体会在不同的时候调用它(从场景中移除,禁用,等等)
*/
public unregisterColliderWithPhysicsSystem() {
if (this._isParentEntityAddedToScene && this._isColliderRegistered) {
Physics.removeCollider(this);
}
this._isColliderRegistered = false;
}
/**
* 检查这个形状是否与物理系统中的其他对撞机重叠
* @param other
*/
public overlaps(other: Collider): boolean {
return this.shape.overlaps(other.shape);
}
/**
* 检查这个与运动应用的碰撞器(移动向量)是否与碰撞器碰撞。如果是这样,将返回true,并且结果将填充碰撞数据。
* @param collider
* @param motion
* @param result
*/
public collidesWith(collider: Collider, motion: Vector2, result: Out<CollisionResult>): boolean {
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
const oldPosition = this.entity.position;
this.entity.position = this.entity.position.add(motion);
const didCollide = this.shape.collidesWithShape(collider.shape, result);
if (didCollide)
result.value.collider = collider;
// 将图形位置返回到检查前的位置
this.entity.position = oldPosition;
return didCollide;
}
/**
* 检查这个对撞机是否与对撞机发生碰撞。如果碰撞,则返回true,结果将被填充
* @param collider
* @param result
*/
public collidesWithNonMotion(collider: Collider, result: Out<CollisionResult>): boolean {
if (this.shape.collidesWithShape(collider.shape, result)) {
result.value.collider = collider;
return true;
}
result.value.collider = null;
return false;
}
/**
* 检查此碰撞器是否已应用运动(增量运动矢量)与任何碰撞器发生碰撞。
* 如果是这样,则将返回true,并且将使用碰撞数据填充结果。 运动将设置为碰撞器在碰撞之前可以行进的最大距离。
* @param motion
* @param result
*/
public collidesWithAny(motion: Vector2, result: Out<CollisionResult>) {
result.value = new CollisionResult();
// 在我们的新位置上获取我们可能会碰到的任何东西
let colliderBounds = this.bounds.clone();
colliderBounds.x += motion.x;
colliderBounds.y += motion.y;
let neighbors = Physics.boxcastBroadphaseExcludingSelf(this, colliderBounds, this.collidesWithLayers.value);
// 更改形状位置,使其处于移动后的位置,以便我们检查是否有重叠
let oldPosition = this.shape.position;
this.shape.position = Vector2.add(this.shape.position, motion);
let didCollide = false;
for (let i = 0; i < neighbors.length; i ++ ){
const neighbor = neighbors[i];
if (neighbor.isTrigger)
continue;
if (this.collidesWithNonMotion(neighbor, result)) {
motion = motion.sub(result.value.minimumTranslationVector);
this.shape.position = this.shape.position.sub(result.value.minimumTranslationVector);
didCollide = true;
}
}
// 将形状位置返回到检查之前的位置
this.shape.position = oldPosition;
return didCollide;
}
/**
* 检查此碰撞器是否与场景中的其他碰撞器碰撞。它相交的第一个碰撞器将在碰撞结果中返回碰撞数据。
* @param result
*/
public collidesWithAnyNonMotion(result: Out<CollisionResult>) {
result.value = new CollisionResult();
// 在我们的新位置上获取我们可能会碰到的任何东西
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this, this.collidesWithLayers.value);
for (let neighbor of neighbors) {
if (neighbor.isTrigger)
continue;
if (this.collidesWithNonMotion(neighbor, result))
return true;
}
return false;
}
}
}
@@ -1,26 +0,0 @@
module es {
/**
* 多边形应该以顺时针方式定义
*/
export class PolygonCollider extends Collider {
/**
* 如果这些点没有居中,它们将以localOffset的差异为居中。
* @param points
*/
constructor(points: Vector2[]) {
super();
// 第一点和最后一点决不能相同。我们想要一个开放的多边形
let isPolygonClosed = points[0] == points[points.length - 1];
// 最后一个移除
if (isPolygonClosed)
points = points.slice(0, points.length - 1);
let center = Polygon.findPolygonCenter(points);
this.setLocalOffset(center);
Polygon.recenterPolygonVerts(points);
this.shape = new Polygon(points);
}
}
}
@@ -1,16 +0,0 @@
module es {
/**
* 扇形碰撞器
*/
export class SectorCollider extends Collider {
constructor(
center: Vector2,
radius: number,
startAngle: number,
endAngle: number
) {
super();
this.shape = new Sector(center, radius, startAngle, endAngle);
}
}
}
@@ -1,47 +0,0 @@
module es {
/**
* 当添加到组件时,每当实体上的冲突器与另一个组件重叠/退出时,将调用这些方法。
* ITriggerListener方法将在实现接口的触发器实体上的任何组件上调用。
* 注意,这个接口只与Mover类一起工作
*/
export interface ITriggerListener {
/**
* 当碰撞器与触发碰撞器相交时调用。这是在触发碰撞器和触发碰撞器上调用的。
* 移动必须由Mover/ProjectileMover方法处理,以使其自动工作。
* @param other
* @param local
*/
onTriggerEnter(other: Collider, local: Collider);
/**
* 当另一个碰撞器离开触发碰撞器时调用
* @param other
* @param local
*/
onTriggerExit(other: Collider, local: Collider);
}
export class TriggerListenerHelper {
public static getITriggerListener(entity: Entity, components: ITriggerListener[]){
if (entity.components._components.length > 0) {
for (let i = 0; i < entity.components._components.length; i ++) {
const component = entity.components._components[i];
if (isITriggerListener(component)) {
components.push(component);
}
}
}
for (let i in entity.components._componentsToAdd) {
let component = entity.components._componentsToAdd[i];
if (isITriggerListener(component)) {
components.push(component);
}
}
return components;
}
}
export var isITriggerListener = (props: any): props is ITriggerListener => typeof (props as ITriggerListener)['onTriggerEnter'] !== 'undefined';
}
-115
View File
@@ -1,115 +0,0 @@
module es {
/**
* 辅助类说明了一种处理移动的方法,它考虑了包括触发器在内的所有冲突。
* ITriggerListener接口用于管理对移动过程中违反的任何触发器的回调。
* 一个物体只能通过移动器移动。要正确报告触发器的move方法。
*
* 请注意,多个移动者相互交互将多次调用ITriggerListener。
*/
export class Mover extends Component {
private _triggerHelper: ColliderTriggerHelper;
public onAddedToEntity() {
this._triggerHelper = new ColliderTriggerHelper(this.entity);
}
/**
* 计算修改运动矢量的运动,以考虑移动时可能发生的碰撞
* @param motion
* @param collisionResult
*/
public calculateMovement(motion: Vector2, collisionResult: Out<CollisionResult>): boolean {
collisionResult.value = new CollisionResult();
let collider = null;
if (this.entity.components.buffer.length > 0)
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
if (collider == null || this._triggerHelper == null) {
return false;
}
// 移动所有的非触发碰撞器并获得最近的碰撞
let colliders: Collider[] = [];
if (this.entity.components.buffer.length > 0)
for (let i = 0; i < this.entity.components.buffer.length; i ++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
colliders.push(component);
}
}
if (colliders.length > 0) {
for (let i = 0; i < colliders.length; i++) {
let collider = colliders[i];
// 不检测触发器 在我们移动后会重新访问它
if (collider.isTrigger)
continue;
// 获取我们在新位置可能发生碰撞的任何东西
let bounds = collider.bounds;
bounds.x += motion.x;
bounds.y += motion.y;
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, bounds, collider.collidesWithLayers.value);
if (neighbors.length > 0) {
for (let i = 0; i < neighbors.length; i ++) {
const neighbor = neighbors[i];
// 不检测触发器
if (neighbor.isTrigger)
return;
let _internalcollisionResult = new Out<CollisionResult>();
if (collider.collidesWith(neighbor, motion, _internalcollisionResult)) {
// 如果碰撞 则退回之前的移动量
motion.subEqual(_internalcollisionResult.value.minimumTranslationVector);
// 如果我们碰到多个对象,为了简单起见,只取第一个。
if (_internalcollisionResult.value.collider != null) {
collisionResult.value.collider = _internalcollisionResult.value.collider;
collisionResult.value.minimumTranslationVector = _internalcollisionResult.value.minimumTranslationVector;
collisionResult.value.normal = _internalcollisionResult.value.normal;
collisionResult.value.point = _internalcollisionResult.value.point;
}
}
}
}
}
}
ListPool.free(Collider, colliders);
return collisionResult.value.collider != null;
}
/**
* 将calculatemomovement应用到实体并更新triggerHelper
* @param motion
*/
public applyMovement(motion: Vector2) {
// 移动实体到它的新位置,如果我们有一个碰撞,否则移动全部数量。当碰撞发生时,运动被更新
this.entity.position = Vector2.add(this.entity.position, motion);
// 对所有是触发器的碰撞器与所有宽相位碰撞器进行重叠检查。任何重叠都会导致触发事件。
if (this._triggerHelper)
this._triggerHelper.update();
}
/**
* 通过调用calculateMovement和applyMovement来移动考虑碰撞的实体;
* @param motion
* @param collisionResult
*/
public move(motion: Vector2, collisionResult: Out<CollisionResult>) {
this.calculateMovement(motion, collisionResult);
this.applyMovement(motion);
return collisionResult.value.collider != null;
}
}
}
@@ -1,66 +0,0 @@
module es {
/**
* 移动时考虑到碰撞,只用于向任何ITriggerListeners报告。
* 物体总是会全量移动,所以如果需要的话,由调用者在撞击时销毁它。
*/
export class ProjectileMover extends Component {
private _tempTriggerList: ITriggerListener[] = [];
private _collider: Collider;
public onAddedToEntity() {
let collider = null;
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
this._collider = collider;
Debug.warnIf(this._collider == null, "ProjectileMover没有Collider。ProjectilMover需要一个Collider!");
}
/**
* 在考虑到碰撞的情况下移动实体
* @param motion
*/
public move(motion: Vector2): boolean {
if (this._collider == null)
return false;
let didCollide = false;
// 获取我们在新的位置上可能会碰撞到的任何东西
this.entity.position = Vector2.add(this.entity.position, motion);
// 获取任何可能在新位置发生碰撞的东西
let neighbors = Physics.boxcastBroadphase(this._collider.bounds, this._collider.collidesWithLayers.value);
if (neighbors.length > 0)
for (let i = 0; i < neighbors.length; i ++) {
const neighbor = neighbors[i];
if (this._collider.overlaps(neighbor) && neighbor.enabled){
didCollide = true;
this.notifyTriggerListeners(this._collider, neighbor);
}
}
return didCollide;
}
private notifyTriggerListeners(self: Collider, other: Collider) {
// 通知我们重叠的碰撞器实体上的任何侦听器
TriggerListenerHelper.getITriggerListener(other.entity, this._tempTriggerList);
for (let i = 0; i < this._tempTriggerList.length; i++) {
this._tempTriggerList[i].onTriggerEnter(self, other);
}
this._tempTriggerList.length = 0;
// 通知此实体上的任何侦听器
TriggerListenerHelper.getITriggerListener(this.entity, this._tempTriggerList);
for (let i = 0; i < this._tempTriggerList.length; i++) {
this._tempTriggerList[i].onTriggerEnter(other, self);
}
this._tempTriggerList.length = 0;
}
}
}
+57 -81
View File
@@ -1,88 +1,64 @@
module es {
export class SceneComponent implements IComparer<SceneComponent> {
/**
* 这个场景组件被附加到的场景
*/
public scene: Scene;
import type { Scene } from '../Scene';
/**
* 如果启用了SceneComponent,则为true。状态的改变会导致调用onEnabled/onDisable。
*/
public get enabled() {
return this._enabled;
}
/**
* 场景组件基类
* 附加到场景的组件,用于实现场景级别的功能
*/
export class SceneComponent {
/** 组件所属的场景 */
public scene!: Scene;
/** 更新顺序 */
public updateOrder: number = 0;
/** 是否启用 */
private _enabled: boolean = true;
/**
* 如果启用了SceneComponent,则为true。状态的改变会导致调用onEnabled/onDisable。
* @param value
*/
public set enabled(value: boolean) {
this.setEnabled(value);
}
/** 获取是否启用 */
public get enabled(): boolean {
return this._enabled;
}
/**
* 更新此场景中SceneComponents的顺序
*/
public updateOrder: number = 0;
public _enabled: boolean = true;
/**
* 在启用此SceneComponent时调用
*/
public onEnabled() {
}
/**
* 当禁用此SceneComponent时调用
*/
public onDisabled() {
}
/**
* 当该SceneComponent从场景中移除时调用
*/
public onRemovedFromScene() {
}
/**
* 在实体更新之前每一帧调用
*/
public update() {
}
/**
* 启用/禁用这个SceneComponent
* @param isEnabled
*/
public setEnabled(isEnabled: boolean): SceneComponent {
if (this._enabled != isEnabled) {
this._enabled = isEnabled;
if (this._enabled) {
this.onEnabled();
} else {
this.onDisabled();
}
/** 设置是否启用 */
public set enabled(value: boolean) {
if (this._enabled !== value) {
this._enabled = value;
if (this._enabled) {
this.onEnabled();
} else {
this.onDisabled();
}
return this;
}
/**
* 设置SceneComponent的updateOrder并触发某种SceneComponent
* @param updateOrder
*/
public setUpdateOrder(updateOrder: number) {
if (this.updateOrder != updateOrder) {
this.updateOrder = updateOrder;
}
return this;
}
public compare(other: SceneComponent): number {
return this.updateOrder - other.updateOrder;
}
}
/**
* 当组件启用时调用
*/
public onEnabled(): void {
}
/**
* 当组件禁用时调用
*/
public onDisabled(): void {
}
/**
* 当组件从场景中移除时调用
*/
public onRemovedFromScene(): void {
}
/**
* 每帧更新
*/
public update(): void {
}
/**
* 比较组件的更新顺序
* @param other 其他组件
* @returns 比较结果
*/
public compare(other: SceneComponent): number {
return this.updateOrder - other.updateOrder;
}
}
+404
View File
@@ -0,0 +1,404 @@
import { Component } from '../Component';
/**
* 组件类型定义
*/
export type ComponentType<T extends Component = Component> = new (...args: any[]) => T;
/**
* 组件注册表
* 管理组件类型的位掩码分配
*/
export class ComponentRegistry {
private static componentTypes = new Map<Function, number>();
private static nextBitIndex = 0;
private static maxComponents = 64; // 支持最多64种组件类型
/**
* 注册组件类型并分配位掩码
* @param componentType 组件类型
* @returns 分配的位索引
*/
public static register<T extends Component>(componentType: ComponentType<T>): number {
if (this.componentTypes.has(componentType)) {
return this.componentTypes.get(componentType)!;
}
if (this.nextBitIndex >= this.maxComponents) {
throw new Error(`Maximum number of component types (${this.maxComponents}) exceeded`);
}
const bitIndex = this.nextBitIndex++;
this.componentTypes.set(componentType, bitIndex);
return bitIndex;
}
/**
* 获取组件类型的位掩码
* @param componentType 组件类型
* @returns 位掩码
*/
public static getBitMask<T extends Component>(componentType: ComponentType<T>): bigint {
const bitIndex = this.componentTypes.get(componentType);
if (bitIndex === undefined) {
throw new Error(`Component type ${componentType.name} is not registered`);
}
return BigInt(1) << BigInt(bitIndex);
}
/**
* 获取组件类型的位索引
* @param componentType 组件类型
* @returns 位索引
*/
public static getBitIndex<T extends Component>(componentType: ComponentType<T>): number {
const bitIndex = this.componentTypes.get(componentType);
if (bitIndex === undefined) {
throw new Error(`Component type ${componentType.name} is not registered`);
}
return bitIndex;
}
/**
* 检查组件类型是否已注册
* @param componentType 组件类型
* @returns 是否已注册
*/
public static isRegistered<T extends Component>(componentType: ComponentType<T>): boolean {
return this.componentTypes.has(componentType);
}
/**
* 获取所有已注册的组件类型
* @returns 组件类型映射
*/
public static getAllRegisteredTypes(): Map<Function, number> {
return new Map(this.componentTypes);
}
}
/**
* 高性能组件存储器
* 使用SoAStructure of Arrays)模式存储组件
*/
export class ComponentStorage<T extends Component> {
private components: (T | null)[] = [];
private entityToIndex = new Map<number, number>();
private indexToEntity: number[] = [];
private freeIndices: number[] = [];
private componentType: ComponentType<T>;
private _size = 0;
constructor(componentType: ComponentType<T>) {
this.componentType = componentType;
// 确保组件类型已注册
if (!ComponentRegistry.isRegistered(componentType)) {
ComponentRegistry.register(componentType);
}
}
/**
* 添加组件
* @param entityId 实体ID
* @param component 组件实例
*/
public addComponent(entityId: number, component: T): void {
// 检查实体是否已有此组件
if (this.entityToIndex.has(entityId)) {
throw new Error(`Entity ${entityId} already has component ${this.componentType.name}`);
}
let index: number;
if (this.freeIndices.length > 0) {
// 重用空闲索引
index = this.freeIndices.pop()!;
this.components[index] = component;
this.indexToEntity[index] = entityId;
} else {
// 添加到末尾
index = this.components.length;
this.components.push(component);
this.indexToEntity.push(entityId);
}
this.entityToIndex.set(entityId, index);
this._size++;
}
/**
* 获取组件
* @param entityId 实体ID
* @returns 组件实例或null
*/
public getComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId);
return index !== undefined ? this.components[index] : null;
}
/**
* 检查实体是否有此组件
* @param entityId 实体ID
* @returns 是否有组件
*/
public hasComponent(entityId: number): boolean {
return this.entityToIndex.has(entityId);
}
/**
* 移除组件
* @param entityId 实体ID
* @returns 被移除的组件或null
*/
public removeComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId);
if (index === undefined) {
return null;
}
const component = this.components[index];
this.entityToIndex.delete(entityId);
this.components[index] = null;
this.freeIndices.push(index);
this._size--;
return component;
}
/**
* 高效遍历所有组件
* @param callback 回调函数
*/
public forEach(callback: (component: T, entityId: number, index: number) => void): void {
for (let i = 0; i < this.components.length; i++) {
const component = this.components[i];
if (component) {
callback(component, this.indexToEntity[i], i);
}
}
}
/**
* 获取所有组件(密集数组)
* @returns 组件数组
*/
public getDenseArray(): { components: T[]; entityIds: number[] } {
const components: T[] = [];
const entityIds: number[] = [];
for (let i = 0; i < this.components.length; i++) {
const component = this.components[i];
if (component) {
components.push(component);
entityIds.push(this.indexToEntity[i]);
}
}
return { components, entityIds };
}
/**
* 清空所有组件
*/
public clear(): void {
this.components.length = 0;
this.entityToIndex.clear();
this.indexToEntity.length = 0;
this.freeIndices.length = 0;
this._size = 0;
}
/**
* 获取组件数量
*/
public get size(): number {
return this._size;
}
/**
* 获取组件类型
*/
public get type(): ComponentType<T> {
return this.componentType;
}
/**
* 压缩存储(移除空洞)
*/
public compact(): void {
if (this.freeIndices.length === 0) {
return; // 没有空洞,无需压缩
}
const newComponents: T[] = [];
const newIndexToEntity: number[] = [];
const newEntityToIndex = new Map<number, number>();
let newIndex = 0;
for (let i = 0; i < this.components.length; i++) {
const component = this.components[i];
if (component) {
newComponents[newIndex] = component;
newIndexToEntity[newIndex] = this.indexToEntity[i];
newEntityToIndex.set(this.indexToEntity[i], newIndex);
newIndex++;
}
}
this.components = newComponents;
this.indexToEntity = newIndexToEntity;
this.entityToIndex = newEntityToIndex;
this.freeIndices.length = 0;
}
/**
* 获取存储统计信息
*/
public getStats(): {
totalSlots: number;
usedSlots: number;
freeSlots: number;
fragmentation: number;
} {
const totalSlots = this.components.length;
const usedSlots = this._size;
const freeSlots = this.freeIndices.length;
const fragmentation = totalSlots > 0 ? freeSlots / totalSlots : 0;
return {
totalSlots,
usedSlots,
freeSlots,
fragmentation
};
}
}
/**
* 组件存储管理器
* 管理所有组件类型的存储器
*/
export class ComponentStorageManager {
private storages = new Map<Function, ComponentStorage<any>>();
/**
* 获取或创建组件存储器
* @param componentType 组件类型
* @returns 组件存储器
*/
public getStorage<T extends Component>(componentType: ComponentType<T>): ComponentStorage<T> {
let storage = this.storages.get(componentType);
if (!storage) {
storage = new ComponentStorage(componentType);
this.storages.set(componentType, storage);
}
return storage;
}
/**
* 添加组件
* @param entityId 实体ID
* @param component 组件实例
*/
public addComponent<T extends Component>(entityId: number, component: T): void {
const componentType = component.constructor as ComponentType<T>;
const storage = this.getStorage(componentType);
storage.addComponent(entityId, component);
}
/**
* 获取组件
* @param entityId 实体ID
* @param componentType 组件类型
* @returns 组件实例或null
*/
public getComponent<T extends Component>(entityId: number, componentType: ComponentType<T>): T | null {
const storage = this.storages.get(componentType);
return storage ? storage.getComponent(entityId) : null;
}
/**
* 检查实体是否有组件
* @param entityId 实体ID
* @param componentType 组件类型
* @returns 是否有组件
*/
public hasComponent<T extends Component>(entityId: number, componentType: ComponentType<T>): boolean {
const storage = this.storages.get(componentType);
return storage ? storage.hasComponent(entityId) : false;
}
/**
* 移除组件
* @param entityId 实体ID
* @param componentType 组件类型
* @returns 被移除的组件或null
*/
public removeComponent<T extends Component>(entityId: number, componentType: ComponentType<T>): T | null {
const storage = this.storages.get(componentType);
return storage ? storage.removeComponent(entityId) : null;
}
/**
* 移除实体的所有组件
* @param entityId 实体ID
*/
public removeAllComponents(entityId: number): void {
for (const storage of this.storages.values()) {
storage.removeComponent(entityId);
}
}
/**
* 获取实体的组件位掩码
* @param entityId 实体ID
* @returns 组件位掩码
*/
public getComponentMask(entityId: number): bigint {
let mask = BigInt(0);
for (const [componentType, storage] of this.storages.entries()) {
if (storage.hasComponent(entityId)) {
mask |= ComponentRegistry.getBitMask(componentType as ComponentType);
}
}
return mask;
}
/**
* 压缩所有存储器
*/
public compactAll(): void {
for (const storage of this.storages.values()) {
storage.compact();
}
}
/**
* 获取所有存储器的统计信息
*/
public getAllStats(): Map<string, any> {
const stats = new Map<string, any>();
for (const [componentType, storage] of this.storages.entries()) {
const typeName = (componentType as any).name || 'Unknown';
stats.set(typeName, storage.getStats());
}
return stats;
}
/**
* 清空所有存储器
*/
public clear(): void {
for (const storage of this.storages.values()) {
storage.clear();
}
this.storages.clear();
}
}
+611
View File
@@ -0,0 +1,611 @@
/**
* 事件处理器函数类型
*/
export type EventHandler<T = any> = (event: T) => void;
/**
* 异步事件处理器函数类型
*/
export type AsyncEventHandler<T = any> = (event: T) => Promise<void>;
/**
* 事件监听器配置
*/
export interface EventListenerConfig {
/** 是否只执行一次 */
once?: boolean;
/** 优先级(数字越大优先级越高) */
priority?: number;
/** 是否异步执行 */
async?: boolean;
/** 执行上下文 */
context?: any;
}
/**
* 内部事件监听器
*/
interface InternalEventListener<T = any> {
handler: EventHandler<T> | AsyncEventHandler<T>;
config: EventListenerConfig;
id: string;
}
/**
* 事件统计信息
*/
export interface EventStats {
/** 事件类型 */
eventType: string;
/** 监听器数量 */
listenerCount: number;
/** 触发次数 */
triggerCount: number;
/** 总执行时间(毫秒) */
totalExecutionTime: number;
/** 平均执行时间(毫秒) */
averageExecutionTime: number;
/** 最后触发时间 */
lastTriggerTime: number;
}
/**
* 事件批处理配置
*/
export interface EventBatchConfig {
/** 批处理大小 */
batchSize: number;
/** 批处理延迟(毫秒) */
delay: number;
/** 是否启用批处理 */
enabled: boolean;
}
/**
* 类型安全的高性能事件系统
* 支持同步/异步事件、优先级、批处理等功能
*/
export class TypeSafeEventSystem {
private listeners = new Map<string, InternalEventListener[]>();
private stats = new Map<string, EventStats>();
private batchQueue = new Map<string, any[]>();
private batchTimers = new Map<string, number>();
private batchConfigs = new Map<string, EventBatchConfig>();
private nextListenerId = 0;
private isEnabled = true;
private maxListeners = 100; // 每个事件类型的最大监听器数量
/**
* 添加事件监听器
* @param eventType 事件类型
* @param handler 事件处理器
* @param config 监听器配置
* @returns 监听器ID(用于移除)
*/
public on<T>(
eventType: string,
handler: EventHandler<T>,
config: EventListenerConfig = {}
): string {
return this.addListener(eventType, handler, config);
}
/**
* 添加一次性事件监听器
* @param eventType 事件类型
* @param handler 事件处理器
* @param config 监听器配置
* @returns 监听器ID
*/
public once<T>(
eventType: string,
handler: EventHandler<T>,
config: EventListenerConfig = {}
): string {
return this.addListener(eventType, handler, { ...config, once: true });
}
/**
* 添加异步事件监听器
* @param eventType 事件类型
* @param handler 异步事件处理器
* @param config 监听器配置
* @returns 监听器ID
*/
public onAsync<T>(
eventType: string,
handler: AsyncEventHandler<T>,
config: EventListenerConfig = {}
): string {
return this.addListener(eventType, handler, { ...config, async: true });
}
/**
* 移除事件监听器
* @param eventType 事件类型
* @param listenerId 监听器ID
* @returns 是否成功移除
*/
public off(eventType: string, listenerId: string): boolean {
const listeners = this.listeners.get(eventType);
if (!listeners) return false;
const index = listeners.findIndex(l => l.id === listenerId);
if (index === -1) return false;
listeners.splice(index, 1);
// 如果没有监听器了,清理相关数据
if (listeners.length === 0) {
this.listeners.delete(eventType);
this.stats.delete(eventType);
}
return true;
}
/**
* 移除指定事件类型的所有监听器
* @param eventType 事件类型
*/
public offAll(eventType: string): void {
this.listeners.delete(eventType);
this.stats.delete(eventType);
this.clearBatch(eventType);
}
/**
* 触发事件
* @param eventType 事件类型
* @param event 事件数据
* @returns Promise(如果有异步监听器)
*/
public async emit<T>(eventType: string, event: T): Promise<void> {
if (!this.isEnabled) return;
// 检查是否启用了批处理
const batchConfig = this.batchConfigs.get(eventType);
if (batchConfig?.enabled) {
this.addToBatch(eventType, event);
return;
}
await this.executeEvent(eventType, event);
}
/**
* 同步触发事件(忽略异步监听器)
* @param eventType 事件类型
* @param event 事件数据
*/
public emitSync<T>(eventType: string, event: T): void {
if (!this.isEnabled) return;
const listeners = this.listeners.get(eventType);
if (!listeners || listeners.length === 0) return;
const startTime = performance.now();
const toRemove: string[] = [];
// 按优先级排序
const sortedListeners = this.sortListenersByPriority(listeners);
for (const listener of sortedListeners) {
if (listener.config.async) continue; // 跳过异步监听器
try {
if (listener.config.context) {
(listener.handler as EventHandler<T>).call(listener.config.context, event);
} else {
(listener.handler as EventHandler<T>)(event);
}
if (listener.config.once) {
toRemove.push(listener.id);
}
} catch (error) {
console.error(`Error in event handler for ${eventType}:`, error);
}
}
// 移除一次性监听器
this.removeListeners(eventType, toRemove);
// 更新统计信息
this.updateStats(eventType, performance.now() - startTime);
}
/**
* 设置事件批处理配置
* @param eventType 事件类型
* @param config 批处理配置
*/
public setBatchConfig(eventType: string, config: EventBatchConfig): void {
this.batchConfigs.set(eventType, config);
}
/**
* 立即处理指定事件类型的批处理队列
* @param eventType 事件类型
*/
public flushBatch(eventType: string): void {
const batch = this.batchQueue.get(eventType);
if (!batch || batch.length === 0) return;
// 清除定时器
const timer = this.batchTimers.get(eventType);
if (timer) {
clearTimeout(timer);
this.batchTimers.delete(eventType);
}
// 处理批处理事件
this.processBatch(eventType, batch);
// 清空队列
this.batchQueue.delete(eventType);
}
/**
* 获取事件统计信息
* @param eventType 事件类型(可选)
* @returns 统计信息
*/
public getStats(eventType?: string): EventStats | Map<string, EventStats> {
if (eventType) {
return this.stats.get(eventType) || this.createEmptyStats(eventType);
}
return new Map(this.stats);
}
/**
* 重置统计信息
* @param eventType 事件类型(可选,不指定则重置所有)
*/
public resetStats(eventType?: string): void {
if (eventType) {
this.stats.delete(eventType);
} else {
this.stats.clear();
}
}
/**
* 启用/禁用事件系统
* @param enabled 是否启用
*/
public setEnabled(enabled: boolean): void {
this.isEnabled = enabled;
}
/**
* 检查是否有指定事件类型的监听器
* @param eventType 事件类型
* @returns 是否有监听器
*/
public hasListeners(eventType: string): boolean {
const listeners = this.listeners.get(eventType);
return listeners ? listeners.length > 0 : false;
}
/**
* 获取指定事件类型的监听器数量
* @param eventType 事件类型
* @returns 监听器数量
*/
public getListenerCount(eventType: string): number {
const listeners = this.listeners.get(eventType);
return listeners ? listeners.length : 0;
}
/**
* 清空所有事件监听器和数据
*/
public clear(): void {
this.listeners.clear();
this.stats.clear();
this.clearAllBatches();
}
/**
* 设置每个事件类型的最大监听器数量
* @param max 最大数量
*/
public setMaxListeners(max: number): void {
this.maxListeners = max;
}
/**
* 添加监听器的内部实现
* @param eventType 事件类型
* @param handler 事件处理器
* @param config 配置
* @returns 监听器ID
*/
private addListener<T>(
eventType: string,
handler: EventHandler<T> | AsyncEventHandler<T>,
config: EventListenerConfig
): string {
let listeners = this.listeners.get(eventType);
if (!listeners) {
listeners = [];
this.listeners.set(eventType, listeners);
}
// 检查监听器数量限制
if (listeners.length >= this.maxListeners) {
console.warn(`Maximum listeners (${this.maxListeners}) exceeded for event type: ${eventType}`);
return '';
}
const listenerId = `listener_${this.nextListenerId++}`;
const listener: InternalEventListener<T> = {
handler,
config: {
priority: 0,
...config
},
id: listenerId
};
listeners.push(listener);
// 初始化统计信息
if (!this.stats.has(eventType)) {
this.stats.set(eventType, this.createEmptyStats(eventType));
}
return listenerId;
}
/**
* 执行事件的内部实现
* @param eventType 事件类型
* @param event 事件数据
*/
private async executeEvent<T>(eventType: string, event: T): Promise<void> {
const listeners = this.listeners.get(eventType);
if (!listeners || listeners.length === 0) return;
const startTime = performance.now();
const toRemove: string[] = [];
// 按优先级排序
const sortedListeners = this.sortListenersByPriority(listeners);
// 分离同步和异步监听器
const syncListeners = sortedListeners.filter(l => !l.config.async);
const asyncListeners = sortedListeners.filter(l => l.config.async);
// 执行同步监听器
for (const listener of syncListeners) {
try {
if (listener.config.context) {
(listener.handler as EventHandler<T>).call(listener.config.context, event);
} else {
(listener.handler as EventHandler<T>)(event);
}
if (listener.config.once) {
toRemove.push(listener.id);
}
} catch (error) {
console.error(`Error in sync event handler for ${eventType}:`, error);
}
}
// 执行异步监听器
const asyncPromises = asyncListeners.map(async (listener) => {
try {
if (listener.config.context) {
await (listener.handler as AsyncEventHandler<T>).call(listener.config.context, event);
} else {
await (listener.handler as AsyncEventHandler<T>)(event);
}
if (listener.config.once) {
toRemove.push(listener.id);
}
} catch (error) {
console.error(`Error in async event handler for ${eventType}:`, error);
}
});
// 等待所有异步监听器完成
await Promise.all(asyncPromises);
// 移除一次性监听器
this.removeListeners(eventType, toRemove);
// 更新统计信息
this.updateStats(eventType, performance.now() - startTime);
}
/**
* 按优先级排序监听器
* @param listeners 监听器数组
* @returns 排序后的监听器数组
*/
private sortListenersByPriority<T>(listeners: InternalEventListener<T>[]): InternalEventListener<T>[] {
return listeners.slice().sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0));
}
/**
* 移除指定的监听器
* @param eventType 事件类型
* @param listenerIds 要移除的监听器ID数组
*/
private removeListeners(eventType: string, listenerIds: string[]): void {
if (listenerIds.length === 0) return;
const listeners = this.listeners.get(eventType);
if (!listeners) return;
for (const id of listenerIds) {
const index = listeners.findIndex(l => l.id === id);
if (index !== -1) {
listeners.splice(index, 1);
}
}
// 如果没有监听器了,清理相关数据
if (listeners.length === 0) {
this.listeners.delete(eventType);
this.stats.delete(eventType);
}
}
/**
* 添加事件到批处理队列
* @param eventType 事件类型
* @param event 事件数据
*/
private addToBatch<T>(eventType: string, event: T): void {
let batch = this.batchQueue.get(eventType);
if (!batch) {
batch = [];
this.batchQueue.set(eventType, batch);
}
batch.push(event);
const config = this.batchConfigs.get(eventType)!;
// 如果达到批处理大小,立即处理
if (batch.length >= config.batchSize) {
this.flushBatch(eventType);
return;
}
// 设置延迟处理定时器
if (!this.batchTimers.has(eventType)) {
const timer = setTimeout(() => {
this.flushBatch(eventType);
}, config.delay);
this.batchTimers.set(eventType, timer as any);
}
}
/**
* 处理批处理事件
* @param eventType 事件类型
* @param batch 批处理事件数组
*/
private async processBatch<T>(eventType: string, batch: T[]): Promise<void> {
// 创建批处理事件对象
const batchEvent = {
type: eventType,
events: batch,
count: batch.length,
timestamp: Date.now()
};
// 触发批处理事件
await this.executeEvent(`${eventType}:batch`, batchEvent);
}
/**
* 清除指定事件类型的批处理
* @param eventType 事件类型
*/
private clearBatch(eventType: string): void {
this.batchQueue.delete(eventType);
const timer = this.batchTimers.get(eventType);
if (timer) {
clearTimeout(timer);
this.batchTimers.delete(eventType);
}
}
/**
* 清除所有批处理
*/
private clearAllBatches(): void {
this.batchQueue.clear();
for (const timer of this.batchTimers.values()) {
clearTimeout(timer);
}
this.batchTimers.clear();
this.batchConfigs.clear();
}
/**
* 更新事件统计信息
* @param eventType 事件类型
* @param executionTime 执行时间
*/
private updateStats(eventType: string, executionTime: number): void {
let stats = this.stats.get(eventType);
if (!stats) {
stats = this.createEmptyStats(eventType);
this.stats.set(eventType, stats);
}
stats.triggerCount++;
stats.totalExecutionTime += executionTime;
stats.averageExecutionTime = stats.totalExecutionTime / stats.triggerCount;
stats.lastTriggerTime = Date.now();
stats.listenerCount = this.getListenerCount(eventType);
}
/**
* 创建空的统计信息
* @param eventType 事件类型
* @returns 空的统计信息
*/
private createEmptyStats(eventType: string): EventStats {
return {
eventType,
listenerCount: 0,
triggerCount: 0,
totalExecutionTime: 0,
averageExecutionTime: 0,
lastTriggerTime: 0
};
}
}
/**
* 全局事件系统实例
*/
export const GlobalEventSystem = new TypeSafeEventSystem();
/**
* 事件装饰器 - 用于自动注册事件监听器
* @param eventType 事件类型
* @param config 监听器配置
*/
export function EventListener(eventType: string, config: EventListenerConfig = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
// 在类实例化时自动注册监听器
const initMethod = target.constructor.prototype.initEventListeners || function() {};
target.constructor.prototype.initEventListeners = function() {
initMethod.call(this);
GlobalEventSystem.on(eventType, originalMethod.bind(this), config);
};
};
}
/**
* 异步事件装饰器
* @param eventType 事件类型
* @param config 监听器配置
*/
export function AsyncEventListener(eventType: string, config: EventListenerConfig = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const initMethod = target.constructor.prototype.initEventListeners || function() {};
target.constructor.prototype.initEventListeners = function() {
initMethod.call(this);
GlobalEventSystem.onAsync(eventType, originalMethod.bind(this), config);
};
};
}
+673
View File
@@ -0,0 +1,673 @@
import { Entity } from '../Entity';
import { Component } from '../Component';
import { Scene } from '../Scene';
import { ComponentType, ComponentStorageManager } from './ComponentStorage';
import { QuerySystem, QueryBuilder } from './QuerySystem';
import { TypeSafeEventSystem } from './EventSystem';
/**
* 实体构建器 - 提供流式API创建和配置实体
*/
export class EntityBuilder {
private entity: Entity;
private scene: Scene;
private storageManager: ComponentStorageManager;
constructor(scene: Scene, storageManager: ComponentStorageManager) {
this.scene = scene;
this.storageManager = storageManager;
this.entity = new Entity("", scene.identifierPool.checkOut());
}
/**
* 设置实体名称
* @param name 实体名称
* @returns 实体构建器
*/
public named(name: string): EntityBuilder {
this.entity.name = name;
return this;
}
/**
* 设置实体标签
* @param tag 标签
* @returns 实体构建器
*/
public tagged(tag: number): EntityBuilder {
this.entity.tag = tag;
return this;
}
/**
* 添加组件
* @param component 组件实例
* @returns 实体构建器
*/
public with<T extends Component>(component: T): EntityBuilder {
this.entity.addComponent(component);
return this;
}
/**
* 添加多个组件
* @param components 组件数组
* @returns 实体构建器
*/
public withComponents(...components: Component[]): EntityBuilder {
for (const component of components) {
this.entity.addComponent(component);
}
return this;
}
/**
* 条件性添加组件
* @param condition 条件
* @param component 组件实例
* @returns 实体构建器
*/
public withIf<T extends Component>(condition: boolean, component: T): EntityBuilder {
if (condition) {
this.entity.addComponent(component);
}
return this;
}
/**
* 使用工厂函数创建并添加组件
* @param factory 组件工厂函数
* @returns 实体构建器
*/
public withFactory<T extends Component>(factory: () => T): EntityBuilder {
const component = factory();
this.entity.addComponent(component);
return this;
}
/**
* 配置组件属性
* @param componentType 组件类型
* @param configurator 配置函数
* @returns 实体构建器
*/
public configure<T extends Component>(
componentType: ComponentType<T>,
configurator: (component: T) => void
): EntityBuilder {
const component = this.entity.getComponent(componentType);
if (component) {
configurator(component);
}
return this;
}
/**
* 设置实体位置(如果有Transform组件)
* @param x X坐标
* @param y Y坐标
* @param z Z坐标(可选)
* @returns 实体构建器
*/
public at(x: number, y: number, z: number = 0): EntityBuilder {
// 直接使用Entity的position属性
this.entity.position.x = x;
this.entity.position.y = y;
return this;
}
/**
* 设置实体旋转(如果有Transform组件)
* @param rotation 旋转角度
* @returns 实体构建器
*/
public rotated(rotation: number): EntityBuilder {
this.entity.rotation = rotation;
return this;
}
/**
* 设置实体缩放(如果有Transform组件)
* @param scaleX X轴缩放
* @param scaleY Y轴缩放(可选,默认与X轴相同)
* @returns 实体构建器
*/
public scaled(scaleX: number, scaleY?: number): EntityBuilder {
this.entity.scale.x = scaleX;
this.entity.scale.y = scaleY !== undefined ? scaleY : scaleX;
return this;
}
/**
* 设置实体为启用状态
* @param enabled 是否启用
* @returns 实体构建器
*/
public enabled(enabled: boolean = true): EntityBuilder {
this.entity.enabled = enabled;
return this;
}
/**
* 设置实体为活跃状态
* @param active 是否活跃
* @returns 实体构建器
*/
public active(active: boolean = true): EntityBuilder {
this.entity.active = active;
return this;
}
/**
* 添加子实体
* @param childBuilder 子实体构建器
* @returns 实体构建器
*/
public withChild(childBuilder: EntityBuilder): EntityBuilder {
const child = childBuilder.build();
this.entity.addChild(child);
return this;
}
/**
* 批量添加子实体
* @param childBuilders 子实体构建器数组
* @returns 实体构建器
*/
public withChildren(...childBuilders: EntityBuilder[]): EntityBuilder {
for (const childBuilder of childBuilders) {
const child = childBuilder.build();
this.entity.addChild(child);
}
return this;
}
/**
* 使用工厂函数创建子实体
* @param childFactory 子实体工厂函数
* @returns 实体构建器
*/
public withChildFactory(childFactory: (parent: Entity) => EntityBuilder): EntityBuilder {
const childBuilder = childFactory(this.entity);
const child = childBuilder.build();
this.entity.addChild(child);
return this;
}
/**
* 条件性添加子实体
* @param condition 条件
* @param childBuilder 子实体构建器
* @returns 实体构建器
*/
public withChildIf(condition: boolean, childBuilder: EntityBuilder): EntityBuilder {
if (condition) {
const child = childBuilder.build();
this.entity.addChild(child);
}
return this;
}
/**
* 构建并返回实体
* @returns 构建的实体
*/
public build(): Entity {
return this.entity;
}
/**
* 构建实体并添加到场景
* @returns 构建的实体
*/
public spawn(): Entity {
this.scene.addEntity(this.entity);
return this.entity;
}
/**
* 克隆当前构建器
* @returns 新的实体构建器
*/
public clone(): EntityBuilder {
const newBuilder = new EntityBuilder(this.scene, this.storageManager);
// 这里需要深度克隆实体,简化实现
newBuilder.entity = this.entity; // 实际应该是深度克隆
return newBuilder;
}
}
/**
* 场景构建器 - 提供流式API创建和配置场景
*/
export class SceneBuilder {
private scene: Scene;
constructor() {
this.scene = new Scene();
}
/**
* 设置场景名称
* @param name 场景名称
* @returns 场景构建器
*/
public named(name: string): SceneBuilder {
this.scene.name = name;
return this;
}
/**
* 添加实体
* @param entity 实体
* @returns 场景构建器
*/
public withEntity(entity: Entity): SceneBuilder {
this.scene.addEntity(entity);
return this;
}
/**
* 使用实体构建器添加实体
* @param builderFn 实体构建器函数
* @returns 场景构建器
*/
public withEntityBuilder(builderFn: (builder: EntityBuilder) => EntityBuilder): SceneBuilder {
const builder = new EntityBuilder(this.scene, this.scene.componentStorageManager);
const configuredBuilder = builderFn(builder);
const entity = configuredBuilder.build();
this.scene.addEntity(entity);
return this;
}
/**
* 批量添加实体
* @param entities 实体数组
* @returns 场景构建器
*/
public withEntities(...entities: Entity[]): SceneBuilder {
for (const entity of entities) {
this.scene.addEntity(entity);
}
return this;
}
/**
* 添加系统
* @param system 系统实例
* @returns 场景构建器
*/
public withSystem(system: any): SceneBuilder {
this.scene.addSystem(system);
return this;
}
/**
* 批量添加系统
* @param systems 系统数组
* @returns 场景构建器
*/
public withSystems(...systems: any[]): SceneBuilder {
for (const system of systems) {
this.scene.addSystem(system);
}
return this;
}
/**
* 构建并返回场景
* @returns 构建的场景
*/
public build(): Scene {
return this.scene;
}
}
/**
* 组件构建器 - 提供流式API创建组件
*/
export class ComponentBuilder<T extends Component> {
private component: T;
constructor(componentClass: new (...args: any[]) => T, ...args: any[]) {
this.component = new componentClass(...args);
}
/**
* 设置组件属性
* @param property 属性名
* @param value 属性值
* @returns 组件构建器
*/
public set<K extends keyof T>(property: K, value: T[K]): ComponentBuilder<T> {
this.component[property] = value;
return this;
}
/**
* 使用配置函数设置组件
* @param configurator 配置函数
* @returns 组件构建器
*/
public configure(configurator: (component: T) => void): ComponentBuilder<T> {
configurator(this.component);
return this;
}
/**
* 条件性设置属性
* @param condition 条件
* @param property 属性名
* @param value 属性值
* @returns 组件构建器
*/
public setIf<K extends keyof T>(condition: boolean, property: K, value: T[K]): ComponentBuilder<T> {
if (condition) {
this.component[property] = value;
}
return this;
}
/**
* 构建并返回组件
* @returns 构建的组件
*/
public build(): T {
return this.component;
}
}
/**
* ECS流式API主入口
* 提供统一的流式接口
*/
export class ECSFluentAPI {
private scene: Scene;
private querySystem: QuerySystem;
private eventSystem: TypeSafeEventSystem;
constructor(scene: Scene, querySystem: QuerySystem, eventSystem: TypeSafeEventSystem) {
this.scene = scene;
this.querySystem = querySystem;
this.eventSystem = eventSystem;
}
/**
* 创建实体构建器
* @returns 实体构建器
*/
public createEntity(): EntityBuilder {
return new EntityBuilder(this.scene, this.scene.componentStorageManager);
}
/**
* 创建场景构建器
* @returns 场景构建器
*/
public createScene(): SceneBuilder {
return new SceneBuilder();
}
/**
* 创建组件构建器
* @param componentClass 组件类
* @param args 构造参数
* @returns 组件构建器
*/
public createComponent<T extends Component>(
componentClass: new (...args: any[]) => T,
...args: any[]
): ComponentBuilder<T> {
return new ComponentBuilder(componentClass, ...args);
}
/**
* 创建查询构建器
* @returns 查询构建器
*/
public query(): QueryBuilder {
return this.querySystem.createQuery();
}
/**
* 查找实体(简化版)
* @param componentTypes 组件类型
* @returns 实体数组
*/
public find(...componentTypes: ComponentType[]): Entity[] {
return this.querySystem.queryAll(...componentTypes).entities;
}
/**
* 查找第一个匹配的实体
* @param componentTypes 组件类型
* @returns 实体或null
*/
public findFirst(...componentTypes: ComponentType[]): Entity | null {
const result = this.querySystem.queryAll(...componentTypes);
return result.entities.length > 0 ? result.entities[0] : null;
}
/**
* 按名称查找实体
* @param name 实体名称
* @returns 实体或null
*/
public findByName(name: string): Entity | null {
return this.scene.getEntityByName(name);
}
/**
* 按标签查找实体
* @param tag 标签
* @returns 实体数组
*/
public findByTag(tag: number): Entity[] {
return this.scene.getEntitiesByTag(tag);
}
/**
* 触发事件
* @param eventType 事件类型
* @param event 事件数据
*/
public emit<T>(eventType: string, event: T): void {
this.eventSystem.emitSync(eventType, event);
}
/**
* 异步触发事件
* @param eventType 事件类型
* @param event 事件数据
*/
public async emitAsync<T>(eventType: string, event: T): Promise<void> {
await this.eventSystem.emit(eventType, event);
}
/**
* 监听事件
* @param eventType 事件类型
* @param handler 事件处理器
* @returns 监听器ID
*/
public on<T>(eventType: string, handler: (event: T) => void): string {
return this.eventSystem.on(eventType, handler);
}
/**
* 一次性监听事件
* @param eventType 事件类型
* @param handler 事件处理器
* @returns 监听器ID
*/
public once<T>(eventType: string, handler: (event: T) => void): string {
return this.eventSystem.once(eventType, handler);
}
/**
* 移除事件监听器
* @param eventType 事件类型
* @param listenerId 监听器ID
*/
public off(eventType: string, listenerId: string): void {
this.eventSystem.off(eventType, listenerId);
}
/**
* 批量操作实体
* @param entities 实体数组
* @returns 批量操作器
*/
public batch(entities: Entity[]): EntityBatchOperator {
return new EntityBatchOperator(entities);
}
/**
* 获取场景统计信息
* @returns 统计信息
*/
public getStats(): {
entityCount: number;
systemCount: number;
componentStats: Map<string, any>;
queryStats: any;
eventStats: Map<string, any>;
} {
return {
entityCount: this.scene.entities.count,
systemCount: this.scene.systems.length,
componentStats: this.scene.componentStorageManager.getAllStats(),
queryStats: this.querySystem.getStats(),
eventStats: this.eventSystem.getStats() as Map<string, any>
};
}
}
/**
* 实体批量操作器
* 提供对多个实体的批量操作
*/
export class EntityBatchOperator {
private entities: Entity[];
constructor(entities: Entity[]) {
this.entities = entities;
}
/**
* 批量添加组件
* @param component 组件实例
* @returns 批量操作器
*/
public addComponent<T extends Component>(component: T): EntityBatchOperator {
for (const entity of this.entities) {
entity.addComponent(component);
}
return this;
}
/**
* 批量移除组件
* @param componentType 组件类型
* @returns 批量操作器
*/
public removeComponent<T extends Component>(componentType: ComponentType<T>): EntityBatchOperator {
for (const entity of this.entities) {
entity.removeComponentByType(componentType);
}
return this;
}
/**
* 批量设置活跃状态
* @param active 是否活跃
* @returns 批量操作器
*/
public setActive(active: boolean): EntityBatchOperator {
for (const entity of this.entities) {
entity.active = active;
}
return this;
}
/**
* 批量设置标签
* @param tag 标签
* @returns 批量操作器
*/
public setTag(tag: number): EntityBatchOperator {
for (const entity of this.entities) {
entity.tag = tag;
}
return this;
}
/**
* 批量执行操作
* @param operation 操作函数
* @returns 批量操作器
*/
public forEach(operation: (entity: Entity, index: number) => void): EntityBatchOperator {
this.entities.forEach(operation);
return this;
}
/**
* 过滤实体
* @param predicate 过滤条件
* @returns 新的批量操作器
*/
public filter(predicate: (entity: Entity) => boolean): EntityBatchOperator {
return new EntityBatchOperator(this.entities.filter(predicate));
}
/**
* 获取实体数组
* @returns 实体数组
*/
public toArray(): Entity[] {
return this.entities.slice();
}
/**
* 获取实体数量
* @returns 实体数量
*/
public count(): number {
return this.entities.length;
}
}
/**
* 创建ECS流式API实例
* @param scene 场景
* @param querySystem 查询系统
* @param eventSystem 事件系统
* @returns ECS流式API实例
*/
export function createECSAPI(
scene: Scene,
querySystem: QuerySystem,
eventSystem: TypeSafeEventSystem
): ECSFluentAPI {
return new ECSFluentAPI(scene, querySystem, eventSystem);
}
/**
* 全局ECS流式API实例(需要在使用前初始化)
*/
export let ECS: ECSFluentAPI;
/**
* 初始化全局ECS API
* @param scene 场景
* @param querySystem 查询系统
* @param eventSystem 事件系统
*/
export function initializeECS(
scene: Scene,
querySystem: QuerySystem,
eventSystem: TypeSafeEventSystem
): void {
ECS = createECSAPI(scene, querySystem, eventSystem);
}
File diff suppressed because it is too large Load Diff
+17 -15
View File
@@ -1,16 +1,18 @@
module es {
export enum CoreEvents {
/**
*
*/
sceneChanged,
/**
*
*/
frameUpdated,
/**
*
*/
renderChanged,
}
/**
*
*
*/
export enum CoreEvents {
/**
*
*/
sceneChanged,
/**
*
*/
frameUpdated,
/**
*
*/
renderChanged,
}
+1316 -524
View File
File diff suppressed because it is too large Load Diff
+331 -251
View File
@@ -1,285 +1,365 @@
///<reference path="../Math/Vector2.ts" />
module es {
/** 场景 */
export class Scene {
/** 这个场景中的实体列表 */
public readonly entities: EntityList;
/** 管理所有实体处理器 */
public readonly entityProcessors: EntityProcessorList;
import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList';
import { EntityProcessorList } from './Utils/EntityProcessorList';
import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
public readonly _sceneComponents: SceneComponent[] = [];
public readonly identifierPool: IdentifierPool;
private _didSceneBegin: boolean;
/**
*
*
*
*
*
* @example
* ```typescript
* class GameScene extends Scene {
* public initialize(): void {
* // 创建游戏实体
* const player = this.createEntity("Player");
*
* // 添加系统
* this.addEntityProcessor(new MovementSystem());
* }
* }
* ```
*/
export class Scene {
/**
*
*
*
*/
public name: string = "";
constructor() {
this.entities = new EntityList(this);
/**
*
*
*
*/
public readonly entities: EntityList;
this.entityProcessors = new EntityProcessorList();
this.identifierPool = new IdentifierPool();
/**
*
*
*
*/
public readonly entityProcessors: EntityProcessorList;
this.initialize();
}
/**
* ID池
*
*
*/
public readonly identifierPool: IdentifierPool;
/**
*
*
*
* 便
*/
public initialize() {
}
/**
*
*
*
*/
public readonly componentStorageManager: ComponentStorageManager;
/**
*
*
*
* UI等等
*/
public onStart() {
}
/**
*
*
*
*/
public readonly querySystem: QuerySystem;
/**
*
*
*
*
*/
public unload() {
}
/**
*
*
*
*/
public readonly eventSystem: TypeSafeEventSystem;
/**
*
*
* onStart方法
*/
public begin() {
// 重置物理系统
Physics.reset();
/**
*
*/
private _didSceneBegin: boolean = false;
// 启动实体处理器
if (this.entityProcessors != null)
this.entityProcessors.begin();
/**
*
*/
public get systems(): EntitySystem[] {
return this.entityProcessors.processors;
}
// 标记场景已开始运行并调用onStart方法
this._didSceneBegin = true;
this.onStart();
}
/**
*
*/
constructor() {
this.entities = new EntityList(this);
this.entityProcessors = new EntityProcessorList();
this.identifierPool = new IdentifierPool();
this.componentStorageManager = new ComponentStorageManager();
this.querySystem = new QuerySystem();
this.eventSystem = new TypeSafeEventSystem();
/**
*
*
* onRemovedFromScene方法unload方法
*/
public end() {
// 标记场景已结束运行
this._didSceneBegin = false;
this.initialize();
}
// 移除所有实体并调用它们的onRemovedFromScene方法
this.entities.removeAllEntities();
/**
*
*
*
*/
public initialize(): void {
}
for (let i = 0; i < this._sceneComponents.length; i++) {
this._sceneComponents[i].onRemovedFromScene();
}
this._sceneComponents.length = 0;
/**
*
*
*
*/
public onStart(): void {
}
// 清除物理系统
Physics.clear();
/**
*
*
*
*/
public unload(): void {
}
// 结束实体处理器
if (this.entityProcessors)
this.entityProcessors.end();
/**
*
*
* onStart方法
*/
public begin() {
// 启动实体处理器
if (this.entityProcessors != null)
this.entityProcessors.begin();
// 调用卸载方法
this.unload();
}
// 标记场景已开始运行并调用onStart方法
this._didSceneBegin = true;
this.onStart();
}
/**
*
*/
public update() {
// 更新实体列表
this.entities.updateLists();
/**
*
*
* unload方法
*/
public end() {
// 标记场景已结束运行
this._didSceneBegin = false;
// 更新场景组件
for (let i = this._sceneComponents.length - 1; i >= 0; i--) {
if (this._sceneComponents[i].enabled)
this._sceneComponents[i].update();
}
// 移除所有实体
this.entities.removeAllEntities();
// 更新实体处理器
if (this.entityProcessors != null)
this.entityProcessors.update();
// 清空组件存储
this.componentStorageManager.clear();
// 更新实体组
this.entities.update();
// 结束实体处理器
if (this.entityProcessors)
this.entityProcessors.end();
// 更新实体处理器的后处理方法
if (this.entityProcessors != null)
this.entityProcessors.lateUpdate();
}
// 调用卸载方法
this.unload();
}
/**
* SceneComponent
* @param component
*/
public addSceneComponent<T extends SceneComponent>(component: T): T {
component.scene = this;
component.onEnabled();
this._sceneComponents.push(component);
this._sceneComponents.sort(component.compare);
return component;
}
/**
*
*/
public update() {
// 更新实体列表
this.entities.updateLists();
/**
* T的第一个SceneComponent并返回它null
* @param type
*/
public getSceneComponent<T extends SceneComponent>(type) {
for (let i = 0; i < this._sceneComponents.length; i++) {
let component = this._sceneComponents[i];
if (component instanceof type)
return component as T;
}
// 更新实体处理器
if (this.entityProcessors != null)
this.entityProcessors.update();
return null;
}
// 更新实体组
this.entities.update();
/**
* T的第一个SceneComponent并返回它SceneComponentSceneComponent
* @param type
*/
public getOrCreateSceneComponent<T extends SceneComponent>(type) {
let comp = this.getSceneComponent<T>(type);
if (comp == null)
comp = this.addSceneComponent<T>(new type());
// 更新实体处理器的后处理方法
if (this.entityProcessors != null)
this.entityProcessors.lateUpdate();
}
return comp;
}
/**
*
* @param name
*/
public createEntity(name: string) {
let entity = new Entity(name, this.identifierPool.checkOut());
return this.addEntity(entity);
}
/**
* SceneComponents列表中删除一个SceneComponent
* @param component
*/
public removeSceneComponent(component: SceneComponent) {
const sceneComponentList = new es.List(this._sceneComponents);
Insist.isTrue(sceneComponentList.contains(component), `SceneComponent${component}不在SceneComponents列表中!`);
sceneComponentList.remove(component);
component.onRemovedFromScene();
}
/**
*
* @param entity
*/
public addEntity(entity: Entity) {
this.entities.add(entity);
entity.scene = this;
/**
*
* @param name
*/
public createEntity(name: string) {
let entity = new Entity(name, this.identifierPool.checkOut());
return this.addEntity(entity);
}
// 将实体添加到查询系统
this.querySystem.addEntity(entity);
/**
*
* @param entity
*/
public addEntity(entity: Entity) {
Insist.isFalse(new es.List(this.entities.buffer).contains(entity), `您试图将同一实体添加到场景两次: ${entity}`);
this.entities.add(entity);
entity.scene = this;
// 触发实体添加事件
this.eventSystem.emitSync('entity:added', { entity, scene: this });
for (let i = 0; i < entity.transform.childCount; i++)
this.addEntity(entity.transform.getChild(i).entity);
return entity;
}
return entity;
}
/**
*
*/
public destroyAllEntities() {
for (let i = 0; i < this.entities.count; i++) {
this.entities.buffer[i].destroy();
}
}
/**
*
* @param name
*/
public findEntity(name: string): Entity {
return this.entities.findEntity(name);
}
public findEntityById(id: number): Entity {
return this.entities.findEntityById(id);
}
/**
*
* @param tag
*/
public findEntitiesWithTag(tag: number): Entity[] {
return this.entities.entitiesWithTag(tag);
}
/**
*
* @param tag
* @returns
*/
public findEntityWithTag(tag: number): Entity {
return this.entities.entityWithTag(tag);
}
/**
* T的组件
* @param type
*/
public findComponentOfType<T extends Component>(type: new (...args) => T): T {
return this.entities.findComponentOfType<T>(type);
}
/**
* T的所有已启用已加载组件的列表
* @param type
*/
public findComponentsOfType<T extends Component>(type: new (...args) => T): T[] {
return this.entities.findComponentsOfType<T>(type);
}
/**
*
* @param type
* @returns
*/
public findEntitiesOfComponent(...types): Entity[] {
return this.entities.findEntitiesOfComponent(...types);
}
/**
* EntitySystem处理器
* @param processor
*/
public addEntityProcessor(processor: EntitySystem) {
processor.scene = this;
this.entityProcessors.add(processor);
processor.setUpdateOrder(this.entityProcessors.count - 1);
this.entityProcessors.clearDirty();
return processor;
}
/**
* EntitySystem处理器
* @param processor
*/
public removeEntityProcessor(processor: EntitySystem) {
this.entityProcessors.remove(processor);
}
/**
* EntitySystem处理器
*/
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T {
return this.entityProcessors.getProcessor<T>(type);
/**
*
*/
public destroyAllEntities() {
for (let i = 0; i < this.entities.count; i++) {
this.entities.buffer[i].destroy();
}
}
/**
*
* @param name
*/
public findEntity(name: string): Entity | null {
return this.entities.findEntity(name);
}
/**
* ID查找实体
* @param id ID
*/
public findEntityById(id: number): Entity | null {
return this.entities.findEntityById(id);
}
/**
*
* @param tag
*/
public findEntitiesByTag(tag: number): Entity[] {
const result: Entity[] = [];
for (const entity of this.entities.buffer) {
if (entity.tag === tag) {
result.push(entity);
}
}
return result;
}
/**
*
* @param name
*/
public getEntityByName(name: string): Entity | null {
return this.findEntity(name);
}
/**
*
* @param tag
*/
public getEntitiesByTag(tag: number): Entity[] {
return this.findEntitiesByTag(tag);
}
/**
* EntitySystem处理器
* @param processor
*/
public addEntityProcessor(processor: EntitySystem) {
processor.scene = this;
this.entityProcessors.add(processor);
processor.setUpdateOrder(this.entityProcessors.count - 1);
return processor;
}
/**
* addEntityProcessor的别名
* @param system
*/
public addSystem(system: EntitySystem) {
return this.addEntityProcessor(system);
}
/**
* EntitySystem处理器
* @param processor
*/
public removeEntityProcessor(processor: EntitySystem) {
this.entityProcessors.remove(processor);
}
/**
* EntitySystem处理器
* @param type
*/
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
return this.entityProcessors.getProcessor(type);
}
/**
*
*/
public getStats(): {
entityCount: number;
processorCount: number;
componentStorageStats: Map<string, any>;
} {
return {
entityCount: this.entities.count,
processorCount: this.entityProcessors.count,
componentStorageStats: this.componentStorageManager.getAllStats()
};
}
/**
*
*/
public compactComponentStorage(): void {
this.componentStorageManager.compactAll();
}
/**
*
*/
public getDebugInfo(): {
name: string;
entityCount: number;
processorCount: number;
isRunning: boolean;
entities: Array<{
name: string;
id: number;
componentCount: number;
componentTypes: string[];
}>;
processors: Array<{
name: string;
updateOrder: number;
entityCount: number;
}>;
componentStats: Map<string, any>;
} {
return {
name: this.constructor.name,
entityCount: this.entities.count,
processorCount: this.entityProcessors.count,
isRunning: this._didSceneBegin,
entities: this.entities.buffer.map(entity => ({
name: entity.name,
id: entity.id,
componentCount: entity.components.length,
componentTypes: entity.components.map(c => c.constructor.name)
})),
processors: this.entityProcessors.processors.map(processor => ({
name: processor.constructor.name,
updateOrder: processor.updateOrder,
entityCount: (processor as any)._entities?.length || 0
})),
componentStats: this.componentStorageManager.getAllStats()
};
}
}
-97
View File
@@ -1,97 +0,0 @@
module es {
/**
* SceneTransition用于从一个场景过渡到另一个场景
* sceneLoadAction为null
*/
export abstract class SceneTransition {
/** 该函数应返回新加载的场景 */
protected sceneLoadAction: () => Scene;
/**
* loadNextScene执行时调用
*
*/
public onScreenObscured: Function;
/**
* 便
*/
public onTransitionCompleted: Function;
/**
*
*/
public _loadsNewScene: boolean = false;
private _hasPreviousSceneRender: boolean = false;
public get hasPreviousSceneRender() {
if (!this._hasPreviousSceneRender) {
this._hasPreviousSceneRender = true;
return false;
}
return true;
}
/**
* _isNewSceneLoaded变为true时会褪色
* isNewSceneLoaded设置为true
*/
public _isNewSceneLoaded: boolean = false;
protected constructor(sceneLoadAction: () => Scene) {
this.sceneLoadAction = sceneLoadAction;
this._loadsNewScene = sceneLoadAction != null;
}
protected * LoadNextScene() {
// 如果我们有渲染界面,可以在这让玩家知道屏幕是模糊的(正在加载)
if (this.onScreenObscured != null)
this.onScreenObscured();
// 如果我们不加载一个新场景,我们只需设置标志
if (!this._loadsNewScene) {
this._isNewSceneLoaded = true;
yield "break";
}
Core.scene = this.sceneLoadAction();
this._isNewSceneLoaded = true;
while (!this._isNewSceneLoaded)
yield null;
}
/**
*
*
*/
public * onBeginTransition(): any {
yield null;
yield Core.startCoroutine(this.LoadNextScene());
this.transitionComplete();
}
/**
*
*/
public preRender() { }
/**
*
*/
public render() { }
/**
*
*/
protected transitionComplete() {
Core.Instance._sceneTransition = null;
if (this.onTransitionCompleted != null)
this.onTransitionCompleted();
}
}
}
@@ -1,120 +0,0 @@
///<reference path="./EntitySystem.ts"/>
module es {
/**
*
*
*/
export abstract class DelayedIteratingSystem extends EntitySystem {
private delay = 0;
private running = false;
private acc = 0;
constructor(matcher: Matcher) {
super(matcher);
}
protected process(entities: Entity[]) {
const processed = entities.length;
if (processed === 0) {
this.stop();
return;
}
this.delay = Number.MAX_VALUE;
for (let i = 0; i < processed; i++) {
const entity = entities[i];
this.processDelta(entity, this.acc);
const remaining = this.getRemainingDelay(entity);
if (remaining <= 0) {
this.processExpired(entity);
} else {
this.offerDelay(remaining);
}
}
this.acc = 0;
}
protected checkProcessing() {
if (this.running) {
this.acc += Time.deltaTime;
return this.acc >= this.delay;
}
return false;
}
/**
*
*
*
*
* @param offeredDelay
*/
public offerDelay(offeredDelay: number) {
if (!this.running) {
this.running = true;
this.delay = offeredDelay;
} else {
this.delay = Math.min(this.delay, offeredDelay);
}
}
/**
*
*/
public getInitialTimeDelay() {
return this.delay;
}
/**
*
*
*/
public getRemainingTimeUntilProcessing(): number {
if (this.running) {
return this.delay - this.acc;
}
return 0;
}
/**
*
*/
public isRunning(): boolean {
return this.running;
}
/**
*
*/
public stop() {
this.running = false;
this.acc = 0;
}
/**
* Delta
* @param entity
* @param accumulatedDelta delta
*/
protected abstract processDelta(entity: Entity, accumulatedDelta: number);
/**
*
* @param entity
*/
protected abstract processExpired(entity: Entity);
/**
*
* @param entity
* @returns
*/
protected abstract getRemainingDelay(entity: Entity): number;
}
}
@@ -1,78 +0,0 @@
///<reference path="./EntitySystem.ts" />
module es {
/**
* EntitySystem
* processEntity
*/
export abstract class EntityProcessingSystem extends EntitySystem {
/**
*
*/
public enabled: boolean = true;
/**
*
* @param matcher
*/
constructor(matcher: Matcher) {
super(matcher);
}
/**
*
* @param entity
*/
public abstract processEntity(entity: Entity): void;
/**
* update
* @param entity
*/
public lateProcessEntity(entity: Entity): void {
// do nothing
}
/**
*
* @param entities
*/
protected process(entities: Entity[]) {
// 如果实体数组为空,则直接返回
if (entities.length === 0) {
return;
}
// 遍历实体数组,逐个进行实体处理
for (let i = 0, len = entities.length; i < len; i++) {
const entity = entities[i];
this.processEntity(entity);
}
}
/**
* update
* @param entities
*/
protected lateProcess(entities: Entity[]) {
// 如果实体数组为空,则直接返回
if (entities.length === 0) {
return;
}
// 遍历实体数组,逐个进行实体处理
for (let i = 0, len = entities.length; i < len; i++) {
const entity = entities[i];
this.lateProcessEntity(entity);
}
}
/**
*
* true
* false
*/
protected checkProcessing(): boolean {
return this.enabled;
}
}
}
+298 -127
View File
@@ -1,147 +1,318 @@
///<reference path="../../Utils/Collections/HashMap.ts"/>
module es {
import { Entity } from '../Entity';
import { Core } from '../../Core';
import { Matcher } from '../Utils/Matcher';
import { PerformanceMonitor } from '../../Utils/PerformanceMonitor';
import type { Scene } from '../Scene';
/**
*
*
* ECS架构中的逻辑处理单元
*
*
* @example
* ```typescript
* class MovementSystem extends EntitySystem {
* constructor() {
* super(Matcher.empty().all(Transform, Velocity));
* }
*
* protected process(entities: Entity[]): void {
* for (const entity of entities) {
* const transform = entity.getComponent(Transform);
* const velocity = entity.getComponent(Velocity);
* transform.position.add(velocity.value);
* }
* }
* }
* ```
*/
export abstract class EntitySystem {
private _entities: Entity[] = [];
private _updateOrder: number = 0;
private _enabled: boolean = true;
private _performanceMonitor = PerformanceMonitor.instance;
private _systemName: string;
/**
*
*
*/
export abstract class EntitySystem {
private _entities: Entity[] = [];
private _updateOrder: number = 0;
private _startTime = 0;
private _endTime = 0;
private _useTime = 0;
public get entities(): readonly Entity[] {
return this._entities;
}
/** 获取系统在当前帧所消耗的时间 仅在debug模式下生效 */
public get useTime() {
return this._useTime;
}
/**
*
*/
public get updateOrder(): number {
return this._updateOrder;
}
/**
*
*/
public get updateOrder() {
return this._updateOrder;
}
public set updateOrder(value: number) {
this.setUpdateOrder(value);
}
public set updateOrder(value: number) {
this.setUpdateOrder(value);
}
/**
*
*/
public get enabled(): boolean {
return this._enabled;
}
constructor(matcher?: Matcher) {
this._matcher = matcher ? matcher : Matcher.empty();
this.initialize();
}
/**
*
*/
public set enabled(value: boolean) {
this._enabled = value;
}
private _scene: Scene;
/**
*
*/
public get systemName(): string {
return this._systemName;
}
/**
*
*/
public get scene() {
return this._scene;
}
constructor(matcher?: Matcher) {
this._matcher = matcher ? matcher : Matcher.empty();
this._systemName = this.constructor.name;
this.initialize();
}
public set scene(value: Scene) {
this._scene = value;
this._entities = [];
}
private _scene!: Scene;
private _matcher: Matcher;
/**
*
*/
public get scene(): Scene {
return this._scene;
}
public get matcher() {
return this._matcher;
}
public set scene(value: Scene) {
this._scene = value;
this._entities = [];
}
/**
*
* @param order
*/
public setUpdateOrder(order: number) {
this._updateOrder = order;
this.scene.entityProcessors.setDirty();
}
private _matcher: Matcher;
public initialize() {
/**
*
*/
public get matcher(): Matcher {
return this._matcher;
}
}
/**
*
* @param order
*/
public setUpdateOrder(order: number): void {
this._updateOrder = order;
this.scene.entityProcessors.setDirty();
}
public onChanged(entity: Entity) {
let contains = !!this._entities.find(e => e.id == entity.id);
let interest = this._matcher.isInterestedEntity(entity);
/**
*
*
*
*/
public initialize(): void {
// 子类可以重写此方法
}
if (interest && !contains)
this.add(entity);
else if (!interest && contains)
this.remove(entity);
}
/**
*
*
*
*
* @param entity
*/
public onChanged(entity: Entity): void {
const contains = this._entities.includes(entity);
const interest = this._matcher.isInterestedEntity(entity);
public add(entity: Entity) {
if (!this._entities.find(e => e.id == entity.id))
this._entities.push(entity);
this.onAdded(entity);
}
public onAdded(entity: Entity) {
}
public remove(entity: Entity) {
new es.List(this._entities).remove(entity);
this.onRemoved(entity);
}
public onRemoved(entity: Entity) {
}
public update() {
if (this.checkProcessing()) {
this.begin();
this.process(this._entities);
}
}
public lateUpdate() {
if (this.checkProcessing()) {
this.lateProcess(this._entities);
this.end();
}
}
/**
*
* 使
*/
protected begin() {
if (!Core.Instance.debug)
return;
this._startTime = Date.now();
}
protected process(entities: Entity[]) {
}
protected lateProcess(entities: Entity[]) {
}
/**
*
*/
protected end() {
if (!Core.Instance.debug)
return;
this._endTime = Date.now();
this._useTime = this._endTime - this._startTime;
}
/**
*
*
*
*
* @returns truefalse
*/
protected checkProcessing() {
return true;
if (interest && !contains) {
this.add(entity);
} else if (!interest && contains) {
this.remove(entity);
}
}
/**
*
*
* @param entity
*/
public add(entity: Entity): void {
if (!this._entities.includes(entity)) {
this._entities.push(entity);
this.onAdded(entity);
}
}
/**
*
*
*
*
* @param entity
*/
protected onAdded(entity: Entity): void {
// 子类可以重写此方法
}
/**
*
*
* @param entity
*/
public remove(entity: Entity): void {
const index = this._entities.indexOf(entity);
if (index !== -1) {
this._entities.splice(index, 1);
this.onRemoved(entity);
}
}
/**
*
*
*
*
* @param entity
*/
protected onRemoved(entity: Entity): void {
// 子类可以重写此方法
}
/**
*
*
*
*/
public update(): void {
if (!this._enabled || !this.checkProcessing()) {
return;
}
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
try {
this.begin();
this.process(this._entities);
} finally {
this._performanceMonitor.endMonitoring(this._systemName, startTime, this._entities.length);
}
}
/**
*
*
* update方法执行完毕后调用
*/
public lateUpdate(): void {
if (!this._enabled || !this.checkProcessing()) {
return;
}
const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`);
try {
this.lateProcess(this._entities);
this.end();
} finally {
this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, this._entities.length);
}
}
/**
*
*
*
*/
protected begin(): void {
// 子类可以重写此方法
}
/**
*
*
*
*
* @param entities
*/
protected process(entities: Entity[]): void {
// 子类必须实现此方法
}
/**
*
*
*
*
* @param entities
*/
protected lateProcess(entities: Entity[]): void {
// 子类可以重写此方法
}
/**
*
*
*
*/
protected end(): void {
// 子类可以重写此方法
}
/**
*
*
*
*
*
* @returns truefalse
*/
protected checkProcessing(): boolean {
return true;
}
/**
*
*
* @returns undefined
*/
public getPerformanceData() {
return this._performanceMonitor.getSystemData(this._systemName);
}
/**
*
*
* @returns undefined
*/
public getPerformanceStats() {
return this._performanceMonitor.getSystemStats(this._systemName);
}
/**
*
*/
public resetPerformanceData(): void {
this._performanceMonitor.resetSystem(this._systemName);
}
/**
*
*
* @returns
*/
public toString(): string {
const entityCount = this._entities.length;
const perfData = this.getPerformanceData();
const perfInfo = perfData ? ` (${perfData.executionTime.toFixed(2)}ms)` : '';
return `${this._systemName}[${entityCount} entities]${perfInfo}`;
}
}
@@ -1,26 +0,0 @@
///<reference path="./IntervalSystem.ts"/>
module es {
/**
*
*/
export abstract class IntervalIteratingSystem extends IntervalSystem {
constructor(matcher: Matcher, interval: number) {
super(matcher, interval);
}
/**
*
* @param entity
*/
public abstract processEntity(entity: Entity);
/**
*
* @param entities
*/
protected process(entities: Entity[]) {
entities.forEach(entity => this.processEntity(entity));
}
}
}
+49 -54
View File
@@ -1,63 +1,58 @@
module es {
import { EntitySystem } from './EntitySystem';
import { Matcher } from '../Utils/Matcher';
import { Time } from '../../Utils/Time';
/**
*
* EntitySystem类
* process方法
*/
export abstract class IntervalSystem extends EntitySystem {
/** 累积增量以跟踪间隔 */
private acc: number = 0;
/** 更新之间需要等待多长时间 */
private readonly interval: number;
/** 时间间隔的余数,用于计算下一次需要等待的时间 */
private intervalRemainder: number = 0;
/**
* EntitySystem
* process
*
* @param matcher
* @param interval
*/
export abstract class IntervalSystem extends EntitySystem {
/**
*
*/
private acc: number = 0;
constructor(matcher: Matcher, interval: number) {
super(matcher);
this.interval = interval;
}
/**
*
*/
private readonly interval: number;
/**
*
* true
* false
*/
protected override checkProcessing(): boolean {
// 更新累积增量
this.acc += Time.deltaTime;
/**
*
*/
private intervalRemainder: number = 0;
/**
*
* @param matcher
* @param interval
*/
constructor(matcher: Matcher, interval: number) {
super(matcher);
this.interval = interval;
// 如果累积增量超过时间间隔,则进行处理
if (this.acc >= this.interval) {
// 更新时间间隔余数
this.intervalRemainder = this.acc - this.interval;
// 重置累积增量
this.acc = 0;
// 返回true,表示需要进行处理
return true;
}
/**
*
* true
* false
*/
protected checkProcessing(): boolean {
// 更新累积增量
this.acc += Time.deltaTime;
// 返回false,表示不需要进行处理
return false;
}
// 如果累积增量超过时间间隔,则进行处理
if (this.acc >= this.interval) {
// 更新时间间隔余数
this.intervalRemainder = this.acc - this.interval;
// 重置累积增量
this.acc = 0;
// 返回 true,表示需要进行处理
return true;
}
// 返回 false,表示不需要进行处理
return false;
}
/**
* delta
* delta
*/
protected getIntervalDelta(): number {
return this.interval + this.intervalRemainder;
}
/**
* delta值
* delta值等于时间间隔加上时间间隔余数
*/
protected getIntervalDelta(): number {
return this.interval + this.intervalRemainder;
}
}
+20 -20
View File
@@ -1,23 +1,23 @@
module es {
/**
* EntitySystem
*
*/
export abstract class PassiveSystem extends EntitySystem {
/**
*
* @param entity
*/
public onChanged(entity: Entity) { }
import { EntitySystem } from './EntitySystem';
import { Entity } from '../Entity';
/**
*
* @param entities 使
*/
protected process(entities: Entity[]) {
// 调用 begin 和 end 方法,开始和结束计时
this.begin();
this.end();
}
/**
*
* EntitySystem类
*
*/
export abstract class PassiveSystem extends EntitySystem {
/**
*
* @param entity
*/
public override onChanged(entity: Entity): void { }
/**
*
* @param entities 使
*/
protected override process(entities: Entity[]): void {
// 被动系统不进行任何处理
}
}
+24 -25
View File
@@ -1,30 +1,29 @@
module es {
import { EntitySystem } from './EntitySystem';
import { Entity } from '../Entity';
/**
*
* EntitySystem类
* processSystem方法
*/
export abstract class ProcessingSystem extends EntitySystem {
/**
* EntitySystem
* processSystem
*
* @param entity
*/
export abstract class ProcessingSystem extends EntitySystem {
/**
*
* @param entity
*/
public onChanged(entity: Entity) { }
public override onChanged(entity: Entity): void { }
/**
* processSystem
* @param entities 使
*/
protected process(entities: Entity[]) {
// 调用 begin 和 end 方法,开始和结束计时
this.begin();
// 调用子类实现的 processSystem 方法进行实体处理
this.processSystem();
this.end();
}
/**
*
*/
public abstract processSystem(): void;
/**
* processSystem方法进行处理
* @param entities 使
*/
protected override process(entities: Entity[]): void {
// 调用子类实现的processSystem方法进行实体处理
this.processSystem();
}
/**
*
*/
public abstract processSystem(): void;
}
+5
View File
@@ -0,0 +1,5 @@
// ECS系统导出
export { EntitySystem } from './EntitySystem';
export { ProcessingSystem } from './ProcessingSystem';
export { PassiveSystem } from './PassiveSystem';
export { IntervalSystem } from './IntervalSystem';
+263 -478
View File
@@ -1,500 +1,285 @@
module es {
export enum ComponentTransform {
position,
scale,
rotation,
import { Vector2 } from '../Math/Vector2';
import { MathHelper } from '../Math/MathHelper';
/**
*
*
*
*
*
* @example
* ```typescript
* const transform = new Transform();
* transform.setPosition(100, 200);
* transform.setRotationDegrees(45);
* transform.setScale(2, 2);
*
* // 设置父子关系
* childTransform.setParent(transform);
* ```
*/
export class Transform {
/**
*
*
*
*/
public position: Vector2 = Vector2.zero;
/**
*
*
*
*/
public rotation: number = 0;
/**
*
*
*
*/
public scale: Vector2 = Vector2.one;
/**
*
*
* null则表示这是根变换
*/
public parent: Transform | null = null;
/**
*
*
*
*/
private _children: Transform[] = [];
/**
*
*
* @param position -
* @param rotation - 0
* @param scale -
*/
constructor(position?: Vector2, rotation: number = 0, scale?: Vector2) {
if (position) this.position = position.clone();
this.rotation = rotation;
if (scale) this.scale = scale.clone();
}
export enum DirtyType {
clean = 0,
positionDirty = 1,
scaleDirty = 2,
rotationDirty = 4,
/**
*
*
* @returns
*/
public get rotationDegrees(): number {
return MathHelper.toDegrees(this.rotation);
}
export class Transform {
/** 与此转换关联的实体 */
public readonly entity: Entity;
public hierarchyDirty: DirtyType;
public _localDirty: boolean;
public _localPositionDirty: boolean;
public _localScaleDirty: boolean;
public _localRotationDirty: boolean;
public _positionDirty: boolean;
public _worldToLocalDirty: boolean;
public _worldInverseDirty: boolean;
/**
*
*/
public _localTransform: Matrix2D = Matrix2D.identity;
/**
*
*/
public _worldTransform = Matrix2D.identity;
public _rotationMatrix: Matrix2D = Matrix2D.identity;
public _translationMatrix: Matrix2D = Matrix2D.identity;
public _scaleMatrix: Matrix2D = Matrix2D.identity;
public _children: Transform[] = [];
/**
*
*
* @param value -
*/
public set rotationDegrees(value: number) {
this.rotation = MathHelper.toRadians(value);
}
constructor(entity: Entity) {
this.entity = entity;
this.scale = this._localScale = Vector2.one;
/**
*
*
*
*
* @returns
*/
public get worldPosition(): Vector2 {
if (!this.parent) {
return this.position.clone();
}
/**
*
*/
public get childCount() {
return this._children.length;
// 计算世界位置
const parentWorld = this.parent.worldPosition;
const cos = Math.cos(this.parent.worldRotation);
const sin = Math.sin(this.parent.worldRotation);
const parentScale = this.parent.worldScale;
const scaledPos = Vector2.multiply(this.position, parentScale);
const rotatedX = scaledPos.x * cos - scaledPos.y * sin;
const rotatedY = scaledPos.x * sin + scaledPos.y * cos;
return new Vector2(parentWorld.x + rotatedX, parentWorld.y + rotatedY);
}
/**
*
*
*
*
* @returns
*/
public get worldRotation(): number {
if (!this.parent) {
return this.rotation;
}
return this.parent.worldRotation + this.rotation;
}
/**
*
*/
public get rotationDegrees(): number {
return MathHelper.toDegrees(this._rotation);
/**
*
*
*
*
* @returns
*/
public get worldScale(): Vector2 {
if (!this.parent) {
return this.scale.clone();
}
return Vector2.multiply(this.parent.worldScale, this.scale);
}
/**
*
* @param value
*/
public set rotationDegrees(value: number) {
this.setRotation(MathHelper.toRadians(value));
}
/**
*
*
* @returns
*/
public get childCount(): number {
return this._children.length;
}
/**
*
*/
public get localRotationDegrees(): number {
return MathHelper.toDegrees(this._localRotation);
}
/**
*
* @param value
*/
public set localRotationDegrees(value: number) {
this.localRotation = MathHelper.toRadians(value);
}
public get localToWorldTransform(): Matrix2D {
this.updateTransform();
return this._worldTransform;
}
public _parent: Transform;
/**
*
*/
public get parent() {
return this._parent;
}
/**
*
* @param value
*/
public set parent(value: Transform) {
this.setParent(value);
}
public _worldToLocalTransform = Matrix2D.identity;
public get worldToLocalTransform(): Matrix2D {
if (this._worldToLocalDirty) {
if (this.parent == null) {
this._worldToLocalTransform = Matrix2D.identity;
} else {
this.parent.updateTransform();
this._worldToLocalTransform = Matrix2D.invert(this.parent._worldTransform);
}
this._worldToLocalDirty = false;
}
return this._worldToLocalTransform;
}
public _worldInverseTransform = Matrix2D.identity;
public get worldInverseTransform(): Matrix2D {
this.updateTransform();
if (this._worldInverseDirty) {
this._worldInverseTransform = Matrix2D.invert(this._worldTransform);
this._worldInverseDirty = false;
}
return this._worldInverseTransform;
}
public _position: Vector2 = Vector2.zero;
/**
*
*/
public get position(): Vector2 {
this.updateTransform();
if (this._positionDirty) {
if (this.parent == null) {
this._position = this._localPosition;
} else {
this.parent.updateTransform();
Vector2Ext.transformR(this._localPosition, this.parent._worldTransform, this._position);
}
this._positionDirty = false;
}
return this._position;
}
/**
*
* @param value
*/
public set position(value: Vector2) {
this.setPosition(value.x, value.y);
}
public _scale: Vector2 = Vector2.one;
/**
*
*/
public get scale(): Vector2 {
this.updateTransform();
return this._scale;
}
/**
*
* @param value
*/
public set scale(value: Vector2) {
this.setScale(value);
}
public _rotation: number = 0;
/**
*
*/
public get rotation(): number {
this.updateTransform();
return this._rotation;
}
/**
*
* @param value
*/
public set rotation(value: number) {
this.setRotation(value);
}
public _localPosition: Vector2 = Vector2.zero;
/**
* transform.position相同
*/
public get localPosition(): Vector2 {
this.updateTransform();
return this._localPosition;
}
/**
* transform.position相同
* @param value
*/
public set localPosition(value: Vector2) {
this.setLocalPosition(value);
}
public _localScale: Vector2 = Vector2.one;
/**
* transform.scale相同
*/
public get localScale(): Vector2 {
this.updateTransform();
return this._localScale;
}
/**
* transform.scale相同
* @param value
*/
public set localScale(value: Vector2) {
this.setLocalScale(value);
}
public _localRotation: number = 0;
/**
* transform.rotation相同
*/
public get localRotation(): number {
this.updateTransform();
return this._localRotation;
}
/**
* transform.rotation相同
* @param value
*/
public set localRotation(value: number) {
this.setLocalRotation(value);
}
/**
*
* @param index
*/
public getChild(index: number): Transform {
/**
*
*
* @param index -
* @returns null
*/
public getChild(index: number): Transform | null {
if (index >= 0 && index < this._children.length) {
return this._children[index];
}
return null;
}
/**
*
* @param parent
*/
public setParent(parent: Transform): Transform {
if (this._parent == parent)
return this;
/**
*
*
*
*
* @param parent - null表示断开父子关系
*/
public setParent(parent: Transform | null): void {
if (this.parent === parent) return;
if (this._parent != null) {
const index = this._parent._children.findIndex(t => t == this);
if (index != -1)
this._parent._children.splice(index, 1);
}
if (parent != null) {
parent._children.push(this);
}
this._parent = parent;
this.setDirty(DirtyType.positionDirty);
return this;
}
/**
*
* @param x
* @param y
*/
public setPosition(x: number, y: number): Transform {
let position = new Vector2(x, y);
if (position.equals(this._position))
return this;
this._position = position;
if (this.parent != null) {
this.localPosition = Vector2.transform(this._position, this.worldToLocalTransform);
} else {
this.localPosition = position;
}
this._positionDirty = false;
return this;
}
/**
* transform.position相同
* @param localPosition
*/
public setLocalPosition(localPosition: Vector2): Transform {
if (localPosition.equals(this._localPosition))
return this;
this._localPosition = localPosition;
this._localDirty = this._positionDirty = this._localPositionDirty = this._localRotationDirty = this._localScaleDirty = true;
this.setDirty(DirtyType.positionDirty);
return this;
}
/**
*
* @param radians
*/
public setRotation(radians: number): Transform {
this._rotation = radians;
if (this.parent != null) {
this.localRotation = this.parent.rotation + radians;
} else {
this.localRotation = radians;
}
this.setDirty(DirtyType.rotationDirty);
return this;
}
/**
*
* @param degrees
*/
public setRotationDegrees(degrees: number): Transform {
return this.setRotation(MathHelper.toRadians(degrees));
}
/**
* 使
* @param pos
*/
public lookAt(pos: Vector2) {
const sign = this.position.x > pos.x ? -1 : 1;
const vectorToAlignTo = this.position.sub(pos).normalize();
this.rotation = sign * Math.acos(vectorToAlignTo.dot(Vector2.unitY));
}
/**
* transform.rotation相同
* @param radians
*/
public setLocalRotation(radians: number) {
this._localRotation = radians;
this._localDirty = this._positionDirty = this._localPositionDirty = this._localRotationDirty = this._localScaleDirty = true;
this.setDirty(DirtyType.rotationDirty);
return this;
}
/**
* transform.rotation相同
* @param degrees
*/
public setLocalRotationDegrees(degrees: number): Transform {
return this.setLocalRotation(MathHelper.toRadians(degrees));
}
/**
*
* @param scale
*/
public setScale(scale: Vector2): Transform {
this._scale = scale;
if (this.parent != null) {
this.localScale = Vector2.divide(scale, this.parent._scale);
} else {
this.localScale = scale;
}
this.setDirty(DirtyType.scaleDirty);
return this;
}
/**
* transform.scale相同
* @param scale
*/
public setLocalScale(scale: Vector2): Transform {
this._localScale = scale;
this._localDirty = this._positionDirty = this._localScaleDirty = true;
this.setDirty(DirtyType.scaleDirty);
return this;
}
/**
*
*/
public roundPosition() {
this.position = Vector2Ext.round(this._position);
}
public updateTransform() {
if (this.hierarchyDirty != DirtyType.clean) {
if (this.parent != null)
this.parent.updateTransform();
if (this._localDirty) {
if (this._localPositionDirty) {
Matrix2D.createTranslation(this._localPosition.x, this._localPosition.y, this._translationMatrix);
this._localPositionDirty = false;
}
if (this._localRotationDirty) {
Matrix2D.createRotation(this._localRotation, this._rotationMatrix);
this._localRotationDirty = false;
}
if (this._localScaleDirty) {
Matrix2D.createScale(this._localScale.x, this._localScale.y, this._scaleMatrix);
this._localScaleDirty = false;
}
Matrix2D.multiply(this._scaleMatrix, this._rotationMatrix, this._localTransform);
Matrix2D.multiply(this._localTransform, this._translationMatrix, this._localTransform);
if (this.parent == null) {
this._worldTransform = this._localTransform;
this._rotation = this._localRotation;
this._scale = this._localScale;
this._worldInverseDirty = true;
}
this._localDirty = false;
}
if (this.parent != null) {
Matrix2D.multiply(this._localTransform, this.parent._worldTransform, this._worldTransform);
this._rotation = this._localRotation + this.parent._rotation;
this._scale = this.parent._scale.multiply(this._localScale);;
this._worldInverseDirty = true;
}
this._worldToLocalDirty = true;
this._positionDirty = true;
this.hierarchyDirty = DirtyType.clean;
// 从旧父级移除
if (this.parent) {
const index = this.parent._children.indexOf(this);
if (index >= 0) {
this.parent._children.splice(index, 1);
}
}
public setDirty(dirtyFlagType: DirtyType) {
if ((this.hierarchyDirty & dirtyFlagType) == 0) {
this.hierarchyDirty |= dirtyFlagType;
switch (dirtyFlagType) {
case DirtyType.positionDirty:
this.entity.onTransformChanged(ComponentTransform.position);
break;
case DirtyType.rotationDirty:
this.entity.onTransformChanged(ComponentTransform.rotation);
break;
case DirtyType.scaleDirty:
this.entity.onTransformChanged(ComponentTransform.scale);
break;
}
// 告诉子项发生了变换
for (let i = 0; i < this._children.length; i++)
this._children[i].setDirty(dirtyFlagType);
}
}
/**
* transform属性进行拷贝
* @param transform
*/
public copyFrom(transform: Transform) {
this._position = transform.position.clone();
this._localPosition = transform._localPosition.clone();
this._rotation = transform._rotation;
this._localRotation = transform._localRotation;
this._scale = transform._scale;
this._localScale = transform._localScale;
this.setDirty(DirtyType.positionDirty);
this.setDirty(DirtyType.rotationDirty);
this.setDirty(DirtyType.scaleDirty);
}
public toString(): string {
return `[Transform: parent: ${this.parent}, position: ${this.position}, rotation: ${this.rotation},
scale: ${this.scale}, localPosition: ${this._localPosition}, localRotation: ${this._localRotation},
localScale: ${this._localScale}]`;
// 设置新父级
this.parent = parent;
if (parent) {
parent._children.push(this);
}
}
/**
*
*
* @param x - X坐标
* @param y - Y坐标
*/
public setPosition(x: number, y: number): void {
this.position.set(x, y);
}
/**
*
*
* @param radians -
*/
public setRotation(radians: number): void {
this.rotation = radians;
}
/**
*
*
* @param degrees -
*/
public setRotationDegrees(degrees: number): void {
this.rotation = MathHelper.toRadians(degrees);
}
/**
*
*
* @param scale -
*/
public setScale(scale: Vector2): void;
/**
*
*
* @param x - X轴缩放比例
* @param y - Y轴缩放比例
*/
public setScale(x: number, y: number): void;
public setScale(scaleOrX: Vector2 | number, y?: number): void {
if (typeof scaleOrX === 'number') {
this.scale.set(scaleOrX, y!);
} else {
this.scale.copyFrom(scaleOrX);
}
}
/**
*
*
* 使
*
* @param target -
*/
public lookAt(target: Vector2): void {
const direction = target.subtract(this.worldPosition);
this.rotation = Math.atan2(direction.y, direction.x);
}
/**
*
*
*
*
* @param offset -
*/
public translate(offset: Vector2): void {
this.position = this.position.add(offset);
}
/**
*
* @param radians
*/
public rotate(radians: number): void {
this.rotation += radians;
}
/**
*
* @param other
*/
public copyFrom(other: Transform): void {
this.position.copyFrom(other.position);
this.rotation = other.rotation;
this.scale.copyFrom(other.scale);
}
/**
*
*/
public clone(): Transform {
const transform = new Transform(this.position, this.rotation, this.scale);
return transform;
}
}
+157 -21
View File
@@ -1,28 +1,164 @@
module es {
/**
*
*/
export class Bits {
private _bit: { [index: number]: number } = {};
/**
*
*
*/
export class Bits {
private _words: number[] = [];
private static readonly WORD_SIZE = 32;
/**
*
* @param index
* @param value 0 1
*/
public set(index: number, value: number) {
this._bit[index] = value;
constructor() {
this._words = [];
}
/**
* 1
* @param index
*/
public set(index: number): void {
const wordIndex = Math.floor(index / Bits.WORD_SIZE);
const bitIndex = index % Bits.WORD_SIZE;
// 确保数组足够大
while (this._words.length <= wordIndex) {
this._words.push(0);
}
/**
*
* @param index
* @returns 0 1
*/
public get(index: number): number {
let v = this._bit[index];
return v == null ? 0 : v;
this._words[wordIndex] |= (1 << bitIndex);
}
/**
* 0
* @param index
*/
public clear(index: number): void {
const wordIndex = Math.floor(index / Bits.WORD_SIZE);
const bitIndex = index % Bits.WORD_SIZE;
if (wordIndex < this._words.length) {
this._words[wordIndex] &= ~(1 << bitIndex);
}
}
/**
*
* @param index
* @returns true或false
*/
public get(index: number): boolean {
const wordIndex = Math.floor(index / Bits.WORD_SIZE);
const bitIndex = index % Bits.WORD_SIZE;
if (wordIndex >= this._words.length) {
return false;
}
return (this._words[wordIndex] & (1 << bitIndex)) !== 0;
}
/**
*
* @param other Bits对象
* @returns true
*/
public containsAll(other: Bits): boolean {
const maxLength = Math.max(this._words.length, other._words.length);
for (let i = 0; i < maxLength; i++) {
const thisWord = i < this._words.length ? this._words[i] : 0;
const otherWord = i < other._words.length ? other._words[i] : 0;
if ((thisWord & otherWord) !== otherWord) {
return false;
}
}
return true;
}
/**
*
* @param other Bits对象
* @returns true
*/
public intersects(other: Bits): boolean {
const minLength = Math.min(this._words.length, other._words.length);
for (let i = 0; i < minLength; i++) {
if ((this._words[i] & other._words[i]) !== 0) {
return true;
}
}
return false;
}
/**
*
* @param other Bits对象
* @returns true
*/
public excludes(other: Bits): boolean {
return !this.intersects(other);
}
/**
*
*/
public clearAll(): void {
this._words.length = 0;
}
/**
*
* @returns true
*/
public isEmpty(): boolean {
for (const word of this._words) {
if (word !== 0) {
return false;
}
}
return true;
}
/**
*
* @returns
*/
public cardinality(): number {
let count = 0;
for (const word of this._words) {
count += this.popCount(word);
}
return count;
}
/**
* 32
* @param n 32
* @returns
*/
private popCount(n: number): number {
n = n - ((n >>> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
return (((n + (n >>> 4)) & 0xF0F0F0F) * 0x1010101) >>> 24;
}
/**
* Bits对象
* @param other Bits对象
*/
public copyFrom(other: Bits): void {
this._words = [...other._words];
}
/**
* Bits的副本
* @returns Bits对象
*/
public clone(): Bits {
const newBits = new Bits();
newBits.copyFrom(this);
return newBits;
}
}
-434
View File
@@ -1,434 +0,0 @@
///<reference path="../Components/IUpdatable.ts" />
module es {
export class ComponentList {
/**
* IUpdatable对象的更新顺序
*/
public static compareUpdatableOrder: IUpdatableComparer = new IUpdatableComparer();
public _entity: Entity;
/**
*
*/
public _components: Component[] = [];
/**
*
*/
public _updatableComponents: IUpdatable[] = [];
/**
*
*/
public _componentsToAdd: { [index: number]: Component } = {};
/**
*
*/
public _componentsToRemove: { [index: number]: Component } = {};
/**
*
*/
public _componentsToAddList: Component[] = [];
/**
*
*/
public _componentsToRemoveList: Component[] = [];
/**
*
*/
public _tempBufferList: Component[] = [];
/**
*
*/
public _isComponentListUnsorted: boolean;
/**
*
*/
private componentsByType = new Map<new (...args: any[]) => Component, es.Component[]>();
/**
*
*/
private componentsToAddByType = new Map<new (...args: any[]) => Component, es.Component[]>();
constructor(entity: Entity) {
this._entity = entity;
}
public get count() {
return this._components.length;
}
public get buffer() {
return this._components;
}
public markEntityListUnsorted() {
this._isComponentListUnsorted = true;
}
/**
*
* @param component
*/
public add(component: Component) {
// 将组件添加到_componentsToAdd和_componentsToAddList中,并添加到相应的组件类型字典中
this._componentsToAdd[component.id] = component;
this._componentsToAddList.push(component);
this.addComponentsToAddByType(component);
}
/**
*
* @param component
*/
public remove(component: Component) {
// 如果组件在_componentsToAdd中,则将其从_componentsToAddList中移除,并从相应的组件类型字典中移除组件
if (this._componentsToAdd[component.id]) {
const index = this._componentsToAddList.findIndex((c) => c.id === component.id);
if (index !== -1) {
this._componentsToAddList.splice(index, 1);
}
delete this._componentsToAdd[component.id];
this.removeComponentsToAddByType(component);
return;
}
// 如果组件不在_componentsToAdd中,则将其添加到_componentsToRemove和_componentsToRemoveList中
this._componentsToRemove[component.id] = component;
this._componentsToRemoveList.push(component);
}
/**
*
*/
public removeAllComponents() {
if (this._components.length > 0) {
for (let i = 0, s = this._components.length; i < s; ++i) {
this.handleRemove(this._components[i]);
}
}
this.componentsByType.clear();
this.componentsToAddByType.clear();
this._components.length = 0;
this._updatableComponents.length = 0;
this._componentsToAdd = {};
this._componentsToRemove = {};
this._componentsToAddList.length = 0;
this._componentsToRemoveList.length = 0;
}
/**
*
*/
public deregisterAllComponents() {
if (this._components.length > 0) {
for (const component of this._components) {
// 处理IUpdatable
if (isIUpdatable(component)) {
// 创建一个新的List实例,从_updatableComponents中移除组件,以避免并发修改异常
new es.List(this._updatableComponents).remove(component);
}
// 从位掩码中减去组件类型的索引,通知实体处理器一个组件已被移除
this.decreaseBits(component);
this._entity.scene.entityProcessors.onComponentRemoved(this._entity);
}
}
}
/**
*
*/
public registerAllComponents() {
if (this._components.length > 0) {
for (const component of this._components) {
if (isIUpdatable(component)) {
// 如果组件是可更新的,则将其添加到_updatableComponents中
this._updatableComponents.push(component);
}
// 将组件类型的索引添加到实体的位掩码中,通知实体处理器一个组件已被添加
this.addBits(component);
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
}
}
}
/**
*
* @param component
*/
private decreaseBits(component: Component) {
const bits = this._entity.componentBits;
// 获取组件类型的索引,将其对应位掩码减1
const typeIndex = ComponentTypeManager.getIndexFor(TypeUtils.getType(component));
bits.set(typeIndex, bits.get(typeIndex) - 1);
}
/**
*
* @param component
*/
private addBits(component: Component) {
const bits = this._entity.componentBits;
// 获取组件类型的索引,将其对应位掩码加1
const typeIndex = ComponentTypeManager.getIndexFor(TypeUtils.getType(component));
bits.set(typeIndex, bits.get(typeIndex) + 1);
}
/**
*
*
*/
public updateLists() {
// 处理要删除的组件
if (this._componentsToRemoveList.length > 0) {
for (const component of this._componentsToRemoveList) {
// 从实体中删除组件,从组件列表和相关数据结构中删除组件
this.handleRemove(component);
// 从_components数组中删除组件
const index = this._components.findIndex((c) => c.id === component.id);
if (index !== -1) {
this._components.splice(index, 1);
}
// 从组件类型字典中删除组件
this.removeComponentsByType(component);
}
// 清空_componentsToRemove和_componentsToRemoveList
this._componentsToRemove = {};
this._componentsToRemoveList.length = 0;
}
// 处理要添加的组件
if (this._componentsToAddList.length > 0) {
for (const component of this._componentsToAddList) {
// 如果组件可以更新,则添加到可更新组件列表中
if (isIUpdatable(component)) {
this._updatableComponents.push(component);
}
// 更新实体的组件位掩码,通知实体处理器一个组件已经添加
this.addBits(component);
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
// 将组件添加到相应类型的fastList中,将组件添加到_components数组中
this.addComponentsByType(component);
this._components.push(component);
// 将组件添加到_tempBufferList中,稍后调用onAddedToEntity和onEnabled
this._tempBufferList.push(component);
}
// 清空_componentsToAdd、_componentsToAddList和componentsToAddByType,设置_isComponentListUnsorted标志
this._componentsToAdd = {};
this._componentsToAddList.length = 0;
this.componentsToAddByType.clear();
this._isComponentListUnsorted = true;
}
// 调用新添加组件的onAddedToEntity和onEnabled方法
if (this._tempBufferList.length > 0) {
for (const component of this._tempBufferList) {
component.onAddedToEntity();
// 如果组件已启用,则调用onEnabled方法
if (component.enabled) {
component.onEnabled();
}
}
// 清空_tempBufferList
this._tempBufferList.length = 0;
}
}
public handleRemove(component: Component) {
// 如果组件可以更新,从可更新组件列表中删除该组件
if (isIUpdatable(component) && this._updatableComponents.length > 0) {
const index = this._updatableComponents.findIndex((c) => (<any>c as Component).id === component.id);
if (index !== -1) {
this._updatableComponents.splice(index, 1);
}
}
// 更新实体的组件位掩码
this.decreaseBits(component);
// 通知实体处理器一个组件已被删除
this._entity.scene.entityProcessors.onComponentRemoved(this._entity);
// 调用组件的onRemovedFromEntity方法,将其entity属性设置为null
component.onRemovedFromEntity();
component.entity = null;
}
private removeComponentsByType(component: Component) {
// 获取存储指定类型组件的fastList数组
const fastList = this.componentsByType.get(TypeUtils.getType(component));
// 在fastList中查找要删除的组件
const index = fastList.findIndex((c) => c.id === component.id);
if (index !== -1) {
// 找到组件后,使用splice方法将其从fastList中删除
fastList.splice(index, 1);
}
}
private addComponentsByType(component: Component) {
// 获取存储指定类型组件的fastList数组
let fastList = this.componentsByType.get(TypeUtils.getType(component));
// 如果fastList不存在,则创建一个空数组
if (!fastList) {
fastList = [];
}
// 在fastList中添加组件
fastList.push(component);
// 更新componentsByType字典,以便它包含fastList数组
this.componentsByType.set(TypeUtils.getType(component), fastList);
}
/**
*
* @param component
*/
private removeComponentsToAddByType(component: Component) {
// 获取待添加组件列表中指定类型的组件列表
let fastList = this.componentsToAddByType.get(TypeUtils.getType(component));
// 在该列表中查找指定组件
let fastIndex = fastList.findIndex(c => c.id == component.id);
// 如果找到了指定组件,则从列表中移除它
if (fastIndex != -1) {
fastList.splice(fastIndex, 1);
}
}
/**
*
* @param component
*/
private addComponentsToAddByType(component: Component) {
// 获取待添加组件列表中指定类型的组件列表
let fastList = this.componentsToAddByType.get(TypeUtils.getType(component));
// 如果指定类型的组件列表不存在,则创建一个新的列表
if (!fastList) fastList = [];
// 向指定类型的组件列表中添加组件
fastList.push(component);
// 更新待添加组件列表中指定类型的组件列表
this.componentsToAddByType.set(TypeUtils.getType(component), fastList);
}
/**
*
* @param type
* @param onlyReturnInitializedComponents
* @returns null
*/
public getComponent<T extends Component>(
type: new (...args: any[]) => T,
onlyReturnInitializedComponents: boolean
): T {
// 获取指定类型的组件列表
let fastList = this.componentsByType.get(type);
// 如果指定类型的组件列表存在并且不为空,则返回第一个组件实例
if (fastList && fastList.length > 0) return fastList[0] as T;
// 如果不仅返回已初始化的组件,则检查待添加组件列表中是否存在指定类型的组件
if (!onlyReturnInitializedComponents) {
let fastToAddList = this.componentsToAddByType.get(type);
if (fastToAddList && fastToAddList.length > 0)
return fastToAddList[0] as T;
}
// 如果指定类型的组件列表为空且待添加组件列表中也不存在该类型的组件,则返回 null
return null;
}
/**
*
* @param typeName
* @param components
* @returns
*/
public getComponents(typeName: any, components?: any[]) {
// 如果没有传入组件实例数组,则创建一个新数组
if (!components) components = [];
// 获取指定类型的组件列表,并将其添加到组件实例数组中
let fastList = this.componentsByType.get(typeName);
if (fastList) components = components.concat(fastList);
// 获取待添加组件列表中的指定类型的组件列表,并将其添加到组件实例数组中
let fastToAddList = this.componentsToAddByType.get(typeName);
if (fastToAddList) components = components.concat(fastToAddList);
// 返回存储了指定类型的所有组件实例的数组
return components;
}
public update() {
this.updateLists();
if (this._updatableComponents.length > 0) {
for (let i = 0, s = this._updatableComponents.length; i < s; ++i) {
let updateComponent = this._updatableComponents[i];
if (updateComponent.enabled)
updateComponent.update();
}
}
}
public onEntityTransformChanged(comp: ComponentTransform) {
if (this._components.length > 0) {
for (let i = 0, s = this._components.length; i < s; ++i) {
let component = this._components[i];
if (component.enabled)
component.onEntityTransformChanged(comp);
}
}
if (this._componentsToAddList.length > 0) {
for (let i = 0, s = this._componentsToAddList.length; i < s; ++i) {
let component = this._componentsToAddList[i];
if (component.enabled)
component.onEntityTransformChanged(comp);
}
}
}
public onEntityEnabled() {
if (this._components.length > 0) {
for (let i = 0, s = this._components.length; i < s; i++)
this._components[i].onEnabled();
}
}
public onEntityDisabled() {
if (this._components.length > 0) {
for (let i = 0, s = this._components.length; i < s; i++)
this._components[i].onDisabled();
}
}
}
}
@@ -1,51 +0,0 @@
module es {
/**
*
* 便
*/
export class ComponentTypeFactory {
/** 组件类型与其唯一索引的映射表 */
private componentTypes: Record<string, ComponentType> = {};
/** 组件类型列表,按索引访问组件类型 */
public readonly types: Bag<ComponentType> = new Bag<ComponentType>();
/** 当前组件类型的计数器 */
private componentTypeCount = 0;
/**
*
*
* @param c
* @returns
*/
public getIndexFor(c: new (...args: any[]) => any): number {
return this.getTypeFor(c).getIndex();
}
/**
* ComponentType对象
* ComponentType对象
* @param c
* @returns ComponentType对象
*/
public getTypeFor(c: new (...args: any[]) => any): ComponentType {
// 如果给定的组件类型是一个已有的索引,则直接返回对应的ComponentType对象
if (typeof c === "number") {
return this.types.get(c);
}
// 获取给定组件类型对应的类名
const className = getClassName(c);
// 如果类型映射表中不存在该组件类型,则创建一个新的ComponentType对象
if (!this.componentTypes[className]) {
const index = this.componentTypeCount++;
const type = new ComponentType(c, index);
this.componentTypes[className] = type;
this.types.set(index, type);
}
// 返回对应的ComponentType对象
return this.componentTypes[className];
}
}
}
+90 -29
View File
@@ -1,38 +1,99 @@
module es {
import { Component } from '../Component';
import { Bits } from './Bits';
/**
*
* ID分配
*/
export class ComponentTypeManager {
private static _instance: ComponentTypeManager;
private _componentTypes = new Map<Function, number>();
private _typeNames = new Map<number, string>();
private _nextTypeId = 0;
/**
*
*
*
*/
export class ComponentTypeManager {
/** 存储组件类型和它们对应的位掩码的Map */
private static _componentTypesMask: Map<any, number> = new Map<any, number>();
public static get instance(): ComponentTypeManager {
if (!ComponentTypeManager._instance) {
ComponentTypeManager._instance = new ComponentTypeManager();
}
return ComponentTypeManager._instance;
}
/**
*
* @param type
*/
public static add(type) {
if (!this._componentTypesMask.has(type)) {
this._componentTypesMask.set(type, this._componentTypesMask.size);
}
private constructor() {}
/**
* ID
* @param componentType
* @returns ID
*/
public getTypeId<T extends Component>(componentType: new (...args: any[]) => T): number {
let typeId = this._componentTypes.get(componentType);
if (typeId === undefined) {
typeId = this._nextTypeId++;
this._componentTypes.set(componentType, typeId);
this._typeNames.set(typeId, componentType.name);
}
/**
*
*
* @param type
* @returns
*/
public static getIndexFor(type) {
let v = -1;
if (!this._componentTypesMask.has(type)) {
this.add(type);
v = this._componentTypesMask.get(type);
} else {
v = this._componentTypesMask.get(type);
}
return typeId;
}
return v;
/**
*
* @param typeId ID
* @returns
*/
public getTypeName(typeId: number): string {
return this._typeNames.get(typeId) || 'Unknown';
}
/**
* Bits对象
* @param componentTypes
* @returns Bits对象
*/
public createBits(...componentTypes: (new (...args: any[]) => Component)[]): Bits {
const bits = new Bits();
for (const componentType of componentTypes) {
const typeId = this.getTypeId(componentType);
bits.set(typeId);
}
return bits;
}
/**
*
* @param components
* @returns Bits对象
*/
public getEntityBits(components: Component[]): Bits {
const bits = new Bits();
for (const component of components) {
const typeId = this.getTypeId(component.constructor as new (...args: any[]) => Component);
bits.set(typeId);
}
return bits;
}
/**
*
*/
public reset(): void {
this._componentTypes.clear();
this._typeNames.clear();
this._nextTypeId = 0;
}
/**
*
*/
public get registeredTypeCount(): number {
return this._componentTypes.size;
}
}
+242 -410
View File
@@ -1,456 +1,288 @@
module es {
export class EntityList {
/**
*
*/
public scene: Scene;
import { Entity } from '../Entity';
import { Component } from '../Component';
/**
*
*/
public _entities: Entity[] = [];
/**
*
*
*/
export class EntityList {
public buffer: Entity[] = [];
private _scene: any; // 临时使用any,避免循环依赖
/**
*
*/
public _entitiesToAdded: { [index: number]: Entity } = {};
// 索引映射,提升查找性能
private _idToEntity = new Map<number, Entity>();
private _nameToEntities = new Map<string, Entity[]>();
/**
*
*/
public _entitiesToRemove: { [index: number]: Entity } = {};
// 延迟操作队列
private _entitiesToAdd: Entity[] = [];
private _entitiesToRemove: Entity[] = [];
private _isUpdating = false;
/**
*
*/
public _entitiesToAddedList: Entity[] = [];
public get count(): number {
return this.buffer.length;
}
/**
*
*/
public _entitiesToRemoveList: Entity[] = [];
constructor(scene: any) {
this._scene = scene;
}
/**
*
*/
public _isEntityListUnsorted: boolean;
/**
*
* @param entity
*/
public add(entity: Entity): void {
if (this._isUpdating) {
// 如果正在更新中,延迟添加
this._entitiesToAdd.push(entity);
} else {
this.addImmediate(entity);
}
}
/**
*
*/
public _entityDict: Map<number, Set<Entity>> = new Map<number, Set<Entity>>();
/**
*
*/
public _unsortedTags: Set<number> = new Set<number>();
constructor(scene: Scene) {
this.scene = scene;
/**
*
* @param entity
*/
private addImmediate(entity: Entity): void {
// 检查是否已存在
if (this._idToEntity.has(entity.id)) {
return;
}
public get count() {
return this._entities.length;
this.buffer.push(entity);
this._idToEntity.set(entity.id, entity);
// 更新名称索引
this.updateNameIndex(entity, true);
}
/**
*
* @param entity
*/
public remove(entity: Entity): void {
if (this._isUpdating) {
// 如果正在更新中,延迟移除
this._entitiesToRemove.push(entity);
} else {
this.removeImmediate(entity);
}
}
/**
*
* @param entity
*/
private removeImmediate(entity: Entity): void {
const index = this.buffer.indexOf(entity);
if (index !== -1) {
this.buffer.splice(index, 1);
this._idToEntity.delete(entity.id);
// 更新名称索引
this.updateNameIndex(entity, false);
}
}
/**
*
*/
public removeAllEntities(): void {
for (let i = this.buffer.length - 1; i >= 0; i--) {
this.buffer[i].destroy();
}
public get buffer() {
return this._entities;
}
this.buffer.length = 0;
this._idToEntity.clear();
this._nameToEntities.clear();
this._entitiesToAdd.length = 0;
this._entitiesToRemove.length = 0;
}
public markEntityListUnsorted() {
this._isEntityListUnsorted = true;
}
public markTagUnsorted(tag: number) {
this._unsortedTags.add(tag);
}
/**
*
* @param entity
*/
public add(entity: Entity) {
this._entitiesToAdded[entity.id] = entity;
this._entitiesToAddedList.push(entity);
}
/**
*
* @param entity
*/
public remove(entity: Entity) {
// 如果实体在添加列表中,则将其从添加列表中移除
if (this._entitiesToAdded[entity.id]) {
const index = this._entitiesToAddedList.findIndex((e) => e.id === entity.id);
if (index !== -1) {
this._entitiesToAddedList.splice(index, 1);
}
delete this._entitiesToAdded[entity.id];
return;
/**
*
*/
public updateLists(): void {
// 处理延迟添加的实体
if (this._entitiesToAdd.length > 0) {
for (const entity of this._entitiesToAdd) {
this.addImmediate(entity);
}
this._entitiesToAdd.length = 0;
}
// 如果实体不在添加列表中,则将其添加到移除列表中并将其添加到移除字典中
this._entitiesToRemoveList.push(entity);
if (!this._entitiesToRemove[entity.id]) {
this._entitiesToRemove[entity.id] = entity;
// 处理延迟移除的实体
if (this._entitiesToRemove.length > 0) {
for (const entity of this._entitiesToRemove) {
this.removeImmediate(entity);
}
this._entitiesToRemove.length = 0;
}
}
/**
*
*/
public removeAllEntities() {
// 清除字典和列表,以及是否已排序的标志
this._unsortedTags.clear();
this._entitiesToAdded = {};
this._entitiesToAddedList.length = 0;
this._isEntityListUnsorted = false;
/**
*
*/
public update(): void {
this._isUpdating = true;
// 调用updateLists方法,以处理要移除的实体
this.updateLists();
// 标记并移除所有实体
for (const entity of this._entities) {
entity._isDestroyed = true;
entity.onRemovedFromScene();
entity.scene = null;
}
// 清空实体列表和实体字典
this._entities.length = 0;
this._entityDict.clear();
}
/**
*
* @param entity
* @returns truefalse
*/
public contains(entity: Entity): boolean {
// 检查实体是否存在于_entitiesToAdded字典中
return !!this._entitiesToAdded[entity.id];
}
/**
*
*
* @param tag
* @returns
*/
public getTagList(tag: number): Set<Entity> {
// 尝试从_entityDict中获取具有指定标签的实体列表
let list = this._entityDict.get(tag);
// 如果列表不存在,则创建一个新的Set实例,并添加到_entityDict中
if (!list) {
list = new Set<Entity>();
this._entityDict.set(tag, list);
}
return list;
}
/**
*
* @param entity
*/
public addToTagList(entity: Entity) {
// 获取标签列表
const list = this.getTagList(entity.tag);
// 将实体添加到标签列表中
list.add(entity);
// 添加未排序标志
this._unsortedTags.add(entity.tag);
}
/**
*
* @param entity
*/
public removeFromTagList(entity: Entity) {
// 获取实体的标签列表
const list = this._entityDict.get(entity.tag);
// 如果标签列表存在,则从中移除实体
if (list) {
list.delete(entity);
}
}
/**
* Update方法
* UpdateInterval为1或Time.frameCount模除UpdateInterval为0Update
*/
public update() {
for (let i = 0; i < this._entities.length; i++) {
const entity = this._entities[i];
if (entity.enabled && (entity.updateInterval === 1 || Time.frameCount % entity.updateInterval === 0)) {
try {
for (let i = 0; i < this.buffer.length; i++) {
const entity = this.buffer[i];
if (entity.enabled && !entity.isDestroyed) {
entity.update();
}
}
} finally {
this._isUpdating = false;
}
// 处理延迟操作
this.updateLists();
}
/**
*
*/
public updateLists() {
// 处理要移除的实体
if (this._entitiesToRemoveList.length > 0) {
for (const entity of this._entitiesToRemoveList) {
// 从标签列表中删除实体
this.removeFromTagList(entity);
/**
* 使O(1)
* @param name
* @returns null
*/
public findEntity(name: string): Entity | null {
const entities = this._nameToEntities.get(name);
return entities && entities.length > 0 ? entities[0] : null;
}
// 从场景实体列表中删除实体
const index = this._entities.findIndex((e) => e.id === entity.id);
if (index !== -1) {
this._entities.splice(index, 1);
}
/**
*
* @param name
* @returns
*/
public findEntitiesByName(name: string): Entity[] {
return this._nameToEntities.get(name) || [];
}
// 调用实体的onRemovedFromScene方法,并将其scene属性设置为null
entity.onRemovedFromScene();
entity.scene = null;
/**
* ID查找实体使O(1)
* @param id ID
* @returns null
*/
public findEntityById(id: number): Entity | null {
return this._idToEntity.get(id) || null;
}
// 通知场景实体处理器,一个实体已被移除
this.scene.entityProcessors.onEntityRemoved(entity);
}
/**
*
* @param tag
* @returns
*/
public findEntitiesByTag(tag: number): Entity[] {
const result: Entity[] = [];
// 清空要移除的实体列表和字典
this._entitiesToRemove = {};
this._entitiesToRemoveList.length = 0;
}
// 处理要添加的实体
if (this._entitiesToAddedList.length > 0) {
// 添加实体到场景实体列表和标签列表中
for (const entity of this._entitiesToAddedList) {
this._entities.push(entity);
entity.scene = this.scene;
this.addToTagList(entity);
}
// 通知场景实体处理器,有新的实体已添加
for (const entity of this._entitiesToAddedList) {
this.scene.entityProcessors.onEntityAdded(entity);
}
// 调用实体的onAddedToScene方法,以允许它们执行任何场景相关的操作
for (const entity of this._entitiesToAddedList) {
entity.onAddedToScene();
}
// 清空要添加的实体列表和字典
this._entitiesToAdded = {};
this._entitiesToAddedList.length = 0;
for (const entity of this.buffer) {
if (entity.tag === tag) {
result.push(entity);
}
}
return result;
}
/**
* name的实体null
* @param name
*/
public findEntity(name: string) {
if (this._entities.length > 0) {
for (let i = 0, s = this._entities.length; i < s; ++i) {
let entity = this._entities[i];
if (entity.name == name)
return entity;
}
/**
*
* @param componentType
* @returns
*/
public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] {
const result: Entity[] = [];
for (const entity of this.buffer) {
if (entity.hasComponent(componentType)) {
result.push(entity);
}
if (this._entitiesToAddedList.length > 0) {
for (let i = 0, s = this._entitiesToAddedList.length; i < s; ++i) {
let entity = this._entitiesToAddedList[i];
if (entity.name == name)
return entity;
}
}
return null;
}
/**
* ID在场景中查找对应实体
* @param id ID
* @returns null
*/
public findEntityById(id: number) {
// 遍历场景中所有实体
if (this._entities.length > 0) {
for (let i = 0, s = this._entities.length; i < s; ++i) {
let entity = this._entities[i];
// 如果实体的ID匹配,返回该实体
if (entity.id == id)
return entity;
}
}
return result;
}
// 在未添加的实体列表中查找
return this._entitiesToAdded[id];
/**
*
* @param action
*/
public forEach(action: (entity: Entity) => void): void {
for (const entity of this.buffer) {
action(entity);
}
}
/**
*
* @param predicate
* @param action
*/
public forEachWhere(predicate: (entity: Entity) => boolean, action: (entity: Entity) => void): void {
for (const entity of this.buffer) {
if (predicate(entity)) {
action(entity);
}
}
}
/**
*
* @param entity
* @param isAdd
*/
private updateNameIndex(entity: Entity, isAdd: boolean): void {
if (!entity.name) {
return;
}
/**
*
* @param tag
* @returns
*/
public entitiesWithTag(tag: number): Entity[] {
// 从字典中获取对应标签的实体列表
const list = this.getTagList(tag);
if (isAdd) {
let entities = this._nameToEntities.get(entity.name);
if (!entities) {
entities = [];
this._nameToEntities.set(entity.name, entities);
}
entities.push(entity);
} else {
const entities = this._nameToEntities.get(entity.name);
if (entities) {
const index = entities.indexOf(entity);
if (index !== -1) {
entities.splice(index, 1);
// 从对象池中获取 Entity 类型的数组
const returnList = ListPool.obtain<Entity>(Entity);
if (list.size > 0) {
// 将实体列表中的实体添加到返回列表中
for (const entity of list) {
returnList.push(entity);
// 如果数组为空,删除映射
if (entities.length === 0) {
this._nameToEntities.delete(entity.name);
}
}
}
}
}
// 返回已填充好实体的返回列表
return returnList;
}
/**
* tag的实体
* @param tag
* @returns
*/
public entityWithTag(tag: number) {
let list = this.getTagList(tag);
if (list.size > 0) {
for (let entity of list) {
return entity;
}
}
return null;
}
/**
*
* @param type
* @returns null
*/
public findComponentOfType<T extends Component>(type: new (...args: any[]) => T): T | null {
// 遍历场景中的所有实体,查找具有给定类型的组件
for (const entity of this._entities) {
if (entity.enabled) {
const comp = entity.getComponent(type);
if (comp) {
return comp;
}
}
}
// 遍历待添加的实体列表中的所有实体,查找具有给定类型的组件
for (const entity of this._entitiesToAddedList) {
if (entity.enabled) {
const comp = entity.getComponent(type);
if (comp) {
return comp;
}
}
}
// 如果找不到具有给定类型的组件,则返回null
return null;
}
/**
*
* @param type
* @returns
*/
public findComponentsOfType<T extends Component>(type: new (...args: any[]) => T): T[] {
// 从池中获取一个可重用的组件列表
const comps = ListPool.obtain<T>(type);
// 遍历场景中的所有实体,查找具有给定类型的组件并添加到组件列表中
for (const entity of this._entities) {
if (entity.enabled) {
entity.getComponents(type, comps);
}
}
// 遍历待添加的实体列表中的所有实体,查找具有给定类型的组件并添加到组件列表中
for (const entity of this._entitiesToAddedList) {
if (entity.enabled) {
entity.getComponents(type, comps);
}
}
// 返回具有给定类型的所有组件的列表
return comps;
}
/**
*
* @param types
* @returns
*/
public findEntitiesOfComponent(...types: any[]): Entity[] {
const entities = [];
// 遍历所有已存在的实体
for (const entity of this._entities) {
// 只有启用的实体才会被考虑
if (entity.enabled) {
// 如果types数组为空,直接将实体添加到结果数组中
if (types.length === 0) {
entities.push(entity);
continue;
}
// 对于每个指定的组件类型,检查实体是否具有该组件
let meet = true;
for (const type of types) {
const hasComp = entity.hasComponent(type);
if (!hasComp) {
meet = false;
break;
}
}
// 如果实体满足要求,将其添加到结果数组中
if (meet) {
entities.push(entity);
}
}
}
// 遍历所有等待添加的实体,和上面的操作类似
for (const entity of this._entitiesToAddedList) {
if (entity.enabled) {
if (types.length === 0) {
entities.push(entity);
continue;
}
let meet = true;
for (const type of types) {
const hasComp = entity.hasComponent(type);
if (!hasComp) {
meet = false;
break;
}
}
if (meet) {
entities.push(entity);
}
}
}
return entities;
/**
*
* @returns
*/
public getStats(): {
totalEntities: number;
activeEntities: number;
pendingAdd: number;
pendingRemove: number;
nameIndexSize: number;
} {
let activeCount = 0;
for (const entity of this.buffer) {
if (entity.enabled && !entity.isDestroyed) {
activeCount++;
}
}
return {
totalEntities: this.buffer.length,
activeEntities: activeCount,
pendingAdd: this._entitiesToAdd.length,
pendingRemove: this._entitiesToRemove.length,
nameIndexSize: this._nameToEntities.size
};
}
}
+101 -175
View File
@@ -1,182 +1,108 @@
module es {
export class EntityProcessorList {
private _processors: EntitySystem[] = []; // 处理器列表
private _orderDirty: boolean = false; // 处理器排序标志
import { EntitySystem } from '../Systems/EntitySystem';
/** 获取处理器列表 */
public get processors() {
return this._processors;
}
/**
*
*
*/
export class EntityProcessorList {
private _processors: EntitySystem[] = [];
private _isDirty = false;
/** 获取处理器数量 */
public get count() {
return this._processors.length;
}
/**
*
*/
public setDirty(): void {
this._isDirty = true;
}
/**
*
* @param processor
*/
public add(processor: EntitySystem): void {
this._processors.push(processor);
}
/**
*
* @param processor
*/
public add(processor: EntitySystem): void {
this._processors.push(processor);
this.setDirty();
}
/**
*
* @param processor
*/
public remove(processor: EntitySystem): void {
// 使用 es.List 类的 remove() 方法从处理器列表中移除指定处理器
new es.List(this._processors).remove(processor);
}
/**
*
* @param entity
*/
public onComponentAdded(entity: Entity): void {
this.notifyEntityChanged(entity);
}
/**
*
* @param entity
*/
public onComponentRemoved(entity: Entity): void {
this.notifyEntityChanged(entity);
}
/**
*
* @param entity
*/
public onEntityAdded(entity: Entity): void {
this.notifyEntityChanged(entity);
}
/**
*
* @param entity
*/
public onEntityRemoved(entity: Entity): void {
this.removeFromProcessors(entity);
}
/** 在处理器列表上开始循环 */
public begin(): void {
}
/** 更新处理器列表 */
public update(): void {
// 如果处理器列表为空,则直接返回
if (this._processors.length === 0) {
return;
}
// 如果需要重新排序处理器列表
if (this._orderDirty) {
// 对处理器列表进行排序
this._processors.sort((a, b) => a.updateOrder - b.updateOrder);
// 重新设置处理器的更新顺序
for (let i = 0, s = this._processors.length; i < s; ++i) {
const processor = this._processors[i];
processor.setUpdateOrder(i);
}
// 将标志设置为“未脏”
this.clearDirty();
}
// 调用每个处理器的 update() 方法
for (let i = 0, s = this._processors.length; i < s; ++i) {
const processor = this._processors[i];
processor.update();
}
}
/** 在处理器列表上完成循环 */
public end(): void {
}
/** 设置处理器排序标志 */
public setDirty(): void {
this._orderDirty = true;
}
/** 清除处理器排序标志 */
public clearDirty(): void {
this._orderDirty = false;
}
/**
*
* @param type
* @returns
*/
public getProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T {
// 如果处理器列表为空,则返回null
if (this._processors.length === 0) {
return null;
}
// 遍历处理器列表,查找指定类型的处理器
for (let i = 0, s = this._processors.length; i < s; ++i) {
const processor = this._processors[i];
// 如果当前处理器是指定类型的实例,则返回当前处理器
if (processor instanceof type) {
return processor as T;
}
}
// 如果没有找到指定类型的处理器,则返回null
return null;
}
/**
*
* @param entity
*/
protected notifyEntityChanged(entity: Entity): void {
if (this._processors.length === 0) {
return;
}
// 遍历处理器列表,调用每个处理器的 onChanged() 方法
for (let i = 0, s = this._processors.length; i < s; ++i) {
const processor = this._processors[i];
processor.onChanged(entity);
}
}
/**
*
* @param entity
*/
protected removeFromProcessors(entity: Entity): void {
if (this._processors.length === 0) {
return;
}
// 遍历处理器列表,调用每个处理器的 remove() 方法
for (let i = 0, s = this._processors.length; i < s; ++i) {
const processor = this._processors[i];
processor.remove(entity);
}
}
/** 在处理器列表上进行后期更新 */
public lateUpdate(): void {
if (this._processors.length === 0) {
return;
}
// 调用每个处理器的 lateUpdate() 方法
for (let i = 0, s = this._processors.length; i < s; ++i) {
const processor = this._processors[i];
processor.lateUpdate();
}
/**
*
* @param processor
*/
public remove(processor: EntitySystem): void {
const index = this._processors.indexOf(processor);
if (index !== -1) {
this._processors.splice(index, 1);
}
}
/**
*
* @param type
*/
public getProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
for (const processor of this._processors) {
if (processor instanceof type) {
return processor as T;
}
}
return null;
}
/**
*
*/
public begin(): void {
this.sortProcessors();
for (const processor of this._processors) {
processor.initialize();
}
}
/**
*
*/
public end(): void {
// 清理处理器
}
/**
*
*/
public update(): void {
this.sortProcessors();
for (const processor of this._processors) {
processor.update();
}
}
/**
*
*/
public lateUpdate(): void {
for (const processor of this._processors) {
processor.lateUpdate();
}
}
/**
*
*/
private sortProcessors(): void {
if (this._isDirty) {
this._processors.sort((a, b) => a.updateOrder - b.updateOrder);
this._isDirty = false;
}
}
/** 获取处理器列表 */
public get processors() {
return this._processors;
}
/** 获取处理器数量 */
public get count() {
return this._processors.length;
}
}
-97
View File
@@ -1,97 +0,0 @@
module es {
export class HashHelpers {
// 哈希冲突阈值,超过此值将使用另一种哈希算法
public static readonly hashCollisionThreshold: number = 100;
// 哈希值用于计算哈希表索引的质数
public static readonly hashPrime: number = 101;
// 一组预定义的质数,用于计算哈希表容量
public static readonly primes = [
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
];
// 可分配的最大数组长度,用于避免 OutOfMemoryException
public static readonly maxPrimeArrayLength = 0x7FEFFFFD;
/**
*
* @param candidate
* @returns
*/
public static isPrime(candidate: number): boolean {
if ((candidate & 1) !== 0) { // 位运算判断奇偶性
let limit = Math.sqrt(candidate);
for (let divisor = 3; divisor <= limit; divisor += 2) { // 奇数因子判断
if ((candidate % divisor) === 0) {
return false;
}
}
return true;
}
return (candidate === 2); // 2是质数
}
/**
*
* @param min
* @returns
*/
public static getPrime(min: number): number {
if (min < 0) {
throw new Error("参数错误 min 不能小于0");
}
for (let i = 0; i < this.primes.length; i++) {
let prime = this.primes[i];
if (prime >= min) {
return prime;
}
}
// 在预定义的质数列表之外,需要计算最小的质数
for (let i = (min | 1); i < Number.MAX_VALUE; i += 2) { // 从 min 向上计算奇数
if (this.isPrime(i) && ((i - 1) % this.hashPrime !== 0)) { // i是质数且不是hashPrime的倍数
return i;
}
}
return min;
}
/**
*
* @param oldSize
* @returns
*/
public static expandPrime(oldSize: number): number {
let newSize = 2 * oldSize;
// 在遇到容量溢出之前,允许哈希特表增长到最大可能的大小
// 请注意,即使当_items.Length溢出时,这项检查也会起作用
if (newSize > this.maxPrimeArrayLength && this.maxPrimeArrayLength > oldSize) {
return this.maxPrimeArrayLength;
}
return this.getPrime(newSize);
}
/**
*
* @param str
* @returns
*/
public static getHashCode(str: string): number {
let hash = 0;
if (str.length === 0) {
return hash;
}
for (let i = 0; i < str.length; i++) {
let char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char; // 采用 FNV-1a 哈希算法
hash = hash & hash; // 将hash值转换为32位整数
}
return hash;
}
}
}
+21 -17
View File
@@ -1,22 +1,26 @@
module es {
export class IdentifierPool {
private ids: Bag<number>;
private nextAvailableId_ = 0;
/**
* ID池管理器
* ID的分配和回收
*/
export class IdentifierPool {
private _nextAvailableId = 0;
private _ids: number[] = [];
constructor() {
this.ids = new Bag<number>();
/**
* ID
*/
public checkOut(): number {
if (this._ids.length > 0) {
return this._ids.pop()!;
}
return this._nextAvailableId++;
}
public checkOut(): number {
if (this.ids.size() > 0) {
return this.ids.removeLast();
}
return this.nextAvailableId_++;
}
public checkIn(id: number): void {
this.ids.add(id);
}
/**
* ID
* @param id ID
*/
public checkIn(id: number): void {
this._ids.push(id);
}
}
+146 -67
View File
@@ -1,89 +1,168 @@
module es {
import { Entity } from '../Entity';
import { Component } from '../Component';
import { Bits } from './Bits';
import { ComponentTypeManager } from './ComponentTypeManager';
/**
*
*
*/
export class Matcher {
protected allSet: (new (...args: any[]) => Component)[] = [];
protected exclusionSet: (new (...args: any[]) => Component)[] = [];
protected oneSet: (new (...args: any[]) => Component)[] = [];
// 缓存的位掩码,避免重复计算
private _allBits?: Bits;
private _exclusionBits?: Bits;
private _oneBits?: Bits;
private _isDirty = true;
public static empty(): Matcher {
return new Matcher();
}
public getAllSet(): (new (...args: any[]) => Component)[] {
return this.allSet;
}
public getExclusionSet(): (new (...args: any[]) => Component)[] {
return this.exclusionSet;
}
public getOneSet(): (new (...args: any[]) => Component)[] {
return this.oneSet;
}
/**
*
*
* @param entity
* @returns
*/
export class Matcher {
protected allSet: (new (...args: any[]) => Component)[] = [];
protected exclusionSet: (new (...args: any[]) => Component)[] = [];
protected oneSet: (new (...args: any[]) => Component)[] = [];
public isInterestedEntity(entity: Entity): boolean {
const entityBits = this.getEntityBits(entity);
return this.isInterested(entityBits);
}
public static empty() {
return new Matcher();
/**
*
* @param componentBits
* @returns
*/
public isInterested(componentBits: Bits): boolean {
this.updateBitsIfDirty();
// 检查必须包含的组件
if (this._allBits && !componentBits.containsAll(this._allBits)) {
return false;
}
public getAllSet() {
return this.allSet;
// 检查排除的组件
if (this._exclusionBits && componentBits.intersects(this._exclusionBits)) {
return false;
}
public getExclusionSet() {
return this.exclusionSet;
// 检查至少包含其中之一的组件
if (this._oneBits && !componentBits.intersects(this._oneBits)) {
return false;
}
public getOneSet() {
return this.oneSet;
return true;
}
/**
*
* @param types
*/
public all(...types: (new (...args: any[]) => Component)[]): Matcher {
this.allSet.push(...types);
this._isDirty = true;
return this;
}
/**
*
* @param types
*/
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
this.exclusionSet.push(...types);
this._isDirty = true;
return this;
}
/**
*
* @param types
*/
public one(...types: (new (...args: any[]) => Component)[]): Matcher {
this.oneSet.push(...types);
this._isDirty = true;
return this;
}
/**
*
* @param entity
* @returns
*/
private getEntityBits(entity: Entity): Bits {
const components = entity.components;
return ComponentTypeManager.instance.getEntityBits(components);
}
/**
*
*/
private updateBitsIfDirty(): void {
if (!this._isDirty) {
return;
}
public isInterestedEntity(e: Entity) {
return this.isInterested(e.componentBits);
const typeManager = ComponentTypeManager.instance;
// 更新必须包含的组件位掩码
if (this.allSet.length > 0) {
this._allBits = typeManager.createBits(...this.allSet);
} else {
this._allBits = undefined;
}
public isInterested(components: Bits) {
if (this.allSet.length !== 0) {
for (let i = 0; i < this.allSet.length; i++) {
const type = this.allSet[i];
if (!components.get(ComponentTypeManager.getIndexFor(type))) {
return false;
}
}
}
if (this.exclusionSet.length !== 0) {
for (let i = 0; i < this.exclusionSet.length; i++) {
const type = this.exclusionSet[i];
if (components.get(ComponentTypeManager.getIndexFor(type))) {
return false;
}
}
}
if (this.oneSet.length !== 0) {
for (let i = 0; i < this.oneSet.length; i++) {
const type = this.oneSet[i];
if (components.get(ComponentTypeManager.getIndexFor(type))) {
return true;
}
}
return false;
}
return true;
// 更新排除的组件位掩码
if (this.exclusionSet.length > 0) {
this._exclusionBits = typeManager.createBits(...this.exclusionSet);
} else {
this._exclusionBits = undefined;
}
/**
*
* @param types
*/
public all(...types: (new (...args: any[]) => Component)[]): Matcher {
this.allSet.push(...types);
return this;
// 更新至少包含其中之一的组件位掩码
if (this.oneSet.length > 0) {
this._oneBits = typeManager.createBits(...this.oneSet);
} else {
this._oneBits = undefined;
}
/**
*
* @param types
*/
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
this.exclusionSet.push(...types);
return this;
this._isDirty = false;
}
/**
*
* @returns
*/
public toString(): string {
const parts: string[] = [];
if (this.allSet.length > 0) {
parts.push(`all: [${this.allSet.map(t => t.name).join(', ')}]`);
}
/**
*
* @param types
*/
public one(...types: (new (...args: any[]) => Component)[]): Matcher {
this.oneSet.push(...types);
return this;
if (this.exclusionSet.length > 0) {
parts.push(`exclude: [${this.exclusionSet.map(t => t.name).join(', ')}]`);
}
if (this.oneSet.length > 0) {
parts.push(`one: [${this.oneSet.map(t => t.name).join(', ')}]`);
}
return `Matcher(${parts.join(', ')})`;
}
}
-245
View File
@@ -1,245 +0,0 @@
module es {
export class StringUtils {
/**
*
*/
private static specialSigns: string[] = [
'&', '&amp;',
'<', '&lt;',
'>', '&gt;',
'"', '&quot;',
"'", '&apos;',
'®', '&reg;',
'©', '&copy;',
'™', '&trade;',
];
/**
*
* @param str
* @return
*/
public static matchChineseWord(str: string): string[] {
//中文字符的unicode值[\u4E00-\u9FA5]
let patternA: RegExp = /[\u4E00-\u9FA5]+/gim;
return str.match(patternA);
}
/**
*
* @param target
* @return
*/
public static lTrim(target: string): string {
let startIndex: number = 0;
while (this.isWhiteSpace(target.charAt(startIndex))) {
startIndex++;
}
return target.slice(startIndex, target.length);
}
/**
*
* @param target
* @return
*/
public static rTrim(target: string): string {
let endIndex: number = target.length - 1;
while (this.isWhiteSpace(target.charAt(endIndex))) {
endIndex--;
}
return target.slice(0, endIndex + 1);
}
/**
* 2
* @param target
* @return 2
*/
public static trim(target: string): string {
if (target == null) {
return null;
}
return this.rTrim(this.lTrim(target));
}
/**
*
* @param str
* @return
*/
public static isWhiteSpace(str: string): boolean {
if (str == " " || str == "\t" || str == "\r" || str == "\n")
return true;
return false;
}
/**
* null
* @param str
* @returns
*/
public static isNullOrEmpty(str: string): boolean {
if (str == "" || str == null || str == undefined)
return true;
return false;
}
/**
*
* @param mainStr
* @param targetStr
* @param replaceStr
* @param caseMark
* @return
*/
public static replaceMatch(mainStr: string, targetStr: string,
replaceStr: string, caseMark: boolean = false): string {
let len: number = mainStr.length;
let tempStr: string = "";
let isMatch: boolean = false;
let tempTarget: string = caseMark == true ? targetStr.toLowerCase() : targetStr;
for (let i: number = 0; i < len; i++) {
isMatch = false;
if (mainStr.charAt(i) == tempTarget.charAt(0)) {
if (mainStr.substr(i, tempTarget.length) == tempTarget) {
isMatch = true;
}
}
if (isMatch) {
tempStr += replaceStr;
i = i + tempTarget.length - 1;
} else {
tempStr += mainStr.charAt(i);
}
}
return tempStr;
}
/**
* html实体换掉字符窜中的特殊字符
* @param str
* @param reversion
* @return
*/
public static htmlSpecialChars(str: string, reversion: boolean = false): string {
let len: number = this.specialSigns.length;
for (let i: number = 0; i < len; i += 2) {
let from: string;
let to: string;
from = this.specialSigns[i];
to = this.specialSigns[i + 1];
if (reversion) {
let temp: string = from;
from = to;
to = temp;
}
str = this.replaceMatch(str, from, to);
}
return str;
}
/**
* "0"
*
* @param str
* @param width
* str.length >= widthstr
* @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;
}
}
}
-92
View File
@@ -1,92 +0,0 @@
module es {
/**
*
*/
export class Time {
/** 游戏运行的总时间,单位为秒 */
public static totalTime: number = 0;
/** deltaTime 的未缩放版本,不受时间尺度的影响 */
public static unscaledDeltaTime: number = 0;
/** 前一帧到当前帧的时间增量,按时间刻度进行缩放 */
public static deltaTime: number = 0;
/** 时间刻度缩放,可以加快或减慢游戏时间 */
public static timeScale = 1;
/** DeltaTime 可以为的最大值,避免游戏出现卡顿情况 */
public static maxDeltaTime = Number.MAX_VALUE;
/** 已传递的帧总数 */
public static frameCount = 0;
/** 自场景加载以来的总时间,单位为秒 */
public static timeSinceSceneLoad: number = 0;
/** 上一次记录的时间,用于计算两次调用 update 之间的时间差 */
private static _lastTime = -1;
/**
*
* @param currentTime
* @param useEngineTime 使
*/
public static update(currentTime: number, useEngineTime: boolean) {
let dt = 0;
if (useEngineTime) {
dt = currentTime;
} else {
// 如果当前时间为 -1,则表示使用系统时间
if (currentTime === -1) {
currentTime = Date.now();
}
// 如果上一次记录的时间为 -1,则表示当前为第一次调用 update
if (this._lastTime === -1) {
this._lastTime = currentTime;
}
// 计算两次调用 update 之间的时间差,并将其转换为秒
dt = (currentTime - this._lastTime) / 1000;
}
// 如果计算得到的时间差超过了最大时间步长,则将其限制为最大时间步长
if (dt > this.maxDeltaTime) {
dt = this.maxDeltaTime;
}
// 更新时间管理器的各个属性
this.totalTime += dt;
this.deltaTime = dt * this.timeScale;
this.unscaledDeltaTime = dt;
this.timeSinceSceneLoad += dt;
this.frameCount++;
// 记录当前时间,以备下一次调用 update 使用
this._lastTime = currentTime;
}
public static sceneChanged() {
this.timeSinceSceneLoad = 0;
}
/**
*
* @param interval
* @returns
*/
public static checkEvery(interval: number): boolean {
// 计算当前时刻所经过的完整时间间隔个数(向下取整)
const passedIntervals = Math.floor(this.timeSinceSceneLoad / interval);
// 计算上一帧到当前帧经过的时间所包含的时间间隔个数(向下取整)
const deltaIntervals = Math.floor(this.deltaTime / interval);
// 如果当前时刻所经过的时间间隔数比上一帧所经过的时间间隔数多,则说明时间间隔已过去
return passedIntervals > deltaIntervals;
}
}
}
-242
View File
@@ -1,242 +0,0 @@
module es {
export class TimeUtils {
/**
*
* @param d
* @returns
*/
public static monthId(d: Date = null): number {
// 如果传入了时间,则使用传入的时间,否则使用当前时间
d = d ? d : new Date();
// 获取当前年份
let y = d.getFullYear();
// 获取当前月份,并将月份转化为两位数的字符串格式
let m = d.getMonth() + 1;
let g = m < 10 ? "0" : "";
// 返回年份和月份的数字组合
return parseInt(y + g + m);
}
/**
*
* @param t - 使
* @returns
*/
public static dateId(t: Date = null): number {
// 如果传入了时间,则使用传入的时间,否则使用当前时间
t = t ? t : new Date();
// 获取当前月份,并将月份转化为两位数的字符串格式
let m: number = t.getMonth() + 1;
let a = m < 10 ? "0" : "";
// 获取当前日期,并将日期转化为两位数的字符串格式
let d: number = t.getDate();
let b = d < 10 ? "0" : "";
// 返回年份、月份和日期的数字组合
return parseInt(t.getFullYear() + a + m + b + d);
}
/**
*
* @param d - 使
* @param first - 1true
* @returns
*/
public static weekId(d: Date = null, first: boolean = true): number {
d = d ? d : new Date();
const c: Date = new Date(d.getTime()); // 复制一个新的日期对象,以免改变原始日期对象
c.setDate(1);
c.setMonth(0); // 将日期设置为当年的第一天
const year: number = c.getFullYear();
let firstDay: number = c.getDay();
if (firstDay == 0) {
firstDay = 7;
}
let max: boolean = false;
if (firstDay <= 4) {
max = firstDay > 1;
c.setDate(c.getDate() - (firstDay - 1));
} else {
c.setDate(c.getDate() + 7 - firstDay + 1);
}
let num: number = this.diffDay(d, c, false); // 计算当前日期与本年度的第一个星期一之间的天数
if (num < 0) {
// 当前日期在本年度第一个星期一之前,则返回上一年度的最后一个星期
c.setDate(1);
c.setMonth(0);
c.setDate(c.getDate() - 1);
return this.weekId(c, false);
}
// 计算当前日期在本年度中是第几个星期
const week: number = Math.floor(num / 7);
const weekIdx: number = Math.floor(week) + 1;
if (weekIdx == 53) {
c.setTime(d.getTime());
c.setDate(c.getDate() - 1);
let endDay: number = c.getDay();
if (endDay == 0) {
endDay = 7;
}
if (first && (!max || endDay < 4)) {
// 如果当前日期在本年度的最后一个星期并且当前年度的星期数不足53或当前日期在本年度第53周的星期4或更早,则返回下一年度的第1周
c.setFullYear(c.getFullYear() + 1);
c.setDate(1);
c.setMonth(0);
return this.weekId(c, false);
}
}
const g: string = weekIdx > 9 ? "" : "0";
const s: string = year + "00" + g + weekIdx; // 加上00防止和月份ID冲突
return parseInt(s);
}
/**
*
* @param a
* @param b
* @param fixOne
* @returns
*/
public static diffDay(a: Date, b: Date, fixOne: boolean = false): number {
let x = (a.getTime() - b.getTime()) / 86400000; // 计算两个日期相差的毫秒数,然后除以一天的毫秒数,得到相差的天数
return fixOne ? Math.ceil(x) : Math.floor(x); // 如果 fixOne 参数为 true,则将相差天数四舍五入到整数,否则向下取整
}
/**
*
* @param d
* @returns
*/
public static getFirstDayOfWeek(d: Date = new Date()): Date {
// 获取当前日期是星期几,如果是0,则设置为7
let dayOfWeek = d.getDay() || 7;
// 计算出指定日期所在周的第一天,即将指定日期减去星期几再加1
// 这里用1减去dayOfWeek是为了保证星期一为一周的第一天
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1 - dayOfWeek, 0, 0, 0, 0);
}
/**
*
*/
public static getFirstOfDay(d?: Date): Date {
d = d ? d : new Date();
d.setHours(0, 0, 0, 0);
return d;
}
/**
*
*/
public static getNextFirstOfDay(d?: Date): Date {
return new Date(this.getFirstOfDay(d).getTime() + 86400000);
}
/**
* "YYYY-MM-DD"
* @param date
* @returns
*/
public static formatDate(date: Date): string {
let y = date.getFullYear();
let m: any = date.getMonth() + 1;
m = m < 10 ? '0' + m : m;
let d: any = date.getDate();
d = d < 10 ? ('0' + d) : d;
return y + '-' + m + '-' + d;
}
/**
* "YYYY-MM-DD HH:mm:ss"
* @param date
* @returns
*/
public static formatDateTime(date: Date): string {
let y = date.getFullYear();
let m: any = date.getMonth() + 1;
m = m < 10 ? ('0' + m) : m;
let d: any = date.getDate();
d = d < 10 ? ('0' + d) : d;
let h = date.getHours();
let i: any = date.getMinutes();
i = i < 10 ? ('0' + i) : i;
let s: any = date.getSeconds();
s = s < 10 ? ('0' + s) : s;
return y + '-' + m + '-' + d + ' ' + h + ':' + i + ":" + s;
}
/**
* Date对象
* @param s 2022-01-01
* @returns Date对象Date对象
*/
public static parseDate(s: string): Date {
let t = Date.parse(s);
if (!isNaN(t)) {
// 如果日期字符串中的分隔符为“-”,则需要先将其转换为“/”,否则解析会失败
return new Date(Date.parse(s.replace(/-/g, "/")));
} else {
return new Date();
}
}
/**
*
* @param time
* @param partition
* @param showHour
* @returns
*/
public static secondToTime(time: number = 0, partition: string = ":", showHour: boolean = true): string {
let hours: number = Math.floor(time / 3600);
let minutes: number = Math.floor(time % 3600 / 60);
let seconds: number = Math.floor(time % 3600 % 60);
let h: string = hours.toString();
let m: string = minutes.toString();
let s: string = seconds.toString();
if (hours < 10) h = "0" + h;
if (minutes < 10) m = "0" + m;
if (seconds < 10) s = "0" + s;
let timeStr: string;
if (showHour)
timeStr = h + partition + m + partition + s;
else
timeStr = m + partition + s;
return timeStr;
}
/**
*
* @param time "01:30:15" 13015
* @param partition ":"
* @returns
*/
public static timeToMillisecond(time: string, partition: string = ":"): string {
let _ary: any[] = time.split(partition);
let timeNum: number = 0;
let len: number = _ary.length;
// 将时间转换成毫秒数
for (let i: number = 0; i < len; i++) {
let n: number = <number>_ary[i];
timeNum += n * Math.pow(60, (len - 1 - i));
}
timeNum *= 1000;
return timeNum.toString();
}
}
}
+7
View File
@@ -0,0 +1,7 @@
// ECS工具类导出
export { EntityList } from './EntityList';
export { EntityProcessorList } from './EntityProcessorList';
export { IdentifierPool } from './IdentifierPool';
export { Matcher } from './Matcher';
export { Bits } from './Bits';
export { ComponentTypeManager } from './ComponentTypeManager';
+8
View File
@@ -0,0 +1,8 @@
// Core目录已删除,直接导出核心类
export { Entity } from './Entity';
export { Component } from './Component';
export { Transform } from './Transform';
export { CoreEvents } from './CoreEvents';
export * from './Systems';
export * from './Utils';
export { Scene } from './Scene';
-126
View File
@@ -1,126 +0,0 @@
module es {
/**
* (cubic and quadratic bezier helper)
*/
export class Bezier {
/**
* 线
* @param p0
* @param p1
* @param p2
* @param t
*/
public static getPoint(p0: Vector2, p1: Vector2, p2: Vector2, t: number): Vector2 {
t = MathHelper.clamp01(t);
let oneMinusT = 1 - t;
return p0.scale(oneMinusT * oneMinusT)
.addEqual(p1.scale(2 * oneMinusT * t))
.addEqual(p2.scale(t * t));
}
/**
*
* @param start
* @param firstControlPoint
* @param secondControlPoint
* @param end
* @param t
*/
public static getPointThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
end: Vector2, t: number): Vector2 {
t = MathHelper.clamp01(t);
const oneMinusT = 1 - t;
return start.scale(oneMinusT * oneMinusT * oneMinusT)
.addEqual(firstControlPoint.scale(3 * oneMinusT * oneMinusT * t))
.addEqual(secondControlPoint.scale(3 * oneMinusT * t * t))
.addEqual(end.scale(t * t * t));
}
/**
*
* @param p0
* @param p1
* @param p2
* @param t
*/
public static getFirstDerivative(p0: Vector2, p1: Vector2, p2: Vector2, t: number) {
return p1.sub(p0).scale(2 * (1 - t))
.addEqual(p2.sub(p1).scale(2 * t));
}
/**
*
* @param start
* @param firstControlPoint
* @param secondControlPoint
* @param end
* @param t
*/
public static getFirstDerivativeThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
end: Vector2, t: number) {
t = MathHelper.clamp01(t);
let oneMunusT = 1 - t;
return firstControlPoint.sub(start).scale(3 * oneMunusT * oneMunusT)
.addEqual(secondControlPoint.sub(firstControlPoint).scale(6 * oneMunusT * t))
.addEqual(end.sub(secondControlPoint).scale(3 * t * t));
}
/**
* bezier曲线
* ListPool的合并列表
* @param start
* @param firstCtrlPoint
* @param secondCtrlPoint
* @param end
* @param distanceTolerance
*/
public static getOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
end: Vector2, distanceTolerance: number = 1) {
let points = ListPool.obtain<Vector2>(Vector2);
points.push(start);
this.recursiveGetOptimizedDrawingPoints(start, firstCtrlPoint, secondCtrlPoint, end, points, distanceTolerance);
points.push(end);
return points;
}
/**
* bezier曲线
* @param start
* @param firstCtrlPoint
* @param secondCtrlPoint
* @param end
* @param points
* @param distanceTolerance
*/
private static recursiveGetOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
end: Vector2, points: Vector2[], distanceTolerance: number) {
// 计算线段的所有中点
let pt12 = Vector2.divideScaler(start.add(firstCtrlPoint), 2);
let pt23 = Vector2.divideScaler(firstCtrlPoint.add(secondCtrlPoint), 2);
let pt34 = Vector2.divideScaler(secondCtrlPoint.add(end), 2);
// 计算新半直线的中点
let pt123 = Vector2.divideScaler(pt12.add(pt23), 2);
let pt234 = Vector2.divideScaler(pt23.add(pt34), 2);
// 最后再细分最后两个中点。如果我们满足我们的距离公差,这将是我们使用的最后一点。
let pt1234 = Vector2.divideScaler(pt123.add(pt234), 2);
// 试着用一条直线来近似整个三次曲线
let deltaLine = end.sub(start);
let d2 = Math.abs(((firstCtrlPoint.x, end.x) * deltaLine.y - (firstCtrlPoint.y - end.y) * deltaLine.x));
let d3 = Math.abs(((secondCtrlPoint.x - end.x) * deltaLine.y - (secondCtrlPoint.y - end.y) * deltaLine.x));
if ((d2 + d3) * (d2 + d3) < distanceTolerance * (deltaLine.x * deltaLine.x + deltaLine.y * deltaLine.y)) {
points.push(pt1234);
return;
}
// 继续细分
this.recursiveGetOptimizedDrawingPoints(start, pt12, pt123, pt1234, points, distanceTolerance);
this.recursiveGetOptimizedDrawingPoints(pt1234, pt234, pt34, end, points, distanceTolerance);
}
}
}
-117
View File
@@ -1,117 +0,0 @@
module es {
/**
* 访
*/
export class BezierSpline {
public _points: Vector2[] = [];
public _curveCount: number = 0;
/**
* t被修改为在曲线段的范围内
* @param t
*/
public pointIndexAtTime(t: number): {time: number, range: number} {
const res = {time: 0, range: 0};
if (t >= 1) {
t = 1;
res.range = this._points.length - 4;
} else {
t = MathHelper.clamp01(t) * this._curveCount;
res.range = MathHelper.toInt(t);
t -= res.range;
res.range *= 3;
}
res.time = t;
return res;
}
/**
*
* @param index
* @param point
*/
public setControlPoint(index: number, point: Vector2) {
if (index % 3 == 0) {
const delta = point.sub(this._points[index]);
if (index > 0)
this._points[index - 1].addEqual(delta);
if (index + 1 < this._points.length)
this._points[index + 1].addEqual(delta);
}
this._points[index] = point;
}
/**
* t的贝塞尔曲线上的点
* @param t
*/
public getPointAtTime(t: number): Vector2{
const res = this.pointIndexAtTime(t);
const i = res.range;
return Bezier.getPointThree(this._points[i], this._points[i + 1], this._points[i + 2],
this._points[i + 3], t);
}
/**
* t的速度
* @param t
*/
public getVelocityAtTime(t: number): Vector2 {
const res = this.pointIndexAtTime(t);
const i = res.range;
return Bezier.getFirstDerivativeThree(this._points[i], this._points[i + 1], this._points[i + 2],
this._points[i + 3], t);
}
/**
* t时贝塞尔的方向
* @param t
*/
public getDirectionAtTime(t: number) {
return this.getVelocityAtTime(t).normalize();
}
/**
* 线线
* @param start
* @param firstControlPoint
* @param secondControlPoint
* @param end
*/
public addCurve(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2, end: Vector2) {
// 只有当这是第一条曲线时,我们才会添加起始点。对于其他所有的曲线,前一个曲线的终点应该等于新曲线的起点。
if (this._points.length == 0)
this._points.push(start);
this._points.push(firstControlPoint);
this._points.push(secondControlPoint);
this._points.push(end);
this._curveCount = (this._points.length - 1) / 3;
}
/**
* bezier
*/
public reset() {
this._points.length = 0;
}
/**
* splitine分解成totalSegments部分使线
* @param totalSegments
*/
public getDrawingPoints(totalSegments: number): Vector2[] {
let points: Vector2[] = [];
for (let i = 0; i < totalSegments; i ++) {
let t = i / totalSegments;
points[i] = this.getPointAtTime(t);
}
return points;
}
}
}
+25
View File
@@ -0,0 +1,25 @@
/**
*
*
*/
export enum Edge {
/**
*
*/
top = 0,
/**
*
*/
bottom = 1,
/**
*
*/
left = 2,
/**
*
*/
right = 3
}
-80
View File
@@ -1,80 +0,0 @@
module es {
/**
*
*/
export class Flags {
/**
*
* @param self
* @param flag 2
* @returns truefalse
*/
public static isFlagSet(self: number, flag: number): boolean {
return (self & flag) !== 0;
}
/**
*
* @param self
* @param flag 2
* @returns truefalse
*/
public static isUnshiftedFlagSet(self: number, flag: number): boolean {
flag = 1 << flag;
return (self & flag) !== 0;
}
/**
*
* @param self
* @param flag 2
*/
public static setFlagExclusive(self: Ref<number>, flag: number) {
self.value = 1 << flag;
}
/**
*
* @param self
* @param flag 2
*/
public static setFlag(self: Ref<number>, flag: number) {
self.value |= 1 << flag;
}
/**
*
* @param self
* @param flag 2
*/
public static unsetFlag(self: Ref<number>, flag: number) {
flag = 1 << flag;
self.value &= ~flag;
}
/**
* 1001
* @param self
*/
public static invertFlags(self: Ref<number>) {
self.value = ~self.value;
}
/**
*
* @param self
* @param leftPadWidth 0
* @returns
*/
public static binaryStringRepresentation(
self: number,
leftPadWidth = 10
): string {
let str = self.toString(2);
while (str.length < (leftPadWidth || 2)) {
str = "0" + str;
}
return str;
}
}
}
File diff suppressed because it is too large Load Diff
-153
View File
@@ -1,153 +0,0 @@
module es {
/**
* 4x4浮点矩阵
*/
export class Matrix {
private static identity = new Matrix(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
public static get Identity() {
return this.identity;
}
public m11: number;
public m12: number;
public m13: number;
public m14: number;
public m21: number;
public m22: number;
public m23: number;
public m24: number;
public m31: number;
public m32: number;
public m33: number;
public m34: number;
public m41: number;
public m42: number;
public m43: number;
public m44: number;
constructor(m11?, m12?, m13?, m14?, m21?, m22?, m23?, m24?, m31?,
m32?, m33?, m34?, m41?, m42?, m43?, m44?) {
this.m11 = m11;
this.m12 = m12;
this.m13 = m13;
this.m14 = m14;
this.m21 = m21;
this.m22 = m22;
this.m23 = m23;
this.m24 = m24;
this.m31 = m31;
this.m32 = m32;
this.m33 = m33;
this.m34 = m34;
this.m41 = m41;
this.m42 = m42;
this.m43 = m43;
this.m44 = m44;
}
/**
*
* @param left
* @param right
* @param top
* @param zFarPlane
* @param result
*/
public static createOrthographicOffCenter(left: number, right: number, bottom: number, top: number, zNearPlane: number, zFarPlane: number, result: Matrix = new Matrix()) {
result.m11 = 2 / (right - left);
result.m12 = 0;
result.m13 = 0;
result.m14 = 0;
result.m21 = 0;
result.m22 = 2 / (top - bottom);
result.m23 = 0;
result.m24 = 0;
result.m31 = 0;
result.m32 = 0;
result.m33 = 1 / (zNearPlane - zFarPlane);
result.m34 = 0;
result.m41 = (left + right) / (left - right);
result.m42 = (top + bottom) / (bottom - top);
result.m43 = zNearPlane / (zNearPlane - zFarPlane);
result.m44 = 1;
}
public static createTranslation(position: Vector2, result: Matrix)
{
result.m11 = 1;
result.m12 = 0;
result.m13 = 0;
result.m14 = 0;
result.m21 = 0;
result.m22 = 1;
result.m23 = 0;
result.m24 = 0;
result.m31 = 0;
result.m32 = 0;
result.m33 = 1;
result.m34 = 0;
result.m41 = position.x;
result.m42 = position.y;
result.m43 = 0;
result.m44 = 1;
}
public static createRotationZ(radians: number, result: Matrix)
{
result = Matrix.Identity;
var val1 = Math.cos(radians);
var val2 = Math.sin(radians);
result.m11 = val1;
result.m12 = val2;
result.m21 = -val2;
result.m22 = val1;
}
/**
*
* @param matrix1
* @param matrix2
* @param result
*/
public static multiply(matrix1: Matrix, matrix2: Matrix, result: Matrix = new Matrix()) {
let m11 = (((matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21)) + (matrix1.m13 * matrix2.m31)) + (matrix1.m14 * matrix2.m41);
let m12 = (((matrix1.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22)) + (matrix1.m13 * matrix2.m32)) + (matrix1.m14 * matrix2.m42);
let m13 = (((matrix1.m11 * matrix2.m13) + (matrix1.m12 * matrix2.m23)) + (matrix1.m13 * matrix2.m33)) + (matrix1.m14 * matrix2.m43);
let m14 = (((matrix1.m11 * matrix2.m14) + (matrix1.m12 * matrix2.m24)) + (matrix1.m13 * matrix2.m34)) + (matrix1.m14 * matrix2.m44);
let m21 = (((matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21)) + (matrix1.m23 * matrix2.m31)) + (matrix1.m24 * matrix2.m41);
let m22 = (((matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22)) + (matrix1.m23 * matrix2.m32)) + (matrix1.m24 * matrix2.m42);
let m23 = (((matrix1.m21 * matrix2.m13) + (matrix1.m22 * matrix2.m23)) + (matrix1.m23 * matrix2.m33)) + (matrix1.m24 * matrix2.m43);
let m24 = (((matrix1.m21 * matrix2.m14) + (matrix1.m22 * matrix2.m24)) + (matrix1.m23 * matrix2.m34)) + (matrix1.m24 * matrix2.m44);
let m31 = (((matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21)) + (matrix1.m33 * matrix2.m31)) + (matrix1.m34 * matrix2.m41);
let m32 = (((matrix1.m31 * matrix2.m12) + (matrix1.m32 * matrix2.m22)) + (matrix1.m33 * matrix2.m32)) + (matrix1.m34 * matrix2.m42);
let m33 = (((matrix1.m31 * matrix2.m13) + (matrix1.m32 * matrix2.m23)) + (matrix1.m33 * matrix2.m33)) + (matrix1.m34 * matrix2.m43);
let m34 = (((matrix1.m31 * matrix2.m14) + (matrix1.m32 * matrix2.m24)) + (matrix1.m33 * matrix2.m34)) + (matrix1.m34 * matrix2.m44);
let m41 = (((matrix1.m41 * matrix2.m11) + (matrix1.m42 * matrix2.m21)) + (matrix1.m43 * matrix2.m31)) + (matrix1.m44 * matrix2.m41);
let m42 = (((matrix1.m41 * matrix2.m12) + (matrix1.m42 * matrix2.m22)) + (matrix1.m43 * matrix2.m32)) + (matrix1.m44 * matrix2.m42);
let m43 = (((matrix1.m41 * matrix2.m13) + (matrix1.m42 * matrix2.m23)) + (matrix1.m43 * matrix2.m33)) + (matrix1.m44 * matrix2.m43);
let m44 = (((matrix1.m41 * matrix2.m14) + (matrix1.m42 * matrix2.m24)) + (matrix1.m43 * matrix2.m34)) + (matrix1.m44 * matrix2.m44);
result.m11 = m11;
result.m12 = m12;
result.m13 = m13;
result.m14 = m14;
result.m21 = m21;
result.m22 = m22;
result.m23 = m23;
result.m24 = m24;
result.m31 = m31;
result.m32 = m32;
result.m33 = m33;
result.m34 = m34;
result.m41 = m41;
result.m42 = m42;
result.m43 = m43;
result.m44 = m44;
}
}
}
-350
View File
@@ -1,350 +0,0 @@
module es {
/**
* 3 * 3
*/
export class Matrix2D implements IEquatable<Matrix2D> {
public m11: number = 0; // x 缩放
public m12: number = 0;
public m21: number = 0;
public m22: number = 0;
public m31: number = 0;
public m32: number = 0;
/**
*
*/
public static get identity(): Matrix2D {
return new Matrix2D().setIdentity();
}
public setIdentity(): Matrix2D {
return this.setValues(1, 0, 0, 1, 0, 0);
}
public setValues(
m11: number,
m12: number,
m21: number,
m22: number,
m31: number,
m32: number
): Matrix2D {
this.m11 = m11;
this.m12 = m12;
this.m21 = m21;
this.m22 = m22;
this.m31 = m31;
this.m32 = m32;
return this;
}
/**
*
*/
public get translation(): Vector2 {
return new Vector2(this.m31, this.m32);
}
public set translation(value: Vector2) {
this.m31 = value.x;
this.m32 = value.y;
}
/**
*
*/
public get rotation(): number {
return Math.atan2(this.m21, this.m11);
}
public set rotation(value: number) {
let val1 = Math.cos(value);
let val2 = Math.sin(value);
this.m11 = val1;
this.m12 = val2;
this.m21 = -val2;
this.m22 = val1;
}
/**
*
*/
public get rotationDegrees() {
return MathHelper.toDegrees(this.rotation);
}
public set rotationDegrees(value: number) {
this.rotation = MathHelper.toRadians(value);
}
/**
*
*/
public get scale(): Vector2 {
return new Vector2(this.m11, this.m22);
}
public set scale(value: Vector2) {
this.m11 = value.x;
this.m22 = value.y;
}
/**
* Z轴的旋转矩阵2D
* @param radians
*/
public static createRotation(radians: number, result: Matrix2D) {
result.setIdentity();
const val1 = Math.cos(radians);
const val2 = Math.sin(radians);
result.m11 = val1;
result.m12 = val2;
result.m21 = val2 * -1;
result.m22 = val1;
}
public static createRotationOut(radians: number, result: Matrix2D) {
let val1 = Math.cos(radians);
let val2 = Math.sin(radians);
result.m11 = val1;
result.m12 = val2;
result.m21 = -val2;
result.m22 = val1;
}
/**
* 2D
* @param xScale
* @param yScale
*/
public static createScale(xScale: number, yScale: number, result: Matrix2D) {
result.m11 = xScale;
result.m12 = 0;
result.m21 = 0;
result.m22 = yScale;
result.m31 = 0;
result.m32 = 0;
}
public static createScaleOut(xScale: number, yScale: number, result: Matrix2D) {
result.m11 = xScale;
result.m12 = 0;
result.m21 = 0;
result.m22 = yScale;
result.m31 = 0;
result.m32 = 0;
}
/**
* 2D
* @param xPosition
* @param yPosition
*/
public static createTranslation(xPosition: number, yPosition: number, result: Matrix2D) {
result.m11 = 1;
result.m12 = 0;
result.m21 = 0;
result.m22 = 1;
result.m31 = xPosition;
result.m32 = yPosition;
return result;
}
public static createTranslationOut(position: Vector2, result: Matrix2D) {
result.m11 = 1;
result.m12 = 0;
result.m21 = 0;
result.m22 = 1;
result.m31 = position.x;
result.m32 = position.y;
}
public static invert(matrix: Matrix2D) {
let det = 1 / matrix.determinant();
let result = this.identity;
result.m11 = matrix.m22 * det;
result.m12 = -matrix.m12 * det;
result.m21 = -matrix.m21 * det;
result.m22 = matrix.m11 * det;
result.m31 = (matrix.m32 * matrix.m21 - matrix.m31 * matrix.m22) * det;
result.m32 = -(matrix.m32 * matrix.m11 - matrix.m31 * matrix.m12) * det;
return result;
}
/**
* matrix,
* @param matrix
*/
public add(matrix: Matrix2D): Matrix2D {
this.m11 += matrix.m11;
this.m12 += matrix.m12;
this.m21 += matrix.m21;
this.m22 += matrix.m22;
this.m31 += matrix.m31;
this.m32 += matrix.m32;
return this;
}
public substract(matrix: Matrix2D): Matrix2D {
this.m11 -= matrix.m11;
this.m12 -= matrix.m12;
this.m21 -= matrix.m21;
this.m22 -= matrix.m22;
this.m31 -= matrix.m31;
this.m32 -= matrix.m32;
return this;
}
public divide(matrix: Matrix2D): Matrix2D {
this.m11 /= matrix.m11;
this.m12 /= matrix.m12;
this.m21 /= matrix.m21;
this.m22 /= matrix.m22;
this.m31 /= matrix.m31;
this.m32 /= matrix.m32;
return this;
}
public multiply(matrix: Matrix2D): Matrix2D {
let m11 = (this.m11 * matrix.m11) + (this.m12 * matrix.m21);
let m12 = (this.m11 * matrix.m12) + (this.m12 * matrix.m22);
let m21 = (this.m21 * matrix.m11) + (this.m22 * matrix.m21);
let m22 = (this.m21 * matrix.m12) + (this.m22 * matrix.m22);
let m31 = (this.m31 * matrix.m11) + (this.m32 * matrix.m21) + matrix.m31;
let m32 = (this.m31 * matrix.m12) + (this.m32 * matrix.m22) + matrix.m32;
this.m11 = m11;
this.m12 = m12;
this.m21 = m21;
this.m22 = m22;
this.m31 = m31;
this.m32 = m32;
return this;
}
public static multiply(matrix1: Matrix2D, matrix2: Matrix2D, result: Matrix2D) {
const m11 = (matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21);
const m12 = (matrix1.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22);
const m21 = (matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21);
const m22 = (matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22);
const m31 = (matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21) + matrix2.m31;
const m32 = (matrix1.m31 * matrix2.m12) + (matrix1.m32 * matrix2.m22) + matrix2.m32;
result.m11 = m11;
result.m12 = m12;
result.m21 = m21;
result.m22 = m22;
result.m31 = m31;
result.m32 = m32;
}
public determinant() {
return this.m11 * this.m22 - this.m12 * this.m21;
}
/**
* Matrix2D线
* @param matrix1
* @param matrix2
* @param amount
*/
public static lerp(matrix1: Matrix2D, matrix2: Matrix2D, amount: number) {
matrix1.m11 = matrix1.m11 + ((matrix2.m11 - matrix1.m11) * amount);
matrix1.m12 = matrix1.m12 + ((matrix2.m12 - matrix1.m12) * amount);
matrix1.m21 = matrix1.m21 + ((matrix2.m21 - matrix1.m21) * amount);
matrix1.m22 = matrix1.m22 + ((matrix2.m22 - matrix1.m22) * amount);
matrix1.m31 = matrix1.m31 + ((matrix2.m31 - matrix1.m31) * amount);
matrix1.m32 = matrix1.m32 + ((matrix2.m32 - matrix1.m32) * amount);
return matrix1;
}
/**
*
* @param matrix
*/
public static transpose(matrix: Matrix2D) {
let ret: Matrix2D = this.identity;
ret.m11 = matrix.m11;
ret.m12 = matrix.m21;
ret.m21 = matrix.m12;
ret.m22 = matrix.m22;
ret.m31 = 0;
ret.m32 = 0;
return ret;
}
public mutiplyTranslation(x: number, y: number) {
let trans = new Matrix2D();
Matrix2D.createTranslation(x, y, trans);
return MatrixHelper.multiply(this, trans);
}
/**
* Matrix2D
* @param other
*/
public equals(other: Matrix2D) {
return this == other;
}
public static toMatrix(mat: Matrix2D) {
let matrix = new Matrix();
matrix.m11 = mat.m11;
matrix.m12 = mat.m12;
matrix.m13 = 0;
matrix.m14 = 0;
matrix.m21 = mat.m21;
matrix.m22 = mat.m22;
matrix.m23 = 0;
matrix.m24 = 0;
matrix.m31 = 0;
matrix.m32 = 0;
matrix.m33 = 1;
matrix.m34 = 0;
matrix.m41 = mat.m31;
matrix.m42 = mat.m32;
matrix.m43 = 0;
matrix.m44 = 1;
return matrix;
}
public toString() {
return `{m11:${this.m11} m12:${this.m12} m21:${this.m21} m22:${this.m22} m31:${this.m31} m32:${this.m32}}`
}
}
}
-102
View File
@@ -1,102 +0,0 @@
module es {
export class MatrixHelper {
/**
* Matrix2D
* @param {Matrix2D} matrix1 -
* @param {Matrix2D} matrix2 -
* @returns {Matrix2D} - Matrix2D
*/
public static add(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
// 创建一个新的 Matrix2D 对象以存储计算结果。
const result = new Matrix2D();
// 计算两个矩阵的和。
result.m11 = matrix1.m11 + matrix2.m11;
result.m12 = matrix1.m12 + matrix2.m12;
result.m21 = matrix1.m21 + matrix2.m21;
result.m22 = matrix1.m22 + matrix2.m22;
result.m31 = matrix1.m31 + matrix2.m31;
result.m32 = matrix1.m32 + matrix2.m32;
// 返回计算结果的 Matrix2D 对象。
return result;
}
/**
* Matrix2D
* @param {Matrix2D} matrix1 -
* @param {Matrix2D} matrix2 -
* @returns {Matrix2D} - Matrix2D
*/
public static divide(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
// 创建一个新的 Matrix2D 对象以存储计算结果。
const result = new Matrix2D();
// 计算两个矩阵的商。
result.m11 = matrix1.m11 / matrix2.m11;
result.m12 = matrix1.m12 / matrix2.m12;
result.m21 = matrix1.m21 / matrix2.m21;
result.m22 = matrix1.m22 / matrix2.m22;
result.m31 = matrix1.m31 / matrix2.m31;
result.m32 = matrix1.m32 / matrix2.m32;
// 返回计算结果的 Matrix2D 对象。
return result;
}
/**
* Matrix2D Matrix2D
* @param {Matrix2D} matrix1 -
* @param {Matrix2D | number} matrix2 -
* @returns {Matrix2D} - Matrix2D
*/
public static multiply(matrix1: Matrix2D, matrix2: Matrix2D | number): Matrix2D {
// 创建一个新的 Matrix2D 对象以存储计算结果。
const result = new Matrix2D();
// 根据第二个参数的类型执行不同的计算。
if (matrix2 instanceof Matrix2D) {
// 执行矩阵乘法。
result.m11 = matrix1.m11 * matrix2.m11 + matrix1.m12 * matrix2.m21;
result.m12 = matrix1.m11 * matrix2.m12 + matrix1.m12 * matrix2.m22;
result.m21 = matrix1.m21 * matrix2.m11 + matrix1.m22 * matrix2.m21;
result.m22 = matrix1.m21 * matrix2.m12 + matrix1.m22 * matrix2.m22;
result.m31 = matrix1.m31 * matrix2.m11 + matrix1.m32 * matrix2.m21 + matrix2.m31;
result.m32 = matrix1.m31 * matrix2.m12 + matrix1.m32 * matrix2.m22 + matrix2.m32;
} else {
// 执行矩阵和标量的乘法。
result.m11 = matrix1.m11 * matrix2;
result.m12 = matrix1.m12 * matrix2;
result.m21 = matrix1.m21 * matrix2;
result.m22 = matrix1.m22 * matrix2;
result.m31 = matrix1.m31 * matrix2;
result.m32 = matrix1.m32 * matrix2;
}
// 返回计算结果的 Matrix2D 对象。
return result;
}
/**
* Matrix2D
* @param {Matrix2D} matrix1 -
* @param {Matrix2D} matrix2 -
* @returns {Matrix2D} - Matrix2D
*/
public static subtract(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
// 创建一个新的 Matrix2D 对象以存储计算结果。
const result = new Matrix2D();
// 计算两个矩阵的差。
result.m11 = matrix1.m11 - matrix2.m11;
result.m12 = matrix1.m12 - matrix2.m12;
result.m21 = matrix1.m21 - matrix2.m21;
result.m22 = matrix1.m22 - matrix2.m22;
result.m31 = matrix1.m31 - matrix2.m31;
result.m32 = matrix1.m32 - matrix2.m32;
// 返回计算结果的 Matrix2D 对象。
return result;
}
}
}
+216 -567
View File
@@ -1,570 +1,219 @@
module es {
export class Rectangle implements IEquatable<Rectangle> {
/**
* x坐标
*/
public x: number = 0;
/**
* y坐标
*/
public y: number = 0;
/**
*
*/
public width: number = 0;
/**
*
*/
public height: number = 0;
/**
* X=0, Y=0, Width=0, Height=0
*/
public static get empty(): Rectangle {
return new Rectangle();
}
/**
* Number.Min/Max值的矩形
*/
public static get maxRect(): Rectangle {
return new Rectangle(Number.MIN_VALUE / 2, Number.MIN_VALUE / 2, Number.MAX_VALUE, Number.MAX_VALUE);
}
/**
* X坐标
*/
public get left(): number {
return this.x;
}
/**
* X坐标
*/
public get right(): number {
return this.x + this.width;
}
/**
* y坐标
*/
public get top(): number {
return this.y;
}
/**
* y坐标
*/
public get bottom(): number {
return this.y + this.height;
}
/**
*
*/
public get max() {
return new Vector2(this.right, this.bottom);
}
/**
* 000
*/
public isEmpty(): boolean {
return ((((this.width == 0) && (this.height == 0)) && (this.x == 0)) && (this.y == 0));
}
/** 这个矩形的左上角坐标 */
public get location() {
return new Vector2(this.x, this.y);
}
public set location(value: Vector2) {
this.x = value.x;
this.y = value.y;
}
/**
* -
*/
public get size() {
return new Vector2(this.width, this.height);
}
public set size(value: Vector2) {
this.width = value.x;
this.height = value.y;
}
/**
*
* "宽度 " "高度 "
*/
public get center() {
return new Vector2(this.x + (this.width / 2), this.y + (this.height / 2));
}
// temp 用于计算边界的矩阵
public _tempMat: Matrix2D = new Matrix2D();
public _transformMat: Matrix2D = new Matrix2D();
/**
* Rectanglestruct实例
* @param x X坐标
* @param y y坐标
* @param width
* @param height
*/
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* /
* @param minX
* @param minY
* @param maxX
* @param maxY
*/
public static fromMinMax(minX: number, minY: number, maxX: number, maxY: number) {
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/**
*
* @param points
* @returns
*/
public static rectEncompassingPoints(points: Vector2[]) {
// 我们需要求出x/y的最小值/最大值
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
let maxX = Number.NEGATIVE_INFINITY;
let maxY = Number.NEGATIVE_INFINITY;
for (let i = 0; i < points.length; i++) {
let pt = points[i];
if (pt.x < minX) minX = pt.x;
if (pt.x > maxX) maxX = pt.x;
if (pt.y < minY) minY = pt.y;
if (pt.y > maxY) maxY = pt.y;
}
return this.fromMinMax(minX, minY, maxX, maxY);
}
/**
*
* @param edge
*/
public getSide(edge: Edge) {
switch (edge) {
case Edge.top:
return this.top;
case Edge.bottom:
return this.bottom;
case Edge.left:
return this.left;
case Edge.right:
return this.right;
default:
throw new Error("Argument Out Of Range");
}
}
/**
*
* @param x X坐标
* @param y Y坐标
*/
public contains(x: number, y: number): boolean {
return ((((this.x <= x) && (x < (this.x + this.width))) &&
(this.y <= y)) && (y < (this.y + this.height)));
}
/**
*
* @param horizontalAmount
* @param verticalAmount
*/
public inflate(horizontalAmount: number, verticalAmount: number) {
this.x -= horizontalAmount;
this.y -= verticalAmount;
this.width += horizontalAmount * 2;
this.height += verticalAmount * 2;
}
/**
*
* @param value
*/
public intersects(value: Rectangle) {
return value.left < this.right &&
this.left < value.right &&
value.top < this.bottom &&
this.top < value.bottom;
}
public rayIntersects(ray: Ray2D): { intersected: boolean; distance: number } {
// 存储相交点和相交距离
const res = {intersected: false, distance: 0};
let maxValue = Infinity;
// 计算射线与矩形的相交距离
if (Math.abs(ray.direction.x) < 1E-06) {
// 如果射线方向的x分量很小,说明它是垂直的,那么它就不会相交
if (ray.start.x < this.x || ray.start.x > this.x + this.width) {
return res;
}
} else {
// 计算射线与x边界的交点,以及在矩形上面和下面的交点
const num11 = 1 / ray.direction.x;
let num8 = (this.x - ray.start.x) * num11;
let num7 = (this.x + this.width - ray.start.x) * num11;
if (num8 > num7) {
[num7, num8] = [num8, num7];
}
// 将最远的相交距离更新为上下两个交点中更远的那个
res.distance = Math.max(num8, res.distance);
maxValue = Math.min(num7, maxValue);
if (res.distance > maxValue) {
return res;
}
}
// 计算射线与y边界的交点,以及在矩形左边和右边的交点
if (Math.abs(ray.direction.y) < 1e-06) {
if (ray.start.y < this.y || ray.start.y > this.y + this.height) {
return res;
}
} else {
const num10 = 1 / ray.direction.y;
let num6 = (this.y - ray.start.y) * num10;
let num5 = (this.y + this.height - ray.start.y) * num10;
if (num6 > num5) {
[num5, num6] = [num6, num5];
}
// 将最远的相交距离更新为左右两个交点中更远的那个
res.distance = Math.max(num6, res.distance);
maxValue = Math.min(num5, maxValue);
if (res.distance > maxValue) {
return res;
}
}
// 如果相交了,将标志设为真,并返回相交点
res.intersected = true;
return res;
}
/**
*
* @param value
*/
public containsRect(value: Rectangle) {
return ((((this.x <= value.x) && (value.x < (this.x + this.width))) &&
(this.y <= value.y)) &&
(value.y < (this.y + this.height)));
}
public getHalfSize() {
return new Vector2(this.width * 0.5, this.height * 0.5);
}
public getClosestPointOnBoundsToOrigin() {
let max = this.max;
let minDist = Math.abs(this.location.x);
let boundsPoint = new Vector2(this.location.x, 0);
if (Math.abs(max.x) < minDist) {
minDist = Math.abs(max.x);
boundsPoint.x = max.x;
boundsPoint.y = 0;
}
if (Math.abs(max.y) < minDist) {
minDist = Math.abs(max.y);
boundsPoint.x = 0;
boundsPoint.y = max.y;
}
if (Math.abs(this.location.y) < minDist) {
minDist = Math.abs(this.location.y);
boundsPoint.x = 0;
boundsPoint.y = this.location.y;
}
return boundsPoint;
}
/**
*
* @param point
*/
public getClosestPointOnRectangleToPoint(point: Vector2) {
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
let res = es.Vector2.zero;
res.x = MathHelper.clamp(point.x, this.left, this.right);
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
return res;
}
/**
*
* @param point
* @param edgeNormal
* @returns
*/
public getClosestPointOnRectangleBorderToPoint(point: Vector2, edgeNormal: Out<Vector2>): Vector2 {
edgeNormal.value = Vector2.zero;
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
const res = Vector2.zero;
res.x = MathHelper.clamp(point.x, this.left, this.right);
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
// 如果点在矩形内,我们需要将res推到边界上,因为它将在矩形内
if (this.contains(res.x, res.y)) {
let dl = res.x - this.left;
let dr = this.right - res.x;
let dt = res.y - this.top;
let db = this.bottom - res.y;
let min = Math.min(dl, dr, dt, db);
if (min == dt) {
res.y = this.top;
edgeNormal.value.y = -1;
} else if (min == db) {
res.y = this.bottom;
edgeNormal.value.y = 1;
} else if (min == dl) {
res.x = this.left;
edgeNormal.value.x = -1;
} else {
res.x = this.right;
edgeNormal.value.x = 1;
}
} else {
if (res.x == this.left) edgeNormal.value.x = -1;
if (res.x == this.right) edgeNormal.value.x = 1;
if (res.y == this.top) edgeNormal.value.y = -1;
if (res.y == this.bottom) edgeNormal.value.y = 1;
}
return res;
}
/**
* RectangleFRectangleF包含两个其他矩形的重叠区域
* @param value1
* @param value2
* @returns
*/
public static intersect(value1: Rectangle, value2: Rectangle) {
if (value1.intersects(value2)) {
let right_side = Math.min(value1.x + value1.width, value2.x + value2.width);
let left_side = Math.max(value1.x, value2.x);
let top_side = Math.max(value1.y, value2.y);
let bottom_side = Math.min(value1.y + value1.height, value2.y + value2.height);
return new Rectangle(left_side, top_side, right_side - left_side, bottom_side - top_side);
} else {
return new Rectangle(0, 0, 0, 0);
}
}
/**
*
* @param offsetX X坐标
* @param offsetY y坐标
*/
public offset(offsetX: number, offsetY: number) {
this.x += offsetX;
this.y += offsetY;
return this;
}
/**
*
* @param value1
* @param value2
*/
public static union(value1: Rectangle, value2: Rectangle) {
let x = Math.min(value1.x, value2.x);
let y = Math.min(value1.y, value2.y);
return new Rectangle(x, y,
Math.max(value1.right, value2.right) - x,
Math.max(value1.bottom, value2.bottom) - y);
}
/**
*
* @param value1
* @param value2
*/
public static overlap(value1: Rectangle, value2: Rectangle): Rectangle {
let x = Math.max(value1.x, value2.x, 0);
let y = Math.max(value1.y, value2.y, 0);
return new Rectangle(x, y,
Math.max(Math.min(value1.right, value2.right) - x, 0),
Math.max(Math.min(value1.bottom, value2.bottom) - y, 0));
}
public calculateBounds(parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
rotation: number, width: number, height: number) {
if (rotation == 0) {
this.x = Math.trunc(parentPosition.x + position.x - origin.x * scale.x);
this.y = Math.trunc(parentPosition.y + position.y - origin.y * scale.y);
this.width = Math.trunc(width * scale.x);
this.height = Math.trunc(height * scale.y);
} else {
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
let worldPosX = parentPosition.x + position.x;
let worldPosY = parentPosition.y + position.y;
// 考虑到原点,将参考点设置为世界参考
Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, this._transformMat);
Matrix2D.createScale(scale.x, scale.y, this._tempMat);
this._transformMat = this._transformMat.multiply(this._tempMat);
Matrix2D.createRotation(rotation, this._tempMat);
this._transformMat = this._transformMat.multiply(this._tempMat);
Matrix2D.createTranslation(worldPosX, worldPosY, this._tempMat);
this._transformMat = this._transformMat.multiply(this._tempMat);
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
let topLeft = new Vector2(worldPosX, worldPosY);
let topRight = new Vector2(worldPosX + width, worldPosY);
let bottomLeft = new Vector2(worldPosX, worldPosY + height);
let bottomRight = new Vector2(worldPosX + width, worldPosY + height);
Vector2Ext.transformR(topLeft, this._transformMat, topLeft);
Vector2Ext.transformR(topRight, this._transformMat, topRight);
Vector2Ext.transformR(bottomLeft, this._transformMat, bottomLeft);
Vector2Ext.transformR(bottomRight, this._transformMat, bottomRight);
// 找出最小值和最大值,这样我们就可以计算出我们的边界框。
let minX = Math.trunc(Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
let maxX = Math.trunc(Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
let minY = Math.trunc(Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
let maxY = Math.trunc(Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
this.location = new Vector2(minX, minY);
this.width = Math.trunc(maxX - minX);
this.height = Math.trunc(maxY - minY);
}
}
/**
*
* @param deltaX
* @param deltaY
*/
public getSweptBroadphaseBounds(deltaX: number, deltaY: number) {
let broadphasebox = Rectangle.empty;
broadphasebox.x = deltaX > 0 ? this.x : this.x + deltaX;
broadphasebox.y = deltaY > 0 ? this.y : this.y + deltaY;
broadphasebox.width = deltaX > 0 ? deltaX + this.width : this.width - deltaX;
broadphasebox.height = deltaY > 0 ? deltaY + this.height : this.height - deltaY;
return broadphasebox;
}
/**
* true
* moveX和moveY将返回b1为避免碰撞而必须移动的移动量
* @param other
* @param moveX
* @param moveY
*/
public collisionCheck(other: Rectangle, moveX: Ref<number>, moveY: Ref<number>) {
moveX.value = moveY.value = 0;
let l = other.x - (this.x + this.width);
let r = (other.x + other.width) - this.x;
let t = (other.y - (this.y + this.height));
let b = (other.y + other.height) - this.y;
// 检验是否有碰撞
if (l > 0 || r < 0 || t > 0 || b < 0)
return false;
// 求两边的偏移量
moveX.value = Math.abs(l) < r ? l : r;
moveY.value = Math.abs(t) < b ? t : b;
// 只使用最小的偏移量
if (Math.abs(moveX.value) < Math.abs(moveY.value))
moveY.value = 0;
else
moveX.value = 0;
return true;
}
/**
*
* @param rectA
* @param rectB
* @returns
* /
*
* Vector2.Zero
*/
public static getIntersectionDepth(rectA: Rectangle, rectB: Rectangle): Vector2 {
// 计算半尺寸
let halfWidthA = rectA.width / 2;
let halfHeightA = rectA.height / 2;
let halfWidthB = rectB.width / 2;
let halfHeightB = rectB.height / 2;
// 计算中心
let centerA = new Vector2(rectA.left + halfWidthA, rectA.top + halfHeightA);
let centerB = new Vector2(rectB.left + halfWidthB, rectB.top + halfHeightB);
// 计算当前中心间的距离和最小非相交距离
let distanceX = centerA.x - centerB.x;
let distanceY = centerA.y - centerB.y;
let minDistanceX = halfWidthA + halfWidthB;
let minDistanceY = halfHeightA + halfHeightB;
// 如果我们根本不相交,则返回(0,0)
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY)
return Vector2.zero;
// 计算并返回交叉点深度
let depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
let depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return new Vector2(depthX, depthY);
}
/**
*
* @param other
*/
public equals(other: Rectangle) {
return this === other;
}
/**
*
*/
public getHashCode(): number {
return (Math.trunc(this.x) ^ Math.trunc(this.y) ^ Math.trunc(this.width) ^ Math.trunc(this.height));
}
public clone(): Rectangle {
return new Rectangle(this.x, this.y, this.width, this.height);
import { Vector2 } from './Vector2';
/**
*
*
*/
export class Rectangle {
/**
* X坐标
*/
public x: number = 0;
/**
* Y坐标
*/
public y: number = 0;
/**
*
*/
public width: number = 0;
/**
*
*/
public height: number = 0;
/**
*
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
*/
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
*
*/
public static get empty(): Rectangle {
return new Rectangle();
}
/**
* X坐标
*/
public get left(): number {
return this.x;
}
/**
* X坐标
*/
public get right(): number {
return this.x + this.width;
}
/**
* Y坐标
*/
public get top(): number {
return this.y;
}
/**
* Y坐标
*/
public get bottom(): number {
return this.y + this.height;
}
/**
*
*/
public get center(): Vector2 {
return new Vector2(this.x + this.width / 2, this.y + this.height / 2);
}
/**
*
*/
public get location(): Vector2 {
return new Vector2(this.x, this.y);
}
public set location(value: Vector2) {
this.x = value.x;
this.y = value.y;
}
/**
*
*/
public get size(): Vector2 {
return new Vector2(this.width, this.height);
}
public set size(value: Vector2) {
this.width = value.x;
this.height = value.y;
}
/**
*
* @param x X坐标
* @param y Y坐标
* @returns truefalse
*/
public contains(x: number, y: number): boolean;
/**
*
* @param point
* @returns truefalse
*/
public contains(point: Vector2): boolean;
public contains(xOrPoint: number | Vector2, y?: number): boolean {
if (typeof xOrPoint === 'number') {
return xOrPoint >= this.x && xOrPoint < this.right && y! >= this.y && y! < this.bottom;
} else {
return this.contains(xOrPoint.x, xOrPoint.y);
}
}
/**
*
* @param other
* @returns truefalse
*/
public intersects(other: Rectangle): boolean {
return other.left < this.right && this.left < other.right &&
other.top < this.bottom && this.top < other.bottom;
}
/**
*
* @param other
* @returns
*/
public intersection(other: Rectangle): Rectangle {
const x1 = Math.max(this.x, other.x);
const x2 = Math.min(this.right, other.right);
const y1 = Math.max(this.y, other.y);
const y2 = Math.min(this.bottom, other.bottom);
if (x2 >= x1 && y2 >= y1) {
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
return Rectangle.empty;
}
/**
*
* @param rect1
* @param rect2
* @returns
*/
public static union(rect1: Rectangle, rect2: Rectangle): Rectangle {
const x = Math.min(rect1.x, rect2.x);
const y = Math.min(rect1.y, rect2.y);
const right = Math.max(rect1.right, rect2.right);
const bottom = Math.max(rect1.bottom, rect2.bottom);
return new Rectangle(x, y, right - x, bottom - y);
}
/**
*
* @param offsetX X轴偏移量
* @param offsetY Y轴偏移量
*/
public offset(offsetX: number, offsetY: number): void {
this.x += offsetX;
this.y += offsetY;
}
/**
*
* @param horizontalAmount
* @param verticalAmount
*/
public inflate(horizontalAmount: number, verticalAmount: number): void {
this.x -= horizontalAmount;
this.y -= verticalAmount;
this.width += horizontalAmount * 2;
this.height += verticalAmount * 2;
}
/**
* 0
* @returns truefalse
*/
public isEmpty(): boolean {
return this.width === 0 && this.height === 0 && this.x === 0 && this.y === 0;
}
/**
*
* @param other
* @returns truefalse
*/
public equals(other: Rectangle): boolean {
return this.x === other.x && this.y === other.y &&
this.width === other.width && this.height === other.height;
}
/**
*
* @returns
*/
public clone(): Rectangle {
return new Rectangle(this.x, this.y, this.width, this.height);
}
}
-36
View File
@@ -1,36 +0,0 @@
module es {
/**
*
*/
export class SubpixelFloat {
/**
* SubpixelFloat
*/
public remainder: number = 0;
/**
* SubpixelFloat
*
* @param {number} amount -
* @returns {number}
*/
public update(amount: number): number {
// 将给定的像素数量添加到余数中
this.remainder += amount;
// 检查余数是否超过一个像素的大小
let motion = Math.trunc(this.remainder);
this.remainder -= motion;
// 返回整数部分作为更新后的值
return motion;
}
/**
* SubpixelFloat
*/
public reset(): void {
this.remainder = 0;
}
}
}
-34
View File
@@ -1,34 +0,0 @@
module es {
/**
*
*/
export class SubpixelVector2 {
/**
* x SubpixelFloat
*/
public _x: SubpixelFloat = new SubpixelFloat();
/**
* y SubpixelFloat
*/
public _y: SubpixelFloat = new SubpixelFloat();
/**
* SubpixelVector2
* @param {Vector2} amount -
*/
public update(amount: Vector2): void {
// 更新 x 和 y 坐标
amount.x = this._x.update(amount.x);
amount.y = this._y.update(amount.y);
}
/**
* SubpixelVector2
*/
public reset(): void {
this._x.reset();
this._y.reset();
}
}
}
+378 -452
View File
@@ -1,471 +1,397 @@
module es {
/** 2d 向量 */
export class Vector2 implements IEquatable<Vector2> {
public x: number = 0;
public y: number = 0;
/**
*
*
*/
export class Vector2 {
/**
* X坐标
*/
public x: number;
/**
* X和Y的二维向量
* @param x x坐标
* @param y y坐标
*/
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
/**
* Y坐标
*/
public y: number;
/**
*
* @param x X坐标0
* @param y Y坐标0
*/
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
/**
*
*/
public static get zero(): Vector2 {
return new Vector2(0, 0);
}
/**
* (1, 1)
*/
public static get one(): Vector2 {
return new Vector2(1, 1);
}
/**
* X向量(1, 0)
*/
public static get unitX(): Vector2 {
return new Vector2(1, 0);
}
/**
* Y向量(0, 1)
*/
public static get unitY(): Vector2 {
return new Vector2(0, 1);
}
/**
* (0, -1)
*/
public static get up(): Vector2 {
return new Vector2(0, -1);
}
/**
* (0, 1)
*/
public static get down(): Vector2 {
return new Vector2(0, 1);
}
/**
* (-1, 0)
*/
public static get left(): Vector2 {
return new Vector2(-1, 0);
}
/**
* (1, 0)
*/
public static get right(): Vector2 {
return new Vector2(1, 0);
}
/**
*
*/
public get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
*
*/
public get lengthSquared(): number {
return this.x * this.x + this.y * this.y;
}
/**
*
* @param x X坐标
* @param y Y坐标
*/
public set(x: number, y: number): Vector2 {
this.x = x;
this.y = y;
return this;
}
/**
*
* @param other
*/
public copyFrom(other: Vector2): Vector2 {
this.x = other.x;
this.y = other.y;
return this;
}
/**
*
*/
public clone(): Vector2 {
return new Vector2(this.x, this.y);
}
/**
*
* @param other
*/
public add(other: Vector2): Vector2 {
return new Vector2(this.x + other.x, this.y + other.y);
}
/**
*
* @param other
*/
public subtract(other: Vector2): Vector2 {
return new Vector2(this.x - other.x, this.y - other.y);
}
/**
*
* @param scalar
*/
public multiply(scalar: number): Vector2 {
return new Vector2(this.x * scalar, this.y * scalar);
}
/**
*
* @param scalar
*/
public divide(scalar: number): Vector2 {
return new Vector2(this.x / scalar, this.y / scalar);
}
/**
*
* @param other
*/
public dot(other: Vector2): number {
return this.x * other.x + this.y * other.y;
}
/**
* 2D中返回标量
* @param other
*/
public cross(other: Vector2): number {
return this.x * other.y - this.y * other.x;
}
/**
*
*/
public normalize(): Vector2 {
const length = this.length;
if (length === 0) {
return Vector2.zero;
}
return new Vector2(this.x / length, this.y / length);
}
public static get zero() {
return new Vector2(0, 0);
}
/**
*
* @param other
*/
public distanceTo(other: Vector2): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
public static get one() {
return new Vector2(1, 1);
}
/**
*
* @param other
*/
public distanceSquaredTo(other: Vector2): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
return dx * dx + dy * dy;
}
public static get unitX() {
return new Vector2(1, 0);
}
/**
*
* @param other
* @param tolerance
*/
public equals(other: Vector2, tolerance: number = 0.0001): boolean {
return Math.abs(this.x - other.x) < tolerance && Math.abs(this.y - other.y) < tolerance;
}
public static get unitY() {
return new Vector2(0, 1);
}
/**
*
*/
public angle(): number {
return Math.atan2(this.y, this.x);
}
public static get up() {
return new Vector2(0, -1);
}
/**
*
* @param radians
*/
public rotate(radians: number): Vector2 {
const cos = Math.cos(radians);
const sin = Math.sin(radians);
return new Vector2(
this.x * cos - this.y * sin,
this.x * sin + this.y * cos
);
}
public static get down() {
return new Vector2(0, 1);
}
/**
* 线
* @param other
* @param t (0-1)
*/
public lerp(other: Vector2, t: number): Vector2 {
return new Vector2(
this.x + (other.x - this.x) * t,
this.y + (other.y - this.y) * t
);
}
public static get left() {
return new Vector2(-1, 0);
}
/**
*
*/
public round(): Vector2 {
return new Vector2(Math.round(this.x), Math.round(this.y));
}
public static get right() {
return new Vector2(1, 0);
}
/**
*
*/
public floor(): Vector2 {
return new Vector2(Math.floor(this.x), Math.floor(this.y));
}
/**
*
* @param value1
* @param value2
*/
public static add(value1: Vector2, value2: Vector2) {
let result: Vector2 = Vector2.zero;
result.x = value1.x + value2.x;
result.y = value1.y + value2.y;
return result;
}
/**
*
*/
public ceil(): Vector2 {
return new Vector2(Math.ceil(this.x), Math.ceil(this.y));
}
/**
*
* @param value1
* @param value2
*/
public static divide(value1: Vector2, value2: Vector2) {
let result: Vector2 = Vector2.zero;
result.x = value1.x / value2.x;
result.y = value1.y / value2.y;
return result;
}
/**
*
*/
public toString(): string {
return `(${this.x.toFixed(2)}, ${this.y.toFixed(2)})`;
}
public static divideScaler(value1: Vector2, value2: number) {
let result: Vector2 = Vector2.zero;
result.x = value1.x / value2;
result.y = value1.y / value2;
return result;
}
// 静态方法
/**
*
* @param value1
* @param value2
*/
public static sqrDistance(value1: Vector2, value2: Vector2) {
return Math.pow(value1.x - value2.x, 2) + Math.pow(value1.y - value2.y, 2);
}
/**
*
* @param a A
* @param b B
*/
public static add(a: Vector2, b: Vector2): Vector2 {
return new Vector2(a.x + b.x, a.y + b.y);
}
/**
*
* @param value1
* @param min
* @param max
*/
public static clamp(value1: Vector2, min: Vector2, max: Vector2) {
return new Vector2(MathHelper.clamp(value1.x, min.x, max.x),
MathHelper.clamp(value1.y, min.y, max.y));
}
/**
*
* @param a A
* @param b B
*/
public static subtract(a: Vector2, b: Vector2): Vector2 {
return new Vector2(a.x - b.x, a.y - b.y);
}
/**
* Vector2线
* @param value1
* @param value2
* @param amount (0.0-1.0)
* @returns 线
*/
public static lerp(value1: Vector2, value2: Vector2, amount: number) {
return new Vector2(MathHelper.lerp(value1.x, value2.x, amount), MathHelper.lerp(value1.y, value2.y, amount));
}
/**
* Vector2线
* @param value1
* @param value2
* @param amount
* @returns
*/
public static lerpPrecise(value1: Vector2, value2: Vector2, amount: number) {
return new Vector2(MathHelper.lerpPrecise(value1.x, value2.x, amount),
MathHelper.lerpPrecise(value1.y, value2.y, amount));
}
/**
* Vector2Vector2包含了通过指定的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);
}
/**
* Vector2Matrix转换的指定法线
* @param normal
* @param matrix
*/
public static transformNormal(normal: Vector2, matrix: Matrix) {
return new Vector2((normal.x * matrix.m11) + (normal.y * matrix.m21),
(normal.x * matrix.m12) + (normal.y * matrix.m22));
}
/**
*
* @param value1
* @param value2
* @returns
*/
public static distance(vec1: Vector2, vec2: Vector2): number {
return Math.sqrt(Math.pow(vec1.x - vec2.x, 2) + Math.pow(vec1.y - vec2.y, 2));
}
/**
*
* @param from
* @param to
*/
public static angle(from: Vector2, to: Vector2): number {
from = from.normalize();
to = to.normalize();
return Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
}
/**
* Vector2
* @param value
* @returns
*/
public static negate(value: Vector2) {
value.x = -value.x;
value.y = -value.y;
return value;
}
/**
* vector和normalvector相对于normal的反射
* @param vector
* @param normal
* @returns
*/
public static reflect(vector: Vector2, normal: Vector2) {
let result: Vector2 = es.Vector2.zero;
// 计算向量与法线的点积,并将结果乘2
let val = 2 * ((vector.x * normal.x) + (vector.y * normal.y));
// 计算反射向量
result.x = vector.x - (normal.x * val);
result.y = vector.y - (normal.y * val);
return result;
}
/**
* Vector2
* @param value1
* @param value2
* @param amount
* @returns
*/
public static smoothStep(value1: Vector2, value2: Vector2, amount: number) {
return new Vector2(MathHelper.smoothStep(value1.x, value2.x, amount),
MathHelper.smoothStep(value1.y, value2.y, amount));
}
public setTo(x: number, y: number) {
this.x = x;
this.y = y;
}
public negate(): Vector2 {
return this.scale(-1);
}
/**
*
* @param value
*/
public add(v: Vector2): Vector2 {
return new Vector2(this.x + v.x, this.y + v.y);
}
public addEqual(v: Vector2): Vector2 {
this.x += v.x;
this.y += v.y;
return this;
}
/**
*
* @param value
*/
public divide(value: Vector2): Vector2 {
return new Vector2(this.x / value.x, this.y / value.y);
}
public divideScaler(value: number): Vector2 {
return new Vector2(this.x / value, this.y / value);
}
/**
*
* @param value
*/
public multiply(value: Vector2): Vector2 {
return new Vector2(value.x * this.x, value.y * this.y);
}
/**
*
* @param value
* @returns
*/
public multiplyScaler(value: number): Vector2 {
this.x *= value;
this.y *= value;
return this;
}
/**
* Vector2减去一个Vector2
* @param value Vector2
* @returns Vector2
*/
public sub(value: Vector2) {
return new Vector2(this.x - value.x, this.y - value.y);
}
public subEqual(v: Vector2): Vector2 {
this.x -= v.x;
this.y -= v.y;
return this;
}
public dot(v: Vector2): number {
return this.x * v.x + this.y * v.y;
}
/**
*
* @param size
* @returns
*/
public scale(size: number): Vector2 {
return new Vector2(this.x * size, this.y * size);
}
public scaleEqual(size: number): Vector2 {
this.x *= size;
this.y *= size;
return this;
}
public transform(matrix: Matrix2D): Vector2 {
return new Vector2(
this.x * matrix.m11 + this.y * matrix.m21 + matrix.m31,
this.x * matrix.m12 + this.y * matrix.m22 + matrix.m32
);
}
public normalize(): Vector2 {
const d = this.distance();
if (d > 0) {
return new Vector2(this.x / d, this.y / d);
} else {
return new Vector2(0, 1);
}
}
/**
* Vector2变成一个方向相同的单位向量
*/
public normalizeEqual(): Vector2 {
const d = this.distance();
if (d > 0) {
this.setTo(this.x / d, this.y / d);
return this;
} else {
this.setTo(0, 1);
return this;
}
}
public magnitude(): number {
return this.distance();
}
public distance(v?: Vector2): number {
if (!v) {
v = Vector2.zero;
}
return Math.sqrt(Math.pow(this.x - v.x, 2) + Math.pow(this.y - v.y, 2));
}
/**
* Vector2的平方长度
* @returns Vector2的平方长度
*/
public lengthSquared(): number {
return (this.x * this.x) + (this.y * this.y);
}
/**
*
* @returns
*/
public getLength(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
* X和Y值
*/
public round(): Vector2 {
return new Vector2(Math.round(this.x), Math.round(this.y));
}
/**
*
* @param left
* @param right
*/
public angleBetween(left: Vector2, right: Vector2) {
let one = left.sub(this);
let two = right.sub(this);
return Vector2Ext.angle(one, two);
}
public getDistance(other: Vector2): number {
return Math.sqrt(this.getDistanceSquared(other));
}
public getDistanceSquared(other: Vector2): number {
const dx = other.x - this.x;
const dy = other.y - this.y;
return dx * dx + dy * dy;
}
public isBetween(v1: Vector2, v2: Vector2): boolean {
const cross = v2.sub(v1).cross(this.sub(v1));
return Math.abs(cross) < Number.EPSILON && this.dot(v2.sub(v1)) >= 0 && this.dot(v1.sub(v2)) >= 0;
}
/**
*
* @param other
* @returns
*/
public cross(other: Vector2): number {
return this.x * other.y - this.y * other.x;
}
/**
* x轴之间的夹角
*/
public getAngle(): number {
return Math.atan2(this.y, this.x);
}
/**
*
* @param other
* @returns true false
*/
public equals(other: Vector2, tolerance: number = 0.001): boolean {
return Math.abs(this.x - other.x) <= tolerance && Math.abs(this.y - other.y) <= tolerance;
}
public isValid(): boolean {
return MathHelper.isValid(this.x) && MathHelper.isValid(this.y);
}
/**
* Vector2
* @param value1
* @param value2
* @returns
*/
public static min(value1: Vector2, value2: Vector2) {
return new Vector2(value1.x < value2.x ? value1.x : value2.x,
value1.y < value2.y ? value1.y : value2.y);
}
/**
* Vector2
* @param value1
* @param value2
* @returns
*/
public static max(value1: Vector2, value2: Vector2) {
return new Vector2(value1.x > value2.x ? value1.x : value2.x,
value1.y > value2.y ? value1.y : value2.y);
}
/**
* Vector2Hermite样条插值
* @param value1
* @param tangent1
* @param value2
* @param tangent2
* @param amount
* @returns
*/
public static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number) {
return new Vector2(MathHelper.hermite(value1.x, tangent1.x, value2.x, tangent2.x, amount),
MathHelper.hermite(value1.y, tangent1.y, value2.y, tangent2.y, amount));
}
public static unsignedAngle(from: Vector2, to: Vector2, round: boolean = true) {
from.normalizeEqual();
to.normalizeEqual();
const angle =
Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
return round ? Math.round(angle) : angle;
}
public static fromAngle(angle: number, magnitude: number = 1): Vector2 {
return new Vector2(magnitude * Math.cos(angle), magnitude * Math.sin(angle));
}
public clone(): Vector2 {
return new Vector2(this.x, this.y);
}
public copyFrom(source: Vector2): Vector2 {
this.x = source.x;
this.y = source.y;
return this;
/**
*
* @param a A
* @param b B或标量
*/
public static multiply(a: Vector2, b: Vector2 | number): Vector2 {
if (typeof b === 'number') {
return new Vector2(a.x * b, a.y * b);
} else {
return new Vector2(a.x * b.x, a.y * b.y);
}
}
/**
*
* @param a A
* @param b B或标量
*/
public static divide(a: Vector2, b: Vector2 | number): Vector2 {
if (typeof b === 'number') {
return new Vector2(a.x / b, a.y / b);
} else {
return new Vector2(a.x / b.x, a.y / b.y);
}
}
/**
*
* @param a A
* @param b B
*/
public static dot(a: Vector2, b: Vector2): number {
return a.x * b.x + a.y * b.y;
}
/**
*
* @param a A
* @param b B
*/
public static cross(a: Vector2, b: Vector2): number {
return a.x * b.y - a.y * b.x;
}
/**
*
* @param a A
* @param b B
*/
public static distance(a: Vector2, b: Vector2): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
*
* @param a A
* @param b B
*/
public static distanceSquared(a: Vector2, b: Vector2): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return dx * dx + dy * dy;
}
/**
* 线
* @param a
* @param b
* @param t (0-1)
*/
public static lerp(a: Vector2, b: Vector2, t: number): Vector2 {
return new Vector2(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
/**
*
* @param vector
*/
public static round(vector: Vector2): Vector2 {
return new Vector2(Math.round(vector.x), Math.round(vector.y));
}
/**
*
* @param radians
* @param length 1
*/
public static fromAngle(radians: number, length: number = 1): Vector2 {
return new Vector2(Math.cos(radians) * length, Math.sin(radians) * length);
}
/**
*
* @param vector
* @param normal
*/
public static reflect(vector: Vector2, normal: Vector2): Vector2 {
const dot = Vector2.dot(vector, normal);
return Vector2.subtract(vector, Vector2.multiply(normal, 2 * dot));
}
}
+5
View File
@@ -0,0 +1,5 @@
// 数学库导出
export { Vector2 } from './Vector2';
export { MathHelper } from './MathHelper';
export { Rectangle } from './Rectangle';
export { Edge } from './Edge';
-129
View File
@@ -1,129 +0,0 @@
module es {
/**
* 使itriggerlistener
*/
export class ColliderTriggerHelper {
private _entity: Entity;
/** 存储当前帧中发生的所有活动交点对 */
private _activeTriggerIntersections: PairSet<Collider> = new PairSet<Collider>();
/** 存储前一帧的交点对,这样我们就可以在移动这一帧后检测到退出 */
private _previousTriggerIntersections: PairSet<Collider> = new PairSet<Collider>();
private _tempTriggerList: ITriggerListener[] = [];
constructor(entity: Entity) {
this._entity = entity;
}
/**
* update应该在实体被移动后被调用Colllider重叠的ITriggerListeners
* Collider重叠的ITriggerListeners
*/
public update() {
const lateColliders = [];
// 对所有实体.colliders进行重叠检查,这些实体.colliders是触发器,与所有宽相碰撞器,无论是否触发器。
// 任何重叠都会导致触发事件
let colliders: Collider[] = this.getColliders();
if (colliders.length > 0) {
for (let i = 0; i < colliders.length; i++) {
let collider = colliders[i];
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, collider.bounds, collider.collidesWithLayers.value);
for (let j = 0; j < neighbors.length; j++) {
let neighbor = neighbors[j];
// 我们至少需要一个碰撞器作为触发器
if (!collider.isTrigger && !neighbor.isTrigger)
continue;
if (collider.overlaps(neighbor)) {
const pair = new Pair<Collider>(collider, neighbor);
// 如果我们的某一个集合中已经有了这个对子(前一个或当前的触发交叉点),就不要调用输入事件了
const shouldReportTriggerEvent = !this._activeTriggerIntersections.has(pair) &&
!this._previousTriggerIntersections.has(pair);
if (shouldReportTriggerEvent) {
if (neighbor.castSortOrder >= Collider.lateSortOrder) {
lateColliders.push(pair);
} else {
this.notifyTriggerListeners(pair, true);
}
}
this._activeTriggerIntersections.add(pair);
}
}
}
}
if (lateColliders.length > 0) {
for (let i = 0; i < lateColliders.length; i ++) {
const pair = lateColliders[i];
this.notifyTriggerListeners(pair, true);
}
}
this.checkForExitedColliders();
}
private getColliders() {
const colliders: Collider[] = [];
if (this._entity.components.buffer.length > 0)
for (let i = 0; i < this._entity.components.buffer.length; i ++) {
const component = this._entity.components.buffer[i];
if (component instanceof Collider) {
colliders.push(component);
}
}
return colliders;
}
private checkForExitedColliders() {
// 删除所有与此帧交互的触发器,留下我们退出的触发器
this._previousTriggerIntersections.except(this._activeTriggerIntersections);
const all = this._previousTriggerIntersections.all;
all.forEach(pair => {
this.notifyTriggerListeners(pair, false);
});
this._previousTriggerIntersections.clear();
// 添加所有当前激活的触发器
this._previousTriggerIntersections.union(this._activeTriggerIntersections);
// 清空活动集,为下一帧做准备
this._activeTriggerIntersections.clear();
}
private notifyTriggerListeners(collisionPair: Pair<Collider>, isEntering: boolean) {
TriggerListenerHelper.getITriggerListener(collisionPair.first.entity, this._tempTriggerList);
if (this._tempTriggerList.length > 0)
for (let i = 0; i < this._tempTriggerList.length; i++) {
const trigger = this._tempTriggerList[i];
if (isEntering) {
trigger.onTriggerEnter(collisionPair.second, collisionPair.first);
} else {
trigger.onTriggerExit(collisionPair.second, collisionPair.first);
}
this._tempTriggerList.length = 0;
if (collisionPair.second.entity) {
TriggerListenerHelper.getITriggerListener(collisionPair.second.entity, this._tempTriggerList);
if (this._tempTriggerList.length > 0)
for (let i = 0; i < this._tempTriggerList.length; i++) {
const trigger = this._tempTriggerList[i];
if (isEntering) {
trigger.onTriggerEnter(collisionPair.first, collisionPair.second);
} else {
trigger.onTriggerExit(collisionPair.first, collisionPair.second);
}
}
this._tempTriggerList.length = 0;
}
}
}
}
}
-236
View File
@@ -1,236 +0,0 @@
module es {
export enum PointSectors {
center = 0,
top = 1,
bottom = 2,
topLeft = 9,
topRight = 5,
left = 8,
right = 4,
bottomLeft = 10,
bottomRight = 6
}
export class Collisions {
public static lineToLine(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2): boolean {
const b = a2.sub(a1);
const d = b2.sub(b1);
const bDotDPerp = b.x * d.y - b.y * d.x;
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
if (bDotDPerp == 0)
return false;
const c = b1.sub(a1);
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1) {
return false;
}
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1) {
return false;
}
return true;
}
public static lineToLineIntersection(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2, intersection: Vector2 = es.Vector2.zero): boolean {
intersection.x = 0;
intersection.y = 0;
const b = a2.sub(a1);
const d = b2.sub(b1);
const bDotDPerp = b.x * d.y - b.y * d.x;
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
if (bDotDPerp == 0)
return false;
const c = b1.sub(a1);
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1)
return false;
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1)
return false;
const temp = a1.add(b.scale(t));
intersection.x = temp.x;
intersection.y = temp.y;
return true;
}
public static closestPointOnLine(lineA: Vector2, lineB: Vector2, closestTo: Vector2) {
const v = lineB.sub(lineA);
const w = closestTo.sub(lineA);
let t = w.dot(v) / v.dot(v);
t = MathHelper.clamp(t, 0, 1);
return lineA.add(v.scale(t));
}
public static circleToCircle(circleCenter1: Vector2, circleRadius1: number, circleCenter2: Vector2, circleRadius2: number): boolean {
return Vector2.sqrDistance(circleCenter1, circleCenter2) < (circleRadius1 + circleRadius2) * (circleRadius1 + circleRadius2);
}
public static circleToLine(circleCenter: Vector2, radius: number, lineFrom: Vector2, lineTo: Vector2): boolean {
return Vector2.sqrDistance(circleCenter, this.closestPointOnLine(lineFrom, lineTo, circleCenter)) < radius * radius;
}
public static circleToPoint(circleCenter: Vector2, radius: number, point: Vector2): boolean {
return Vector2.sqrDistance(circleCenter, point) < radius * radius;
}
public static rectToCircle(rect: Rectangle, cPosition: Vector2, cRadius: number): boolean {
// 检查矩形是否包含圆的中心点
if (this.rectToPoint(rect.x, rect.y, rect.width, rect.height, cPosition))
return true;
// 对照相关边缘检查圆圈
let edgeFrom: Vector2;
let edgeTo: Vector2;
const sector = this.getSector(rect.x, rect.y, rect.width, rect.height, cPosition);
if ((sector & PointSectors.top) !== 0) {
edgeFrom = new Vector2(rect.x, rect.y);
edgeTo = new Vector2(rect.x + rect.width, rect.y);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
if ((sector & PointSectors.bottom) !== 0) {
edgeFrom = new Vector2(rect.x, rect.y + rect.width);
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
if ((sector & PointSectors.left) !== 0) {
edgeFrom = new Vector2(rect.x, rect.y);
edgeTo = new Vector2(rect.x, rect.y + rect.height);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
if ((sector & PointSectors.right) !== 0) {
edgeFrom = new Vector2(rect.x + rect.width, rect.y);
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
return false;
}
/**
* 线
* @param rect -
* @param lineFrom - 线
* @param lineTo - 线
* @returns true false
*/
public static rectToLine(rect: Rectangle, lineFrom: Vector2, lineTo: Vector2): boolean {
// 获取起点和终点所在矩形的位置
const fromSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineFrom);
const toSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineTo);
// 起点或终点位于矩形内部
if (fromSector == PointSectors.center || toSector == PointSectors.center) {
return true;
}
// 起点和终点都在矩形外部的同一区域
if ((fromSector & toSector) != 0) {
return false;
}
// 到这里说明起点和终点分别在矩形的两个不同区域,需要检查线段是否与矩形的边相交
// 枚举起点和终点所在区域
const both = fromSector | toSector;
// 逐条检查矩形的四条边是否与线段相交
if ((both & PointSectors.top) != 0) {
if (this.lineToLine(
new Vector2(rect.x, rect.y),
new Vector2(rect.x + rect.width, rect.y),
lineFrom,
lineTo
)) {
return true;
}
}
if ((both & PointSectors.bottom) != 0) {
if (this.lineToLine(
new Vector2(rect.x, rect.y + rect.height),
new Vector2(rect.x + rect.width, rect.y + rect.height),
lineFrom,
lineTo
)) {
return true;
}
}
if ((both & PointSectors.left) != 0) {
if (this.lineToLine(
new Vector2(rect.x, rect.y),
new Vector2(rect.x, rect.y + rect.height),
lineFrom,
lineTo
)) {
return true;
}
}
if ((both & PointSectors.right) != 0) {
if (this.lineToLine(
new Vector2(rect.x + rect.width, rect.y),
new Vector2(rect.x + rect.width, rect.y + rect.height),
lineFrom,
lineTo
)) {
return true;
}
}
return false;
}
public static rectToPoint(rX: number, rY: number, rW: number, rH: number, point: Vector2) {
return point.x >= rX && point.y >= rY && point.x < rX + rW && point.y < rY + rH;
}
/**
* 使CohenSutherland算法
*
* :
* 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;
}
}
}
-202
View File
@@ -1,202 +0,0 @@
///<reference path="./RaycastHit.ts" />
module es {
export class Physics {
public static _spatialHash: SpatialHash;
/** 用于在全局范围内存储重力值的方便字段 */
public static gravity = new Vector2(0, -300);
/** 调用reset并创建一个新的SpatialHash时使用的单元格大小 */
public static spatialHashCellSize = 100;
/** 接受layerMask的所有方法的默认值 */
public static readonly allLayers: number = -1;
/**
* raycast是否检测配置为触发器的碰撞器
*/
public static raycastsHitTriggers: boolean = false;
/**
* 线/线
*/
public static raycastsStartInColliders = false;
public static debugRender: boolean = false;
/**
* raycast发生时分配它
*/
public static _hitArray: RaycastHit[] = [
new RaycastHit()
];
/**
*
*/
public static _colliderArray: Collider[] = [
null
];
public static reset() {
this._spatialHash = new SpatialHash(this.spatialHashCellSize);
this._hitArray[0].reset();
this._colliderArray[0] = null;
}
/**
* SpatialHash中移除所有碰撞器
*/
public static clear() {
this._spatialHash.clear();
}
/**
*
* @param center
* @param radius
* @param layerMask
*/
public static overlapCircle(center: Vector2, radius: number, layerMask: number = Physics.allLayers) {
this._colliderArray[0] = null;
this._spatialHash.overlapCircle(center, radius, this._colliderArray, layerMask);
return this._colliderArray[0];
}
/**
*
* @param center
* @param randius
* @param results
* @param layerMask
*/
public static overlapCircleAll(center: Vector2, radius: number, results: Collider[], layerMask: number = this.allLayers) {
return this._spatialHash.overlapCircle(
center,
radius,
results,
layerMask
);
}
/**
* boundsbroadphase检查!
* @param rect
* @param layerMask
*/
public static boxcastBroadphase(rect: Rectangle, layerMask: number = this.allLayers) {
return this._spatialHash.aabbBroadphase(rect, null, layerMask);
}
/**
* self
*
* @param collider
* @param rect
* @param layerMask
*/
public static boxcastBroadphaseExcludingSelf(collider: Collider, rect: Rectangle, layerMask = this.allLayers) {
return this._spatialHash.aabbBroadphase(rect, collider, layerMask);
}
/**
* collider.bounds (self)
* @param collider
* @param layerMask
*/
public static boxcastBroadphaseExcludingSelfNonRect(collider: Collider, layerMask = this.allLayers) {
let bounds = collider.bounds;
return this._spatialHash.aabbBroadphase(bounds, collider, layerMask);
}
/**
* collider.bounds deltaX/deltaY self
* @param collider
* @param deltaX
* @param deltaY
* @param layerMask
*/
public static boxcastBroadphaseExcludingSelfDelta(collider: Collider, deltaX: number, deltaY: number, layerMask: number = Physics.allLayers) {
let colliderBounds = collider.bounds;
let sweptBounds = colliderBounds.getSweptBroadphaseBounds(deltaX, deltaY);
return this._spatialHash.aabbBroadphase(sweptBounds, collider, layerMask);
}
/**
*
* @param collider
*/
public static addCollider(collider: Collider) {
Physics._spatialHash.register(collider);
}
/**
*
* @param collider
*/
public static removeCollider(collider: Collider) {
Physics._spatialHash.remove(collider);
}
/**
*
* @param collider
*/
public static updateCollider(collider: Collider) {
this._spatialHash.remove(collider);
this._spatialHash.register(collider);
}
/**
* layerMask匹配的碰撞器的第一次命中
* @param start
* @param end
* @param layerMask
*/
public static linecast(start: Vector2, end: Vector2, layerMask: number = this.allLayers, ignoredColliders: Set<Collider> = null): RaycastHit {
this._hitArray[0].reset();
Physics.linecastAll(
start,
end,
this._hitArray,
layerMask,
ignoredColliders
);
return this._hitArray[0];
}
/**
* hits数组
* @param start
* @param end
* @param hits
* @param layerMask
*/
public static linecastAll(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number = this.allLayers, ignoredColliders: Set<Collider> = null) {
return this._spatialHash.linecast(
start,
end,
hits,
layerMask,
ignoredColliders
);
}
/**
*
* @param rect
* @param layerMask
*/
public static overlapRectangle(rect: Rectangle, layerMask: number = Physics.allLayers) {
this._colliderArray[0] = null;
this._spatialHash.overlapRectangle(rect, this._colliderArray, layerMask);
return this._colliderArray[0];
}
/**
*
* @param rect
* @param results
* @param layerMask
*/
public static overlapRectangleAll(rect: Rectangle, results: Collider[], layerMask: number = Physics.allLayers) {
if (results.length == 0) {
console.warn("传入了一个空的结果数组。不会返回任何结果");
return 0;
}
return this._spatialHash.overlapRectangle(rect, results, layerMask);
}
}
}
-28
View File
@@ -1,28 +0,0 @@
module es {
/**
* 线(线)线线
*/
export class Ray2D {
public get start(): Vector2 {
return this._start;
}
public get direction(): Vector2 {
return this._direction;
}
public get end(): Vector2 {
return this._end;
}
constructor(pos: Vector2, end: Vector2) {
this._start = pos.clone();
this._end = end.clone();
this._direction = this._end.sub(this._start);
}
private _start: Vector2;
private _direction: Vector2;
private _end: Vector2;
}
}
-77
View File
@@ -1,77 +0,0 @@
module es {
export class RaycastHit {
/**
* 线
*/
public collider: Collider;
/**
* 沿线
*/
public fraction: number = 0;
/**
* 线
*/
public distance: number = 0;
/**
* 线
*/
public point: Vector2 = Vector2.zero;
/**
* 线
*/
public normal: Vector2 = Vector2.zero;
/**
* 使
*/
public centroid: Vector2;
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2) {
this.collider = collider;
this.fraction = fraction;
this.distance = distance;
this.point = point;
this.centroid = Vector2.zero;
}
public setAllValues(collider: Collider, fraction: number, distance: number, point: Vector2, normal: Vector2) {
this.collider = collider;
this.fraction = fraction;
this.distance = distance;
this.point = point;
this.normal = normal;
}
public setValues(fraction: number, distance: number, point: Vector2, normal: Vector2) {
this.fraction = fraction;
this.distance = distance;
this.point = point;
this.normal = normal;
}
public reset() {
this.collider = null;
this.fraction = this.distance = 0;
}
public clone(): RaycastHit {
const hit = new RaycastHit();
hit.setAllValues(
this.collider,
this.fraction,
this.distance,
this.point,
this.normal
);
return hit;
}
public toString() {
return `[RaycastHit] fraction: ${this.fraction}, distance: ${this.distance}, normal: ${this.normal}, centroid: ${this.centroid}, point: ${this.point}`;
}
}
}
-136
View File
@@ -1,136 +0,0 @@
///<reference path="./Polygon.ts" />
module es {
/**
* SAT碰撞检查时28
*/
export class Box extends Polygon {
public width: number;
public height: number;
constructor(width: number, height: number) {
super(Box.buildBox(width, height), true);
this.width = width;
this.height = height;
}
/**
*
* @param width
* @param height
*/
private static buildBox(width: number, height: number): Vector2[] {
// 我们在(0,0)的中心周围创建点
let halfWidth = width / 2;
let halfHeight = height / 2;
let verts = new Array(4);
verts[0] = new Vector2(-halfWidth, -halfHeight);
verts[1] = new Vector2(halfWidth, -halfHeight);
verts[2] = new Vector2(halfWidth, halfHeight);
verts[3] = new Vector2(-halfWidth, halfHeight);
return verts;
}
/**
* /
* @param width
* @param height
*/
public updateBox(width: number, height: number) {
this.width = width;
this.height = height;
// 我们在(0,0)的中心周围创建点
let halfWidth = width / 2;
let halfHeight = height / 2;
this.points[0] = new Vector2(-halfWidth, -halfHeight);
this.points[1] = new Vector2(halfWidth, -halfHeight);
this.points[2] = new Vector2(halfWidth, halfHeight);
this.points[3] = new Vector2(-halfWidth, halfHeight);
for (let i = 0; i < this.points.length; i++)
this._originalPoints[i] = this.points[i];
}
public getEdges(): Array<Line> {
const edges = [];
for (let i = 0; i < this.points.length; i++) {
const j = (i + 1) % this.points.length;
edges.push(new Line(this.points[i], this.points[j]));
}
return edges;
}
public overlaps(other: Shape) {
// 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测
if (this.isUnrotated) {
if (other instanceof Box && other.isUnrotated)
return this.bounds.intersects(other.bounds);
if (other instanceof Circle)
return Collisions.rectToCircle(this.bounds, other.position, other.radius);
}
return super.overlaps(other);
}
public collidesWithShape(other: Shape, result: Out<CollisionResult>): boolean {
// 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测
if (other instanceof Box && (other as Box).isUnrotated) {
return ShapeCollisionsBox.boxToBox(this, other, result);
}
// TODO: 让 minkowski 运行于 cricleToBox
return super.collidesWithShape(other, result);
}
public containsPoint(point: Vector2) {
if (this.isUnrotated)
return this.bounds.contains(point.x, point.y);
return super.containsPoint(point);
}
public pointCollidesWithShape(point: Vector2, result: Out<CollisionResult>): boolean {
if (this.isUnrotated)
return ShapeCollisionsPoint.pointToBox(point, this, result);
return super.pointCollidesWithShape(point, result);
}
public getFurthestPoint(normal: Vector2): Vector2 {
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
let furthestPoint = new Vector2(halfWidth, halfHeight);
let dotProduct = furthestPoint.dot(normal);
let tempPoint = new Vector2(-halfWidth, halfHeight);
let tempDotProduct = tempPoint.dot(normal);
if (tempDotProduct > dotProduct) {
furthestPoint.copyFrom(tempPoint);
dotProduct = tempDotProduct;
}
tempPoint.setTo(-halfWidth, -halfHeight);
tempDotProduct = tempPoint.dot(normal);
if (tempDotProduct > dotProduct) {
furthestPoint.copyFrom(tempPoint);
dotProduct = tempDotProduct;
}
tempPoint.setTo(halfWidth, -halfHeight);
tempDotProduct = tempPoint.dot(normal);
if (tempDotProduct > dotProduct) {
furthestPoint.copyFrom(tempPoint);
}
return furthestPoint;
}
}
}
-89
View File
@@ -1,89 +0,0 @@
///<reference path="./Shape.ts" />
module es {
export class Circle extends Shape {
public radius: number;
public _originalRadius: number;
constructor(radius: number) {
super();
this.radius = radius;
this._originalRadius = radius;
}
public recalculateBounds(collider: Collider) {
// 如果我们没有旋转或不关心TRS我们使用localOffset作为中心
this.center = collider.localOffset;
if (collider.shouldColliderScaleAndRotateWithTransform) {
// 我们只将直线缩放为一个圆,所以我们将使用最大值
const scale = collider.entity.transform.scale;
const hasUnitScale = scale.x === 1 && scale.y === 1;
const maxScale = Math.max(scale.x, scale.y);
this.radius = this._originalRadius * maxScale;
if (collider.entity.transform.rotation !== 0) {
// 为了处理偏移原点的旋转,我们只需要将圆心围绕(0,0)在一个圆上移动,我们的偏移量就是0角
const offsetAngle = Math.atan2(collider.localOffset.y, collider.localOffset.x) * MathHelper.Rad2Deg;
const offsetLength = hasUnitScale ? collider._localOffsetLength : collider.localOffset.multiply(collider.entity.transform.scale).magnitude();
this.center = MathHelper.pointOnCircle(Vector2.zero, offsetLength, collider.entity.transform.rotation + offsetAngle);
}
}
this.position = collider.transform.position.add(this.center);
this.bounds = new Rectangle(this.position.x - this.radius, this.position.y - this.radius, this.radius * 2, this.radius * 2);
}
public overlaps(other: Shape) {
const result = new Out<CollisionResult>();
if (other instanceof Box && (other as Box).isUnrotated)
return Collisions.rectToCircle(other.bounds, this.position, this.radius);
if (other instanceof Circle)
return Collisions.circleToCircle(this.position, this.radius, other.position, (other as Circle).radius);
if (other instanceof Polygon)
return ShapeCollisionsCircle.circleToPolygon(this, other, result);
throw new Error(`overlaps of circle to ${other} are not supported`);
}
public collidesWithShape(other: Shape, result: Out<CollisionResult>): boolean {
if (other instanceof Box && (other as Box).isUnrotated) {
return ShapeCollisionsCircle.circleToBox(this, other, result);
}
if (other instanceof Circle) {
return ShapeCollisionsCircle.circleToCircle(this, other, result);
}
if (other instanceof Polygon) {
return ShapeCollisionsCircle.circleToPolygon(this, other, result);
}
throw new Error(`Collisions of Circle to ${other} are not supported`);
}
public collidesWithLine(start: Vector2, end: Vector2, hit: Out<RaycastHit>): boolean {
return ShapeCollisionsLine.lineToCircle(start, end, this, hit);
}
public getPointAlongEdge(angle: number): Vector2 {
return new Vector2(
this.position.x + this.radius * Math.cos(angle),
this.position.y + this.radius * Math.sin(angle)
);
}
/**
*
* @param point
*/
public containsPoint(point: Vector2) {
return (point.sub(this.position)).lengthSquared() <= this.radius * this.radius;
}
public pointCollidesWithShape(point: Vector2, result: Out<CollisionResult>): boolean {
return ShapeCollisionsPoint.pointToCircle(point, this, result);
}
}
}
@@ -1,72 +0,0 @@
module es {
export class CollisionResult {
/**
*
*/
public collider: Collider;
/**
*
*/
public normal: Vector2 = Vector2.zero;
/**
*
*/
public minimumTranslationVector: Vector2 = Vector2.zero;
/**
* 使!ShapeCollisions切割类!
*/
public point: Vector2 = Vector2.zero;
public reset() {
this.collider = null;
this.normal.setTo(0, 0);
this.minimumTranslationVector.setTo(0, 0);
if (this.point) {
this.point.setTo(0, 0);
}
}
public cloneTo(cr: CollisionResult) {
cr.collider = this.collider;
cr.normal.setTo(this.normal.x, this.normal.y);
cr.minimumTranslationVector.setTo(
this.minimumTranslationVector.x,
this.minimumTranslationVector.y
);
if (this.point) {
if (!cr.point) {
cr.point = new Vector2(0, 0);
}
cr.point.setTo(this.point.x, this.point.y);
}
}
/**
* 沿
* @param deltaMovement -
*/
public removeHorizontalTranslation(deltaMovement: Vector2) {
// 如果运动方向和法线方向不在同一方向或者移动量为0且法线方向不为0,则需要修复
if (Math.sign(this.normal.x) !== Math.sign(deltaMovement.x) || (deltaMovement.x === 0 && this.normal.x !== 0)) {
// 获取响应距离
const responseDistance = this.minimumTranslationVector.magnitude();
// 计算需要修复的位移
const fix = responseDistance / this.normal.y;
// 如果法线方向不是完全水平或垂直,并且修复距离小于移动向量的3倍,则修复距离
if (Math.abs(this.normal.x) != 1 && Math.abs(fix) < Math.abs(deltaMovement.y * 3)) {
this.minimumTranslationVector = new Vector2(0, -fix);
}
}
}
public invertResult() {
this.minimumTranslationVector = this.minimumTranslationVector.negate();
this.normal = this.normal.negate();
}
public toString() {
return `[CollisionResult] normal: ${this.normal}, minimumTranslationVector: ${this.minimumTranslationVector}`;
}
}
}
-55
View File
@@ -1,55 +0,0 @@
module es {
export class Line {
public start: Vector2;
public end: Vector2;
constructor(start: Vector2, end: Vector2) {
this.start = start.clone();
this.end = end.clone();
}
public get direction(): Vector2 {
return this.end.sub(this.start).normalize();
}
public getNormal(): Vector2 {
const angle = this.direction.getAngle() - Math.PI / 2;
return new Vector2(Math.cos(angle), Math.sin(angle));
}
public getDirection(out: Vector2) {
return out.copyFrom(this.end).sub(this.start).normalize();
}
public getLength() {
return this.start.getDistance(this.end);
}
public getLengthSquared() {
return this.start.getDistanceSquared(this.end);
}
public distanceToPoint(normal: Vector2, center: Vector2): number {
return Math.abs((this.end.y - this.start.y) * normal.x - (this.end.x - this.start.x) * normal.y + this.end.x * this.start.y - this.end.y * this.start.x) / (2 * normal.magnitude());
}
public getFurthestPoint(direction: Vector2): Vector2 {
const d1 = this.start.dot(direction);
const d2 = this.end.dot(direction);
return d1 > d2 ? this.start : this.end;
}
public getClosestPoint(point: Vector2, out: Vector2) {
const delta = out.copyFrom(this.end).sub(this.start);
const t = (point.sub(this.start)).dot(delta) / delta.lengthSquared();
if (t < 0) {
return out.copyFrom(this.start);
} else if (t > 1) {
return out.copyFrom(this.end);
}
return out.copyFrom(delta).multiplyScaler(t).add(this.start);
}
}
}
-325
View File
@@ -1,325 +0,0 @@
///<reference path="./Shape.ts" />
module es {
/**
*
*/
export class Polygon extends Shape {
/**
*
*
*/
public points: Vector2[];
public _areEdgeNormalsDirty = true;
/**
*
*/
public _originalPoints: Vector2[];
public _polygonCenter: Vector2;
/**
* box碰撞
*/
public isBox: boolean;
public isUnrotated: boolean = true;
/**
*
* /0 0
* @param points
* @param isBox
*/
constructor(points: Vector2[], isBox?: boolean) {
super();
this.setPoints(points);
this.isBox = isBox;
}
public create(vertCount: number, radius: number) {
Polygon.buildSymmetricalPolygon(vertCount, radius);
}
public _edgeNormals: Vector2[];
/**
* 线SAT碰撞检测squareRoots
* box只有两个边缘
*/
public get edgeNormals() {
if (this._areEdgeNormalsDirty)
this.buildEdgeNormals();
return this._edgeNormals;
}
/**
* 线
* @param points
*/
public setPoints(points: Vector2[]) {
this.points = points;
this.recalculateCenterAndEdgeNormals();
this._originalPoints = [];
this.points.forEach(p => {
this._originalPoints.push(p.clone());
});
}
/**
*
*
*/
public recalculateCenterAndEdgeNormals() {
this._polygonCenter = Polygon.findPolygonCenter(this.points);
this._areEdgeNormalsDirty = true;
}
/**
* 线
* edgeNormals getter惰性创建和更新
*/
public buildEdgeNormals() {
// 对于box 我们只需要两条边,因为另外两条边是平行的
let totalEdges = this.isBox ? 2 : this.points.length;
if (this._edgeNormals == undefined || this._edgeNormals.length != totalEdges)
this._edgeNormals = new Array(totalEdges);
let p2: Vector2;
for (let i = 0; i < totalEdges; i++) {
let p1 = this.points[i];
if (i + 1 >= this.points.length)
p2 = this.points[0];
else
p2 = this.points[i + 1];
let perp = Vector2Ext.perpendicular(p1, p2);
Vector2Ext.normalize(perp);
this._edgeNormals[i] = perp;
}
}
/**
* (n角形)
* @param vertCount
* @param radius
*/
public static buildSymmetricalPolygon(vertCount: number, radius: number) {
const verts = new Array(vertCount);
for (let i = 0; i < vertCount; i++) {
const a = 2 * Math.PI * (i / vertCount);
verts[i] = new Vector2(Math.cos(a) * radius, Math.sin(a) * radius);
}
return verts;
}
/**
*
* @param points
*/
public static recenterPolygonVerts(points: Vector2[]) {
const center = this.findPolygonCenter(points);
for (let i = 0; i < points.length; i++)
points[i] = points[i].sub(center);
}
/**
*
* @param points
*/
public static findPolygonCenter(points: Vector2[]) {
let x = 0, y = 0;
for (let i = 0; i < points.length; i++) {
x += points[i].x;
y += points[i].y;
}
return new Vector2(x / points.length, y / points.length);
}
/**
* climbing算法
* @param points
* @param direction
*/
public static getFarthestPointInDirection(points: Vector2[], direction: Vector2): Vector2 {
let index = 0;
let maxDot = points[index].dot(direction);
for (let i = 1; i < points.length; i++) {
let dot = points[i].dot(direction);
if (dot > maxDot) {
maxDot = dot;
index = i;
}
}
return points[index];
}
/**
*
* 线
* (-.)
* @param points
* @param point
* @param distanceSquared
* @param edgeNormal
*/
public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2): {
distanceSquared: number;
edgeNormal: Vector2;
closestPoint: Vector2
} {
const res = {
distanceSquared: Number.MAX_VALUE,
edgeNormal: Vector2.zero,
closestPoint: Vector2.zero,
};
let tempDistanceSquared = 0;
for (let i = 0; i < points.length; i++) {
let j = i + 1;
if (j === points.length)
j = 0;
const closest = ShapeCollisionsCircle.closestPointOnLine(points[i], points[j], point);
tempDistanceSquared = Vector2.sqrDistance(point, closest);
if (tempDistanceSquared < res.distanceSquared) {
res.distanceSquared = tempDistanceSquared;
res.closestPoint = closest;
// 求直线的法线
const line = points[j].sub(points[i]);
res.edgeNormal.x = line.y;
res.edgeNormal.y = -line.x;
}
}
res.edgeNormal = res.edgeNormal.normalize();
return res;
}
/**
*
* @param radians
* @param originalPoints
* @param rotatedPoints
*/
public static rotatePolygonVerts(radians: number, originalPoints: Vector2[], rotatedPoints: Vector2[]) {
let cos = Math.cos(radians);
let sin = Math.sin(radians);
for (let i = 0; i < originalPoints.length; i++) {
let position = originalPoints[i];
rotatedPoints[i] = new Vector2(position.x * cos + position.y * -sin, position.x * sin + position.y * cos);
}
}
public recalculateBounds(collider: Collider) {
this.center = collider.localOffset;
if (collider.shouldColliderScaleAndRotateWithTransform) {
let hasUnitScale = true;
const tempMat = new Matrix2D();
const combinedMatrix = new Matrix2D();
Matrix2D.createTranslation(-this._polygonCenter.x, -this._polygonCenter.y, combinedMatrix);
const scale = collider.entity.transform.scale;
if (!scale.equals(Vector2.one)) {
Matrix2D.createScale(scale.x, scale.y, tempMat);
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
hasUnitScale = false;
const scaledOffset = collider.localOffset.multiply(scale);
this.center = scaledOffset;
}
const rotation = collider.entity.transform.rotationDegrees;
if (rotation !== 0) {
const offsetLength = hasUnitScale ? collider._localOffsetLength : collider.localOffset.multiply(scale).magnitude();
const offsetAngle = Math.atan2(collider.localOffset.y * scale.y, collider.localOffset.x * scale.x) * MathHelper.Rad2Deg;
this.center = MathHelper.pointOnCircle(Vector2.zero, offsetLength, rotation + offsetAngle);
Matrix2D.createRotation(MathHelper.Deg2Rad * rotation, tempMat);
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
}
Matrix2D.createTranslation(this._polygonCenter.x + collider.transform.position.x + this.center.x, this._polygonCenter.y + collider.transform.position.y + this.center.y, tempMat);
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
this.points = this._originalPoints.map(p => p.transform(combinedMatrix));
this.isUnrotated = rotation === 0;
if (collider._isRotationDirty) this._areEdgeNormalsDirty = true;
}
this.position = collider.transform.position.add(this.center);
this.bounds = Rectangle.rectEncompassingPoints(this.points).offset(this.position.x, this.position.y);
}
public overlaps(other: Shape) {
let result = new Out<CollisionResult>();
if (other instanceof Polygon)
return ShapeCollisionsPolygon.polygonToPolygon(this, other, result);
if (other instanceof Circle) {
if (ShapeCollisionsCircle.circleToPolygon(other, this, result)) {
result.value.invertResult();
return true;
}
return false;
}
throw new Error(`overlaps of Pologon to ${other} are not supported`);
}
public collidesWithShape(other: Shape, result: Out<CollisionResult>): boolean {
if (other instanceof Polygon) {
return ShapeCollisionsPolygon.polygonToPolygon(this, other, result);
}
if (other instanceof Circle) {
if (ShapeCollisionsCircle.circleToPolygon(other, this, result)) {
result.value.invertResult();
return true;
}
return false;
}
throw new Error(`overlaps of Polygon to ${other} are not supported`);
}
public collidesWithLine(start: Vector2, end: Vector2, hit: Out<RaycastHit>): boolean {
return ShapeCollisionsLine.lineToPoly(start, end, this, hit);
}
/**
* 线
*
* @param point
*/
public containsPoint(point: Vector2) {
// 将点归一化到多边形坐标空间中
point = point.sub(this.position);
let isInside = false;
for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) {
if (((this.points[i].y > point.y) !== (this.points[j].y > point.y)) &&
(point.x < (this.points[j].x - this.points[i].x) * (point.y - this.points[i].y) / (this.points[j].y - this.points[i].y) +
this.points[i].x)) {
isInside = !isInside;
}
}
return isInside;
}
public pointCollidesWithShape(point: Vector2, result: Out<CollisionResult>): boolean {
return ShapeCollisionsPoint.pointToPoly(point, this, result);
}
}
}

Some files were not shown because too many files have changed in this diff Show More