文档更新

This commit is contained in:
yhh
2023-03-14 11:22:09 +08:00
parent caa3ffc8f5
commit 78e0b09c7a
8 changed files with 406 additions and 244 deletions

View File

@@ -1,7 +1,7 @@
ecs-framework 的目标是成为功能强大的框架。它为您构建游戏提供了坚实的基础。它包括的许多功能包括: ecs-framework 的目标是成为功能强大的框架。它为您构建游戏提供了坚实的基础。它包括的许多功能包括:
- 完整的场景/实体/组件系统 - 完整的场景/实体/组件系统
- SpatialHash用于超快速的广相物理学查找。您永远不会看到它,因为它在幕后起作用,但是您仍然会喜欢它,因为它可以通过射线广播或重叠检查迅速找到您附近的所有事物 - SpatialHash是一种空间散列数据结构用于加速2D物理引擎的碰撞检测它能够将物体分割为多个小区域并快速查询每个区域内包含的物体从而大幅度提高碰撞检测的效率
- AABB圆和多边形碰撞/触发检测 - AABB圆和多边形碰撞/触发检测
- 高效的协程可在多个帧或动画定时中分解大型任务Core.startCoroutine - 高效的协程可在多个帧或动画定时中分解大型任务Core.startCoroutine
- 通过Astar和广度优先搜索提供寻路支持以查找图块地图或您自己的自定义格式 ( 参见 https://github.com/esengine/ecs-astar ) - 通过Astar和广度优先搜索提供寻路支持以查找图块地图或您自己的自定义格式 ( 参见 https://github.com/esengine/ecs-astar )
@@ -24,51 +24,83 @@ ecs-framework 的目标是成为功能强大的框架。它为您构建游戏提
## Scene/Entity/Component ## Scene/Entity/Component
框架的大部分围绕着实体组件系统ECS。ECS与您可能使用过的任何其他ECS均不同所以我为您再以下详细介绍 Scene表示游戏场景是所有实体组件的容器Entity表示游戏场景中的实体是组件的容器Component表示游戏实体中的组件包含实体的具体行为逻辑
### Scene ### Scene
ECS的根源。可以将场景视为游戏的不同部分在适当的时间调用它们的方法。您也可以使用场景通过findEntity和findEntitiesByTag方法定位实体。 这是一个ECSEntity-Component-System框架的场景类用于管理游戏中的实体和处理器。它具有以下特点
场景可以包含一种称为场景组件的特殊类型的组件。 SceneComponent通过add / get / removeSceneComponent方法进行管理。可以将场景组件视为简化组件。它包含少量可重写的生命周期方法onEnabled / onDisabled / update / onRemovedFromScene。当您需要一个位于场景级别但不需要实体容器的对象时可以使用这些对象。 - 维护了一个实体列表和一个实体处理器列表,以便在游戏中轻松添加、更新和删除实体。
- 具有实体、组件和处理器的基本结构,可帮助您组织代码并实现分离的关注点。
- 可以添加和删除场景组件,这是一个特殊类型的组件,可用于实现场景范围的逻辑。
- 通过使用实体处理器,可以轻松地处理实体的更新和渲染。
- 可以搜索场景中的实体、组件和处理器,并根据需要添加或删除它们。
- 可以为实体分配唯一的标识符,以便在处理实体时轻松地跟踪它们。
### Entity ### Entity
将实体添加到场景中/从场景中删除,并由场景进行管理。 您可以子类化Entity也可以只创建一个Entity实例然后向其中添加任何必需的组件通过addComponent然后通过getComponent检索。 在实体的最基本层次上,可以将其视为组件的容器。 实体具有一系列在整个生命周期中的不同时间被场景调用的方法 Entity 是ECS中的一个基础概念它代表了游戏中的实体。每个 Entity 都有唯一的 ID 和名称,可以在一个 Scene 中被创建、添加、删除和管理
实体生命周期方法: 在一个 Entity 中,可以添加多个 Component 来实现不同的功能。例如Transform Component 用来表示实体的位置、旋转和缩放等变换属性,其他自定义的 Component 则可以实现各种具体的游戏逻辑。
- onAddedToScene在将所有未决的实体更改提交后将实体添加到场景中时调用
- onRemovedFromScene当实体从场景中移除时调用
- update只要启用了实体就会每帧调用
实体上的一些关键/重要属性如下: 在 Entity 中,可以通过以下方法来管理 Component
- updateOrder控制实体的顺序。 这会影响在每个实体上调用更新的顺序以及标签列表的顺序 - createComponent(componentType: new (...args) => T): T 用来创建并返回一个指定类型的 Component 实例
- tag随便使用它。 以后可以使用它在场景中查询具有特定标签Scene.findEntitiesByTag的所有实体 - addComponent<T extends Component>(component: T): T 用来添加一个已有的 Component 实例
- updateInterval指定应多久调用一次此Entities更新方法。 1表示每帧2表示每两帧依此类推 - 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 实例。
最后,在 Entity 中还可以通过 Tween 类来实现各种动画效果,例如 tweenPositionTo()、tweenScaleTo() 和 tweenRotationDegreesTo() 等方法。
以下是Entity类中一些关键和重要的属性
- scene实体所属的场景对象。
- name实体的名称。
- id实体的唯一标识符。
- transform实体的变换组件。
- components实体的组件列表。
- updateInterval实体更新间隔用于控制实体的更新频率。
- componentBits用于标记实体拥有哪些组件。
这些属性是Entity类中非常重要的它们可以用来操作和控制实体的行为和状态。其中transform和components属性是实体最为关键的组成部分前者用于操作实体的位置、旋转和缩放等变换信息后者用于添加、删除、查询和更新实体的组件信息。通过这些属性我们可以非常方便地构建出自己的游戏对象并实现一些基本的功能如移动、碰撞、动画、音效等。
### Component ### Component
组件添加到实体并由实体管理。 它们构成了您游戏的重点,并且基本上是可重用的代码块,这些代码决定了实体的行为方式 这是一个用于构建实体组件系统的基本组件类,所有其他组件都应该从这个抽象类派生。每个组件都可以与一个实体相关联,可以通过实体来访问其它组件
组件生命周期方法: 重要属性:
- initialize在创建组件并分配Entity字段但在onAddedToEntity之前调用 - id: 组件的唯一标识符。
- onAddedToEntity在将所有未决组件更改提交后将组件添加到实体时调用 - entity: 附加此组件的实体。
- onRemovedFromEntity当组件从其实体中移除时调用。 在这里进行所有清理。 重要方法:
- onEntityPositionChanged在实体位置更改时调用。 这使组件可以知道它们是由于父实体移动而移动的。
- update只要启用了实体和组件并且组件实现IUpdatable就会每帧调用 - addComponent<T extends Component>(component: T): T将指定的组件添加到此组件所在的实体中。
- onEnabled在启用父实体或组件时调用 - getComponent<T extends Component>(type: new (...args: any[]) => T): T获取指定类型的组件。
- onDisabled在禁用父实体或组件时调用 - getComponents(typeName: any, componentList?: any[]): any[]:获取指定类型的组件数组。
- hasComponent(type: new (...args: any[]) => Component): boolean判断实体是否包含指定类型的组件。
- removeComponent(component?: Component): void从此组件所在的实体中删除指定的组件如果未指定组件则删除此组件本身。
此外,组件还具有一些生命周期方法,例如 initialize()、onAddedToEntity()、onRemovedFromEntity()、onEntityTransformChanged()、onEnabled()、onDisabled() 等,这些方法可以根据需要在派生类中进行重写,以便在实体上添加或移除组件时执行相应的操作。
## Debug ## Debug
Debug类提供日志记录。 Insist类提供各种断言条件。 您可以在整个代码中自由使用它们。 这是一个静态类 Debug它包含了一些方法用于打印调试信息。其中包含的方法如下
- 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。
该类的优点是它提供了一种统一的调试信息输出方式,可以帮助开发者更方便地输出调试信息,以便在调试时更快地定位问题。缺点是它的功能比较单一,只能输出调试信息,不能对调试信息进行更加复杂的处理。
## Flags ## Flags
您是否喜欢将大量数据打包为单个number的功能但讨厌处理该数据的语法 Flags类可以为您提供帮助。 它包括用于处理number的辅助方法以检查是否设置了位以及设置/取消设置了它们。 处理Collider.physicsLayer非常方便。 这是一个静态工具类 Flags提供了一些位标志操作的方法
### 示例地址 - isFlagSet检查位标志是否已在数值中设置该标志未移位
- isUnshiftedFlagSet检查位标志是否在数值中设置该标志已移位
#### [laya-demo](https://github.com/esengine/ecs-laya-demo) - setFlagExclusive设置数值标志位移除所有已经设置的标志
#### [egret-demo](https://github.com/esengine/ecs-egret-demo) - setFlag设置标志位
- unsetFlag取消标志位
- invertFlags反转数值集合位
- binaryStringRepresentation打印 number 的二进制表示,方便调试 number 标志。
### 如何参与项目 ### 如何参与项目
#### Node.js版本 #### Node.js版本
@@ -79,9 +111,6 @@ v10.20.1
3. 打包成js: `gulp build` 3. 打包成js: `gulp build`
> 如遇到gulp未找到则先执行 `npm install gulp -g` > 如遇到gulp未找到则先执行 `npm install gulp -g`
### 渲染集成框架
#### [cocos-framework](https://github.com/esengine/cocos-framework)
## 扩展库 ## 扩展库
#### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar) #### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar)

View File

@@ -115,16 +115,17 @@ module es {
* 删除全局管理器对象 * 删除全局管理器对象
* @param manager * @param manager
*/ */
public static unregisterGlobalManager(manager: es.GlobalManager) { public static unregisterGlobalManager(manager: GlobalManager) {
new es.List(this._instance._globalManagers).remove(manager); new es.List(this._instance._globalManagers).remove(manager);
manager.enabled = false; manager.enabled = false;
} }
/** /**
* 获取类型为T的全局管理器 * 获取指定类型的全局管理器实例
* @param type * @param type 管理器类型的构造函数
* @returns 指定类型的全局管理器实例,如果找不到则返回 null
*/ */
public static getGlobalManager<T extends es.GlobalManager>(type: new (...args) => T): T { public static getGlobalManager<T extends GlobalManager>(type: new (...args) => T): T {
for (let i = 0, s = Core._instance._globalManagers.length; i < s; ++i) { for (let i = 0, s = Core._instance._globalManagers.length; i < s; ++i) {
let manager = Core._instance._globalManagers[i]; let manager = Core._instance._globalManagers[i];
if (manager instanceof type) if (manager instanceof type)

View File

@@ -8,19 +8,41 @@ module es {
} }
export class Debug { export class Debug {
/**
* 如果条件为true则在控制台中以警告方式打印消息。
* @param condition 是否应该打印消息的条件
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static warnIf(condition: boolean, format: string, ...args: any[]) { public static warnIf(condition: boolean, format: string, ...args: any[]) {
if (condition) if (condition)
this.log(LogType.warn, format, args); this.log(LogType.warn, format, args);
} }
/**
* 在控制台中以警告方式打印消息。
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static warn(format: string, ...args: any[]) { public static warn(format: string, ...args: any[]) {
this.log(LogType.warn, format, args); this.log(LogType.warn, format, args);
} }
/**
* 在控制台中以错误方式打印消息。
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static error(format: string, ...args: any[]) { public static error(format: string, ...args: any[]) {
this.log(LogType.error, format, args); this.log(LogType.error, format, args);
} }
/**
* 在控制台中以标准日志方式打印消息。
* @param type 要打印的日志类型
* @param format 要打印的消息格式
* @param args 与消息格式相对应的参数列表
*/
public static log(type: LogType, format: string, ...args: any[]) { public static log(type: LogType, format: string, ...args: any[]) {
switch (type) { switch (type) {
case LogType.error: case LogType.error:

View File

@@ -1,74 +1,78 @@
module es { module es {
/** /**
* 帮助处理位掩码的实用程序类 * 一个用于操作二进制标志(也称为位字段)
* 除了isFlagSet之外所有方法都期望flag参数是一个非移位的标志
* 允许您使用普通的(0、1、2、3等)来设置/取消您的标记
*/ */
export class Flags { export class Flags {
/** /**
* 检查位标志是否已在数值中设置 * 检查指定二进制数字中是否已设置了指定标志位
* 检查期望标志是否已经移位 * @param self 二进制数字
* @param self * @param flag 标志位应该为2的幂
* @param flag * @returns 如果设置了指定的标志位则返回true否则返回false
*/ */
public static isFlagSet(self: number, flag: number): boolean { public static isFlagSet(self: number, flag: number): boolean {
return (self & flag) != 0; return (self & flag) !== 0;
} }
/** /**
* 检查位标志是否在数值中设置 * 检查指定二进制数字中是否已设置未移位的指定标志位
* @param self * @param self 二进制数字
* @param flag * @param flag 标志位不应移位应为2的幂
* @returns 如果设置了指定的标志位则返回true否则返回false
*/ */
public static isUnshiftedFlagSet(self: number, flag: number): boolean { public static isUnshiftedFlagSet(self: number, flag: number): boolean {
flag = 1 << flag; flag = 1 << flag;
return (self & flag) != 0; return (self & flag) !== 0;
} }
/** /**
* 设置数值标志位,移除所有已经设置的标志 * 将指定的标志位设置为二进制数字的唯一标志
* @param self * @param self 二进制数字
* @param flag * @param flag 标志位应该为2的幂
*/ */
public static setFlagExclusive(self: Ref<number>, flag: number) { public static setFlagExclusive(self: Ref<number>, flag: number) {
self.value = 1 << flag; self.value = 1 << flag;
} }
/** /**
* 设置标志位 * 将指定的标志位设置为二进制数字
* @param self * @param self 二进制数字的引用
* @param flag * @param flag 标志位应该为2的幂
*/ */
public static setFlag(self: Ref<number>, flag: number) { public static setFlag(self: Ref<number>, flag: number) {
self.value = (self.value | 1 << flag); self.value |= 1 << flag;
} }
/** /**
* 取消标志位 * 将指定的标志位从二进制数字中取消设置
* @param self * @param self 二进制数字的引用
* @param flag * @param flag 标志位应该为2的幂
*/ */
public static unsetFlag(self: Ref<number>, flag: number) { public static unsetFlag(self: Ref<number>, flag: number) {
flag = 1 << flag; flag = 1 << flag;
self.value = (self.value & (~flag)); self.value &= ~flag;
} }
/** /**
* 反转数值集合位 * 反转二进制数字中的所有位将1变为0将0变为1
* @param self * @param self 二进制数字的引用
*/ */
public static invertFlags(self: Ref<number>) { public static invertFlags(self: Ref<number>) {
self.value = ~self.value; self.value = ~self.value;
} }
/** /**
* 打印 number 的二进制表示。 方便调试 number 标志 * 返回二进制数字的字符串表示形式(以二进制形式)
* @param self 二进制数字
* @param leftPadWidth 返回的字符串的最小宽度在左侧填充0
* @returns 二进制数字的字符串表示形式
*/ */
public static binaryStringRepresentation(self: number, public static binaryStringRepresentation(
leftPadWidth: number = 10) { self: number,
leftPadWidth = 10
): string {
let str = self.toString(2); let str = self.toString(2);
while (str.length < (leftPadWidth || 2)) { while (str.length < (leftPadWidth || 2)) {
str = '0' + str; str = "0" + str;
} }
return str; return str;
} }

View File

@@ -214,14 +214,18 @@ module es {
} }
public rayIntersects(ray: Ray2D): { intersected: boolean; distance: number } { public rayIntersects(ray: Ray2D): { intersected: boolean; distance: number } {
// 存储相交点和相交距离
const res = {intersected: false, distance: 0}; const res = {intersected: false, distance: 0};
let maxValue = Infinity; let maxValue = Infinity;
// 计算射线与矩形的相交距离
if (Math.abs(ray.direction.x) < 1E-06) { if (Math.abs(ray.direction.x) < 1E-06) {
// 如果射线方向的x分量很小说明它是垂直的那么它就不会相交
if (ray.start.x < this.x || ray.start.x > this.x + this.width) { if (ray.start.x < this.x || ray.start.x > this.x + this.width) {
return res; return res;
} }
} else { } else {
// 计算射线与x边界的交点以及在矩形上面和下面的交点
const num11 = 1 / ray.direction.x; const num11 = 1 / ray.direction.x;
let num8 = (this.x - ray.start.x) * num11; let num8 = (this.x - ray.start.x) * num11;
let num7 = (this.x + this.width - ray.start.x) * num11; let num7 = (this.x + this.width - ray.start.x) * num11;
@@ -229,6 +233,7 @@ module es {
[num7, num8] = [num8, num7]; [num7, num8] = [num8, num7];
} }
// 将最远的相交距离更新为上下两个交点中更远的那个
res.distance = Math.max(num8, res.distance); res.distance = Math.max(num8, res.distance);
maxValue = Math.min(num7, maxValue); maxValue = Math.min(num7, maxValue);
if (res.distance > maxValue) { if (res.distance > maxValue) {
@@ -236,6 +241,7 @@ module es {
} }
} }
// 计算射线与y边界的交点以及在矩形左边和右边的交点
if (Math.abs(ray.direction.y) < 1e-06) { if (Math.abs(ray.direction.y) < 1e-06) {
if (ray.start.y < this.y || ray.start.y > this.y + this.height) { if (ray.start.y < this.y || ray.start.y > this.y + this.height) {
return res; return res;
@@ -248,6 +254,7 @@ module es {
[num5, num6] = [num6, num5]; [num5, num6] = [num6, num5];
} }
// 将最远的相交距离更新为左右两个交点中更远的那个
res.distance = Math.max(num6, res.distance); res.distance = Math.max(num6, res.distance);
maxValue = Math.min(num5, maxValue); maxValue = Math.min(num5, maxValue);
if (res.distance > maxValue) { if (res.distance > maxValue) {
@@ -255,6 +262,7 @@ module es {
} }
} }
// 如果相交了,将标志设为真,并返回相交点
res.intersected = true; res.intersected = true;
return res; return res;
} }

View File

@@ -125,45 +125,74 @@ module es {
return false; return false;
} }
public static rectToLine(rect: Rectangle, lineFrom: Vector2, lineTo: Vector2) { /**
* 检查矩形和线段之间是否相交
* @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 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); const toSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineTo);
// 起点或终点位于矩形内部
if (fromSector == PointSectors.center || toSector == PointSectors.center) { if (fromSector == PointSectors.center || toSector == PointSectors.center) {
return true; return true;
} else if ((fromSector & toSector) != 0) { }
return false;
} else {
const both = fromSector | toSector;
// 线对边进行检查
let edgeFrom: Vector2;
let edgeTo: Vector2;
// 起点和终点都在矩形外部的同一区域
if ((fromSector & toSector) != 0) {
return false;
}
// 到这里说明起点和终点分别在矩形的两个不同区域,需要检查线段是否与矩形的边相交
// 枚举起点和终点所在区域
const both = fromSector | toSector;
// 逐条检查矩形的四条边是否与线段相交
if ((both & PointSectors.top) != 0) { if ((both & PointSectors.top) != 0) {
edgeFrom = new Vector2(rect.x, rect.y); if (this.lineToLine(
edgeTo = new Vector2(rect.x + rect.width, rect.y); new Vector2(rect.x, rect.y),
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo)) new Vector2(rect.x + rect.width, rect.y),
lineFrom,
lineTo
)) {
return true; return true;
} }
}
if ((both & PointSectors.bottom) != 0) { if ((both & PointSectors.bottom) != 0) {
edgeFrom = new Vector2(rect.x, rect.y + rect.height); if (this.lineToLine(
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height); new Vector2(rect.x, rect.y + rect.height),
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo)) new Vector2(rect.x + rect.width, rect.y + rect.height),
lineFrom,
lineTo
)) {
return true; return true;
} }
}
if ((both & PointSectors.left) != 0) { if ((both & PointSectors.left) != 0) {
edgeFrom = new Vector2(rect.x, rect.y); if (this.lineToLine(
edgeTo = new Vector2(rect.x, rect.y + rect.height); new Vector2(rect.x, rect.y),
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo)) new Vector2(rect.x, rect.y + rect.height),
lineFrom,
lineTo
)) {
return true; return true;
} }
}
if ((both & PointSectors.right) != 0) { if ((both & PointSectors.right) != 0) {
edgeFrom = new Vector2(rect.x + rect.width, rect.y); if (this.lineToLine(
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height); new Vector2(rect.x + rect.width, rect.y),
if (this.lineToLine(edgeFrom, edgeTo, lineFrom, lineTo)) new Vector2(rect.x + rect.width, rect.y + rect.height),
lineFrom,
lineTo
)) {
return true; return true;
} }
} }

View File

@@ -35,52 +35,59 @@ module es {
} }
/** /**
* 将对象添加到SpatialHash * 注册一个碰撞器
* @param collider * @param collider 碰撞器
*/ */
public register(collider: Collider) { public register(collider: Collider): void {
let bounds = collider.bounds.clone(); // 克隆碰撞器的 bounds 属性
const bounds = collider.bounds.clone();
// 存储克隆后的 bounds 属性到 registeredPhysicsBounds 属性中
collider.registeredPhysicsBounds = bounds; collider.registeredPhysicsBounds = bounds;
let p1 = this.cellCoords(bounds.x, bounds.y); // 获取碰撞器所在的网格坐标
let p2 = this.cellCoords(bounds.right, bounds.bottom); const p1 = this.cellCoords(bounds.x, bounds.y);
const p2 = this.cellCoords(bounds.right, bounds.bottom);
// 更新边界以跟踪网格大小 // 更新网格边界,以确保其覆盖所有碰撞器
if (!this.gridBounds.contains(p1.x, p1.y)) { if (!this.gridBounds.contains(p1.x, p1.y)) {
this.gridBounds = RectangleExt.union(this.gridBounds, p1); this.gridBounds = RectangleExt.union(this.gridBounds, p1);
} }
if (!this.gridBounds.contains(p2.x, p2.y)) { if (!this.gridBounds.contains(p2.x, p2.y)) {
this.gridBounds = RectangleExt.union(this.gridBounds, p2); this.gridBounds = RectangleExt.union(this.gridBounds, p2);
} }
// 将碰撞器添加到所在的所有单元格中
for (let x = p1.x; x <= p2.x; x++) { for (let x = p1.x; x <= p2.x; x++) {
for (let y = p1.y; y <= p2.y; y++) { for (let y = p1.y; y <= p2.y; y++) {
// 如果没有单元格,我们需要创建它 // 如果单元格不存在,创建一个新的单元格
let c: Collider[] = this.cellAtPosition(x, y, true); const cell: Collider[] = this.cellAtPosition(x, y, /* createIfNotExists = */ true);
c.push(collider); cell.push(collider);
} }
} }
} }
/** /**
* 从SpatialHash中删除对象 * 从空间哈希中移除一个碰撞器
* @param collider * @param collider 碰撞器
*/ */
public remove(collider: Collider) { public remove(collider: Collider): void {
let bounds = collider.registeredPhysicsBounds.clone(); // 克隆碰撞器的 registeredPhysicsBounds 属性
let p1 = this.cellCoords(bounds.x, bounds.y); const bounds = collider.registeredPhysicsBounds.clone();
let p2 = this.cellCoords(bounds.right, bounds.bottom); // 获取碰撞器所在的网格坐标
const p1 = this.cellCoords(bounds.x, bounds.y);
const p2 = this.cellCoords(bounds.right, bounds.bottom);
// 从所有单元格中移除该碰撞器
for (let x = p1.x; x <= p2.x; x++) { for (let x = p1.x; x <= p2.x; x++) {
for (let y = p1.y; y <= p2.y; y++) { for (let y = p1.y; y <= p2.y; y++) {
// 单元格应该始终存在,因为这个碰撞器应该在所有查询的单元格中 // 单元格应该始终存在,因为碰撞器应该在所有查询的单元格中
let cell = this.cellAtPosition(x, y); const cell = this.cellAtPosition(x, y);
Insist.isNotNull(cell, `从不存在碰撞器的单元格中移除碰撞器: [${collider}]`); Insist.isNotNull(cell, `从不存在碰撞器的单元格中移除碰撞器: [${collider}]`);
if (cell != null) if (cell != null) {
new es.List(cell).remove(collider); new es.List(cell).remove(collider);
} }
} }
} }
}
/** /**
* 使用蛮力方法从SpatialHash中删除对象 * 使用蛮力方法从SpatialHash中删除对象
@@ -95,32 +102,38 @@ module es {
} }
/** /**
* 返回边框与单元格相交的所有对象 * 执行基于 AABB 的广域相交检测并返回碰撞器列表
* @param bounds * @param bounds 边界矩形
* @param excludeCollider * @param excludeCollider 排除的碰撞器
* @param layerMask * @param layerMask 碰撞层掩码
* @returns 碰撞器列表
*/ */
public aabbBroadphase(bounds: Rectangle, excludeCollider: Collider, layerMask: number): Collider[] { public aabbBroadphase(bounds: Rectangle, excludeCollider: Collider, layerMask: number): Collider[] {
this._tempHashSet.clear(); this._tempHashSet.clear();
// 获取边界矩形所在的网格单元格
const p1 = this.cellCoords(bounds.x, bounds.y); const p1 = this.cellCoords(bounds.x, bounds.y);
const p2 = this.cellCoords(bounds.right, bounds.bottom); const p2 = this.cellCoords(bounds.right, bounds.bottom);
// 对所有相交的单元格中的碰撞器执行检测
for (let x = p1.x; x <= p2.x; x++) { for (let x = p1.x; x <= p2.x; x++) {
for (let y = p1.y; y <= p2.y; y++) { for (let y = p1.y; y <= p2.y; y++) {
const cell = this.cellAtPosition(x, y); const cell = this.cellAtPosition(x, y);
if (!cell) if (!cell) {
continue; continue;
}
// 当cell不为空循环并取回所有碰撞器 // 如果单元格不为空循环并取回所有碰撞器
if (cell.length > 0) { if (cell.length > 0) {
for (let i = 0; i < cell.length; i++) { for (let i = 0; i < cell.length; i++) {
const collider = cell[i]; const collider = cell[i];
// 如果它是自身或者如果它不匹配我们的层掩码跳过这个碰撞器 // 如果它是自身或者如果它不匹配我们的层掩码跳过这个碰撞器
if (collider == excludeCollider || !Flags.isFlagSet(layerMask, collider.physicsLayer.value)) if (collider === excludeCollider || !Flags.isFlagSet(layerMask, collider.physicsLayer.value)) {
continue; continue;
}
// 检查碰撞器的 bounds 是否与边界矩形相交
if (bounds.intersects(collider.bounds)) { if (bounds.intersects(collider.bounds)) {
this._tempHashSet.add(collider); this._tempHashSet.add(collider);
} }
@@ -129,66 +142,65 @@ module es {
} }
} }
// 返回所有相交的碰撞器列表
return Array.from(this._tempHashSet); return Array.from(this._tempHashSet);
} }
/** /**
* 通过空间散列投掷一条线,并将该线碰到的任何碰撞器填入碰撞数组 * 执行基于线段的射线检测并返回所有命中的碰撞器
* https://github.com/francisengelmann/fast_voxel_traversal/blob/master/main.cpp * @param start 射线起点
* http://www.cse.yorku.ca/~amana/research/grid.pdf * @param end 射线终点
* @param start * @param hits 射线命中结果
* @param end * @param layerMask 碰撞层掩码
* @param hits * @param ignoredColliders 忽略的碰撞器
* @param layerMask * @returns 命中的碰撞器数量
*/ */
public linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>) { public linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>): number {
let ray = new Ray2D(start, end); // 创建一个射线
const ray = new Ray2D(start, end);
// 使用射线解析器初始化线段命中结果
this._raycastParser.start(ray, hits, layerMask, ignoredColliders); this._raycastParser.start(ray, hits, layerMask, ignoredColliders);
// 获取我们的起始/结束位置,与我们的网格在同一空间内 // 获取起点和终点所在的网格单元格
let currentCell = this.cellCoords(start.x, start.y); let currentCell = this.cellCoords(start.x, start.y);
let lastCell = this.cellCoords(end.x, end.y); const lastCell = this.cellCoords(end.x, end.y);
// 我们向什么方向递增单元格检查? // 计算射线在 x 和 y 方向上的步长
let stepX = Math.sign(ray.direction.x); let stepX = Math.sign(ray.direction.x);
let stepY = Math.sign(ray.direction.y); let stepY = Math.sign(ray.direction.y);
if (currentCell.x === lastCell.x) {
stepX = 0;
}
if (currentCell.y === lastCell.y) {
stepY = 0;
}
// 我们要确保,如果我们在同一条线上或同一排上,就不会踩到不必要的方向上 // 计算 x 和 y 方向上的网格单元格步长
if (currentCell.x == lastCell.x) stepX = 0; const xStep = stepX < 0 ? 0 : stepX;
if (currentCell.y == lastCell.y) stepY = 0; const yStep = stepY < 0 ? 0 : stepY;
// 计算单元格的边界。
// 当步长为正数时下一个单元格在这个单元格之后意味着我们要加1。
let xStep = stepX < 0 ? 0 : stepX;
let yStep = stepY < 0 ? 0 : stepY;
let nextBoundaryX = (currentCell.x + xStep) * this._cellSize; let nextBoundaryX = (currentCell.x + xStep) * this._cellSize;
let nextBoundaryY = (currentCell.y + yStep) * this._cellSize; let nextBoundaryY = (currentCell.y + yStep) * this._cellSize;
// 确定射线穿过第一个垂直体素边界时的t值y/水平也是如此。 // 计算 t 值的最大值和步长
// 这两个值的最小值将表明我们可以沿着射线移动多少,并且仍然保持在当前的体素中,对于接近垂直/水平的射线可能是无限的。 let tMaxX = ray.direction.x !== 0 ? (nextBoundaryX - ray.start.x) / ray.direction.x : Number.MAX_VALUE;
let tMaxX = ray.direction.x != 0 ? (nextBoundaryX - ray.start.x) / ray.direction.x : Number.MAX_VALUE; let tMaxY = ray.direction.y !== 0 ? (nextBoundaryY - ray.start.y) / ray.direction.y : Number.MAX_VALUE;
let tMaxY = ray.direction.y != 0 ? (nextBoundaryY - ray.start.y) / ray.direction.y : Number.MAX_VALUE; const tDeltaX = ray.direction.x !== 0 ? this._cellSize / (ray.direction.x * stepX) : Number.MAX_VALUE;
const tDeltaY = ray.direction.y !== 0 ? this._cellSize / (ray.direction.y * stepY) : Number.MAX_VALUE;
// 我们要走多远才能从一个单元格的边界穿过一个单元格 // 检查射线起点所在的单元格是否与射线相交
let tDeltaX = ray.direction.x != 0 ? this._cellSize / (ray.direction.x * stepX) : Number.MAX_VALUE;
let tDeltaY = ray.direction.y != 0 ? this._cellSize / (ray.direction.y * stepY) : Number.MAX_VALUE;
// 开始遍历并返回交叉单元格。
let cell = this.cellAtPosition(currentCell.x, currentCell.y); let cell = this.cellAtPosition(currentCell.x, currentCell.y);
if (cell !== null && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)) {
if (cell != null && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)) {
this._raycastParser.reset(); this._raycastParser.reset();
return this._raycastParser.hitCounter; return this._raycastParser.hitCounter;
} }
while (currentCell.x != lastCell.x || currentCell.y != lastCell.y) { // 在所有相交的单元格中沿着射线前进并检查碰撞器
while (currentCell.x !== lastCell.x || currentCell.y !== lastCell.y) {
if (tMaxX < tMaxY) { if (tMaxX < tMaxY) {
currentCell.x = MathHelper.toInt(MathHelper.approach(currentCell.x, lastCell.x, Math.abs(stepX))); currentCell.x = MathHelper.toInt(MathHelper.approach(currentCell.x, lastCell.x, Math.abs(stepX)));
tMaxX += tDeltaX; tMaxX += tDeltaX;
} else { } else {
currentCell.y = MathHelper.toInt(MathHelper.approach(currentCell.y, lastCell.y, Math.abs(stepY))); currentCell.y = MathHelper.toInt(MathHelper.approach(currentCell.y, lastCell.y, Math.abs(stepY)));
tMaxY += tDeltaY; tMaxY += tDeltaY;
} }
@@ -199,36 +211,43 @@ module es {
} }
} }
// 复位 // 重置射线解析器并返回命中的碰撞器数量
this._raycastParser.reset(); this._raycastParser.reset();
return this._raycastParser.hitCounter; return this._raycastParser.hitCounter;
} }
/** /**
* 获取所有在指定矩形范围内的碰撞器 * 执行矩形重叠检测并返回所有命中的碰撞器
* @param rect * @param rect 矩形
* @param results * @param results 碰撞器命中结果
* @param layerMask * @param layerMask 碰撞层掩码
* @returns 命中的碰撞器数量
*/ */
public overlapRectangle(rect: Rectangle, results: Collider[], layerMask: number) { public overlapRectangle(rect: Rectangle, results: Collider[], layerMask: number): number {
// 更新重叠检测框的位置和大小
this._overlapTestBox.updateBox(rect.width, rect.height); this._overlapTestBox.updateBox(rect.width, rect.height);
this._overlapTestBox.position = rect.location; this._overlapTestBox.position = rect.location;
let resultCounter = 0; let resultCounter = 0;
let potentials = this.aabbBroadphase(rect, null, layerMask); // 获取潜在的相交碰撞器
const potentials = this.aabbBroadphase(rect, null, layerMask);
// 遍历所有潜在的碰撞器并检查它们是否与矩形相交
for (let i = 0; i < potentials.length; i++) { for (let i = 0; i < potentials.length; i++) {
const collider = potentials[i]; const collider = potentials[i];
if (collider instanceof BoxCollider) { if (collider instanceof BoxCollider) {
// 如果是 BoxCollider直接将其添加到命中结果中
results[resultCounter] = collider; results[resultCounter] = collider;
resultCounter++; resultCounter++;
} else if (collider instanceof CircleCollider) { } else if (collider instanceof CircleCollider) {
// 如果是 CircleCollider使用 rectToCircle 函数检查矩形与圆是否相交
if (Collisions.rectToCircle(rect, collider.bounds.center, collider.bounds.width * 0.5)) { if (Collisions.rectToCircle(rect, collider.bounds.center, collider.bounds.width * 0.5)) {
results[resultCounter] = collider; results[resultCounter] = collider;
resultCounter++; resultCounter++;
} }
} else if (collider instanceof PolygonCollider) { } else if (collider instanceof PolygonCollider) {
// 如果是 PolygonCollider使用 Polygon.shape.overlaps 函数检查矩形与多边形是否相交
if (collider.shape.overlaps(this._overlapTestBox)) { if (collider.shape.overlaps(this._overlapTestBox)) {
results[resultCounter] = collider; results[resultCounter] = collider;
resultCounter++; resultCounter++;
@@ -237,42 +256,53 @@ module es {
throw new Error("overlapRectangle对这个类型没有实现!"); throw new Error("overlapRectangle对这个类型没有实现!");
} }
if (resultCounter == results.length) if (resultCounter === results.length) {
return resultCounter; return resultCounter;
} }
}
return resultCounter; return resultCounter;
} }
/** /**
* 获取所有落在指定圆圈内的碰撞器 * 执行圆形重叠检测并返回所有命中的碰撞器
* @param circleCenter * @param circleCenter 圆心坐标
* @param radius * @param radius 圆形半径
* @param results * @param results 碰撞器命中结果
* @param layerMask * @param layerMask 碰撞层掩码
* @returns 命中的碰撞器数量
*/ */
public overlapCircle(circleCenter: Vector2, radius: number, results: Collider[], layerMask): number { public overlapCircle(circleCenter: Vector2, radius: number, results: Collider[], layerMask: number): number {
// 计算包含圆形的最小矩形框
const bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2); const bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2);
// 更新重叠检测圆的位置和半径
this._overlapTestCircle.radius = radius; this._overlapTestCircle.radius = radius;
this._overlapTestCircle.position = circleCenter; this._overlapTestCircle.position = circleCenter;
let resultCounter = 0; let resultCounter = 0;
// 获取潜在的相交碰撞器
const potentials = this.aabbBroadphase(bounds, null, layerMask); const potentials = this.aabbBroadphase(bounds, null, layerMask);
if (potentials.length > 0)
// 遍历所有潜在的碰撞器并检查它们是否与圆相交
if (potentials.length > 0) {
for (let i = 0; i < potentials.length; i++) { for (let i = 0; i < potentials.length; i++) {
const collider = potentials[i]; const collider = potentials[i];
if (collider instanceof BoxCollider) { if (collider instanceof BoxCollider) {
// 如果是 BoxCollider使用 BoxCollider.shape.overlaps 函数检查矩形与圆是否相交
if (collider.shape.overlaps(this._overlapTestCircle)) { if (collider.shape.overlaps(this._overlapTestCircle)) {
results[resultCounter] = collider; results[resultCounter] = collider;
resultCounter++; resultCounter++;
} }
} else if (collider instanceof CircleCollider) { } else if (collider instanceof CircleCollider) {
// 如果是 CircleCollider使用 CircleCollider.shape.overlaps 函数检查圆与圆是否相交
if (collider.shape.overlaps(this._overlapTestCircle)) { if (collider.shape.overlaps(this._overlapTestCircle)) {
results[resultCounter] = collider; results[resultCounter] = collider;
resultCounter++; resultCounter++;
} }
} else if (collider instanceof PolygonCollider) { } else if (collider instanceof PolygonCollider) {
// 如果是 PolygonCollider使用 PolygonCollider.shape.overlaps 函数检查多边形与圆是否相交
if (collider.shape.overlaps(this._overlapTestCircle)) { if (collider.shape.overlaps(this._overlapTestCircle)) {
results[resultCounter] = collider; results[resultCounter] = collider;
resultCounter++; resultCounter++;
@@ -281,70 +311,101 @@ module es {
throw new Error("对这个对撞机类型的overlapCircle没有实现!"); throw new Error("对这个对撞机类型的overlapCircle没有实现!");
} }
// 如果我们所有的结果数据有了则返回 if (resultCounter === results.length) {
if (resultCounter === results.length)
return resultCounter; return resultCounter;
} }
}
}
return resultCounter; return resultCounter;
} }
/** /**
* 获取单元格的x,y值作为世界空间的x,y值 * 将给定的 x 和 y 坐标转换为单元格坐标
* @param x * @param x X 坐标
* @param y * @param y Y 坐标
* @returns 转换后的单元格坐标
*/ */
public cellCoords(x: number, y: number): Vector2 { public cellCoords(x: number, y: number): Vector2 {
// 使用 inverseCellSize 计算出单元格的 x 和 y 坐标
return new Vector2(Math.floor(x * this._inverseCellSize), Math.floor(y * this._inverseCellSize)); return new Vector2(Math.floor(x * this._inverseCellSize), Math.floor(y * this._inverseCellSize));
} }
/** /**
* 获取世界空间x,y值的单元格。 * 返回一个包含特定位置处的所有碰撞器的数组
* 如果单元格为空且createCellIfEmpty为true则会创建一个新的单元格 * 如果此位置上没有单元格且createCellIfEmpty参数为true则会创建一个新的单元格
* @param x * @param x 单元格 x 坐标
* @param y * @param y 单元格 y 坐标
* @param createCellIfEmpty * @param createCellIfEmpty 如果该位置上没有单元格是否创建一个新单元格默认为false
* @returns 该位置上的所有碰撞器
*/ */
public cellAtPosition(x: number, y: number, createCellIfEmpty: boolean = false): Collider[] { public cellAtPosition(x: number, y: number, createCellIfEmpty: boolean = false): Collider[] {
// 获取指定位置的单元格
let cell: Collider[] = this._cellDict.tryGetValue(x, y); let cell: Collider[] = this._cellDict.tryGetValue(x, y);
// 如果不存在此位置的单元格,并且需要创建,则创建并返回空单元格
if (!cell) { if (!cell) {
if (createCellIfEmpty) { if (createCellIfEmpty) {
cell = []; cell = [];
this._cellDict.add(x, y, cell); this._cellDict.add(x, y, cell);
} }
} }
return cell; return cell;
} }
} }
/**
* 数字字典
*/
export class NumberDictionary<T> { export class NumberDictionary<T> {
// 存储数据的 Map 对象
public _store: Map<string, T[]> = new Map<string, T[]>(); public _store: Map<string, T[]> = new Map<string, T[]>();
/**
* 将指定的列表添加到以给定 x 和 y 为键的字典条目中
* @param x 字典的 x 坐标
* @param y 字典的 y 坐标
* @param list 要添加到字典的列表
*/
public add(x: number, y: number, list: T[]) { public add(x: number, y: number, list: T[]) {
this._store.set(this.getKey(x, y), list); this._store.set(this.getKey(x, y), list);
} }
/** /**
* 使用蛮力方法从字典存储列表中移除碰撞器 * 从字典中删除给定的对象
* @param obj * @param obj 要删除的对象
*/ */
public remove(obj: T) { public remove(obj: T) {
// 遍历 Map 中的所有值,从值中查找并删除给定的对象
this._store.forEach(list => { this._store.forEach(list => {
let index = list.indexOf(obj); let index = list.indexOf(obj);
list.splice(index, 1); list.splice(index, 1);
}) })
} }
/**
* 尝试从字典中检索指定键的值
* @param x 字典的 x 坐标
* @param y 字典的 y 坐标
* @returns 指定键的值,如果不存在则返回 null
*/
public tryGetValue(x: number, y: number): T[] { public tryGetValue(x: number, y: number): T[] {
return this._store.get(this.getKey(x, y)); return this._store.get(this.getKey(x, y));
} }
/**
* 根据给定的 x 和 y 坐标返回一个唯一的字符串键
* @param x 字典的 x 坐标
* @param y 字典的 y 坐标
* @returns 唯一的字符串键
*/
public getKey(x: number, y: number) { public getKey(x: number, y: number) {
return `${x}_${y}`; return `${x}_${y}`;
} }
/** /**
* 清字典数据 * 清字典
*/ */
public clear() { public clear() {
this._store.clear(); this._store.clear();
@@ -378,46 +439,51 @@ module es {
} }
/** /**
* 如果hits数组被填充返回true。单元格不能为空! * 对射线检测到的碰撞器进行进一步的处理,将结果存储在传递的碰撞数组中。
* @param cellX * @param cellX 当前单元格的x坐标
* @param cellY * @param cellY 当前单元格的y坐标
* @param cell * @param cell 该单元格中的碰撞器列表
* @returns 如果当前单元格有任何碰撞器与射线相交则返回true
*/ */
public checkRayIntersection(cellX: number, cellY: number, cell: Collider[]): boolean { public checkRayIntersection(cellX: number, cellY: number, cell: Collider[]): boolean {
for (let i = 0; i < cell.length; i++) { for (let i = 0; i < cell.length; i++) {
const potential = cell[i]; const potential = cell[i];
// 管理我们已经处理过的碰撞器 // 如果该碰撞器已经处理过,则跳过它
if (this._checkedColliders.indexOf(potential) != -1) if (this._checkedColliders.indexOf(potential) != -1)
continue; continue;
// 将该碰撞器标记为已处理
this._checkedColliders.push(potential); this._checkedColliders.push(potential);
// 只有当我们被设置为这样做时才会点击触发器
// 如果该碰撞器是触发器且当前不允许触发器响应射线检测,则跳过它
if (potential.isTrigger && !Physics.raycastsHitTriggers) if (potential.isTrigger && !Physics.raycastsHitTriggers)
continue; continue;
// 确保碰撞器在图层蒙版上 // 确保碰撞器的图层与所提供的图层掩码相匹配
if (!Flags.isFlagSet(this._layerMask, potential.physicsLayer.value)) if (!Flags.isFlagSet(this._layerMask, potential.physicsLayer.value))
continue; continue;
// 如果设置了要忽略的碰撞器并且该碰撞器是被忽略的,则跳过它
if (this._ignoredColliders && this._ignoredColliders.has(potential)) { if (this._ignoredColliders && this._ignoredColliders.has(potential)) {
continue; continue;
} }
// TODO: rayIntersects的性能够吗?需要测试它。Collisions.rectToLine可能更快 // TODO: Collisions.rectToLine方法可能更快一些,因为它没有涉及到浮点数除法和平方根计算,而且更简单
// TODO: 如果边界检查返回更多数据我们就不需要为BoxCollider检查做任何事情 // 但是rayIntersects方法也很快并且在实际情况下可能更适合用于特定的应用程序
// 在做形状测试之前先做一个边界检查 // 先进行一个边界检查
const colliderBounds = potential.bounds; const colliderBounds = potential.bounds;
const res = colliderBounds.rayIntersects(this._ray); const res = colliderBounds.rayIntersects(this._ray);
if (res.intersected && res.distance <= 1) { if (res.intersected && res.distance <= 1) { // 只有当该碰撞器与射线相交且交点在射线长度范围内才进一步进行形状检测
let tempHit = new Out<RaycastHit>(this._tempHit); let tempHit = new Out<RaycastHit>(this._tempHit);
// 调用形状的方法检查该碰撞器是否与射线相交并将结果保存在tempHit中
if (potential.shape.collidesWithLine(this._ray.start, this._ray.end, tempHit)) { if (potential.shape.collidesWithLine(this._ray.start, this._ray.end, tempHit)) {
// 检查一下我们应该排除这些射线射线cast是否在碰撞器中开始 // 如果碰撞器包含射线起点,而且不允许射线起点在碰撞器中启动检测,那么跳过该碰撞器
if (!Physics.raycastsStartInColliders && potential.shape.containsPoint(this._ray.start)) if (!Physics.raycastsStartInColliders && potential.shape.containsPoint(this._ray.start))
continue; continue;
// TODO: 确保碰撞点在当前单元格中,如果它没有保存它以供以后计算 // 将碰撞信息添加到列表中
tempHit.value.collider = potential; tempHit.value.collider = potential;
this._cellHits.push(tempHit.value); this._cellHits.push(tempHit.value);
} }

View File

@@ -1,55 +1,58 @@
module es { module es {
/**
* 全局管理器的基类。所有全局管理器都应该从此类继承。
*/
export class GlobalManager { export class GlobalManager {
/**
* 表示管理器是否启用
*/
public _enabled: boolean; public _enabled: boolean;
/** /**
* 如果true则启用了GlobalManager。 * 获取或设置管理器是否启用
* 状态的改变会导致调用OnEnabled/OnDisable
*/ */
public get enabled() { public get enabled() {
return this._enabled; return this._enabled;
} }
/**
* 如果true则启用了GlobalManager。
* 状态的改变会导致调用OnEnabled/OnDisable
* @param value
*/
public set enabled(value: boolean) { public set enabled(value: boolean) {
this.setEnabled(value); this.setEnabled(value);
} }
/** /**
* 启用/禁用这个GlobalManager * 设置管理器是否启用
* @param isEnabled * @param isEnabled 如果为true则启用管理器否则禁用管理器
*/ */
public setEnabled(isEnabled: boolean) { public setEnabled(isEnabled: boolean) {
if (this._enabled != isEnabled) { if (this._enabled != isEnabled) {
this._enabled = isEnabled; this._enabled = isEnabled;
if (this._enabled) { if (this._enabled) {
// 如果启用了管理器则调用onEnabled方法
this.onEnabled(); this.onEnabled();
} else { } else {
// 如果禁用了管理器则调用onDisabled方法
this.onDisabled(); this.onDisabled();
} }
} }
} }
/** /**
* 此GlobalManager启用时调用 * 在启用管理器时调用的回调方法
*/ */
public onEnabled() { public onEnabled() {
} }
/** /**
* 此GlobalManager禁用时调用 * 在禁用管理器时调用的回调方法
*/ */
public onDisabled() { public onDisabled() {
} }
/** /**
* 在frame .update之前调用每一帧 * 更新管理器状态的方法
*/ */
public update() { public update() {
} }
} }
} }