Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dccd4e21b6 | |||
| c96e8b3a04 | |||
| e686ba64d7 | |||
| 14cb9cd257 | |||
| 13e7737cb9 | |||
| dd65c60921 | |||
| 7dffb4d94a | |||
| 476204a296 | |||
| 983c8fbc99 | |||
| c30e591f6e | |||
| d9a1b5578c | |||
| f0e04b6981 | |||
| 14598f08c7 | |||
| 032b293085 | |||
| d9bb76c105 | |||
| 20392c8ab6 | |||
| 583e03d025 | |||
| f6c2d81a83 | |||
| e703ff4e6c | |||
| 877fc4c9bf | |||
| a80bb4b6f3 | |||
| 9b9d210109 | |||
| 817b703d4f | |||
| 88a25453e6 | |||
| 6e3eb1189a | |||
| aea50926a9 | |||
| 299c1b8e7d | |||
| 3f6ab79894 | |||
| b14fee1685 | |||
| ace8fb685d | |||
| 1870ee5e45 | |||
| 8be65fa685 | |||
| cf4e76b12d | |||
| c156463f10 | |||
| e7796550c6 | |||
| c3c9181400 |
@@ -3,29 +3,12 @@
|
||||
|
||||
[](https://lgtm.com/projects/g/esengine/egret-framework/context:javascript)
|
||||
|
||||
```
|
||||
[](https://lgtm.com/projects/g/esengine/egret-framework/context:javascript)
|
||||
```
|
||||
这是一套用于egret的游戏框架,里面包含ECS框架用于管理场景实体,一些常用2D碰撞检测及A*寻路。如果您还需要包含其他的AI系统可以查看作者其他库(行为树、简易FSM、实用AI)。
|
||||
|
||||
这是一套用于egret的游戏框架,里面包含ECS框架用于管理场景实体,MVC框架用于管理ui界面(fairygui),一些常用2D碰撞检测及A*寻路。如果您还需要包含其他的AI系统可以查看作者其他库(行为树、简易FSM、实用AI)。
|
||||
|
||||
## 当前版本功能
|
||||
## 版本计划功能
|
||||
|
||||
- [x] 简易ECS框架
|
||||
- [x] A*寻路(AStar)
|
||||
- [x] 常用碰撞检测
|
||||
- [x] 数学库
|
||||
- [x] 简易矩阵类
|
||||
- [x] 简易2d 向量类
|
||||
- [x] 掩码实用类
|
||||
- [x] BreadthFirst 寻路算法
|
||||
- [x] Dijkstra 寻路算法
|
||||
- [x] 事件处理器
|
||||
|
||||
## 计划列表
|
||||
|
||||
- [ ] ECS
|
||||
- [ ] 组件列表
|
||||
- [x] 组件列表
|
||||
- [x] 碰撞组件
|
||||
- [x] 移动组件
|
||||
- [ ] 刚体组件
|
||||
@@ -37,16 +20,42 @@
|
||||
- [ ] 相机震动组件
|
||||
- [ ] 霓虹灯组件
|
||||
- [x] 跟随相机组件
|
||||
- [ ] 系统列表
|
||||
- [ ] 被动系统
|
||||
- [ ] 协调系统
|
||||
- [ ] 简易MVC框架
|
||||
- [ ] 数学库
|
||||
- [ ] 贝塞尔曲线
|
||||
- [ ] 快速随机数类
|
||||
- [ ] 浮点助手类
|
||||
- [ ] 高性能数组
|
||||
- [x] 系统列表
|
||||
- [x] 被动系统
|
||||
- [x] 协调系统
|
||||
- [x] A*寻路(AStar)
|
||||
- [x] 常用碰撞检测
|
||||
- [x] 数学库
|
||||
- [x] 简易矩阵类
|
||||
- [x] 简易2d 向量类
|
||||
- [x] 掩码实用类
|
||||
- [x] 贝塞尔曲线
|
||||
- [x] 快速随机数类
|
||||
- [x] BreadthFirst 寻路算法
|
||||
- [x] Dijkstra 寻路算法
|
||||
- [x] 事件处理器
|
||||
|
||||
## 关于egret用ecs框架(typescript/javascript)
|
||||
ecs 是功能强大的实体组件系统。typescript与其他语言不同,因此我对ecs的设计尽可能的支持typescript特性。虽然ecs拥有标准实体组件系统,但在细节上有很大不同。创建标准ecs通常处于原始速度、缓存位置和其他性能原因。使用typescript,我们没有struct,因为没有必要匹配标准实体组件系统的设计方式,因为我们对内存布局没有那种控制。
|
||||
|
||||
ecs更灵活,可以更好的集中、组织、排序和过滤游戏中的对象。ecs让您拥有轻量级实体和组件,这些组件可以由系统批量处理。
|
||||
例如,您在制作一个射手,您可能会有几十到几百个子弹,这些作为轻量级实体由系统批量处理。
|
||||
|
||||
所以ecs在设计当中拥有四种重要类型:世界(Scene),过滤器(Matcher),系统(System)和实体(Entity)
|
||||
|
||||
## 世界(Scene)
|
||||
Scene是ecs包含系统和实体最外面的容器。
|
||||
|
||||
## 实体(Entity)
|
||||
实体只由系统处理。
|
||||
|
||||
## 组件(Component)
|
||||
组件应该只包含数据而没有逻辑代码。对数据进行逻辑是系统的工作。
|
||||
|
||||
## 系统(System)
|
||||
ecs中的系统会不断的更新实体。系统使用过滤器选择某些实体,然后仅更新那些选择的实体。
|
||||
|
||||
## 作者其他库(egret)
|
||||
|
||||
- [x] [行为树/实用AI 系统](https://github.com/esengine/egret-BehaviourTree-ai)
|
||||
- [行为树/实用AI 系统](https://github.com/esengine/egret-BehaviourTree-ai)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-slate
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "egret",
|
||||
"isShellCommand": true,
|
||||
"showOutput": "silent",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"problemMatcher": "$tsc"
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
data-entry-class="Main"
|
||||
data-orientation="auto"
|
||||
data-scale-mode="fixedWidth"
|
||||
data-frame-rate="30"
|
||||
data-frame-rate="60"
|
||||
data-content-width="640"
|
||||
data-content-height="1136"
|
||||
data-multi-fingered="2"
|
||||
|
||||
Vendored
-2452
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+553
-137
File diff suppressed because it is too large
Load Diff
+2413
-433
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -132,9 +132,19 @@
|
||||
"name": "egret_icon_png"
|
||||
},
|
||||
{
|
||||
"name": "description_json",
|
||||
"url": "config/description.json",
|
||||
"type": "json",
|
||||
"url": "config/description.json"
|
||||
"name": "description_json"
|
||||
},
|
||||
{
|
||||
"url": "assets/isometric_grass_and_water.json",
|
||||
"type": "json",
|
||||
"name": "isometric_grass_and_water_json"
|
||||
},
|
||||
{
|
||||
"url": "assets/isometric_grass_and_water.png",
|
||||
"type": "image",
|
||||
"name": "isometric_grass_and_water_png"
|
||||
}
|
||||
]
|
||||
}
|
||||
+24
-15
@@ -14,14 +14,15 @@ class MainScene extends Scene {
|
||||
bg.addComponent(new SpriteRenderer()).setSprite(sprite).setColor(0xff0000);
|
||||
bg.addComponent(new PlayerController());
|
||||
bg.addComponent(new Mover());
|
||||
// bg.addComponent(new BoxCollider());
|
||||
bg.addComponent(new ScrollingSpriteRenderer(sprite));
|
||||
bg.addComponent(new BoxCollider());
|
||||
bg.position = new Vector2(Math.random() * 200, Math.random() * 200);
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
let sprite = new Sprite(RES.getRes("checkbox_select_disabled_png"));
|
||||
let player2 = this.createEntity("player2");
|
||||
player2.addComponent(new SpriteRenderer()).setSprite(sprite);
|
||||
player2.position = new Vector2(Math.random() * 100 * i, Math.random() * 100 * i);
|
||||
player2.position = new Vector2(Math.random() * 1000, Math.random() * 1000);
|
||||
player2.addComponent(new BoxCollider());
|
||||
}
|
||||
|
||||
@@ -44,6 +45,13 @@ class MainScene extends Scene {
|
||||
return new MainScene();
|
||||
}));
|
||||
}, this);
|
||||
|
||||
Main.emitter.addObserver(CoreEmitterType.Update, this.handleFuncTest, this);
|
||||
}
|
||||
|
||||
/** 测试Emitter */
|
||||
private handleFuncTest(){
|
||||
Main.emitter.removeObserver(CoreEmitterType.Update, this.handleFuncTest);
|
||||
}
|
||||
|
||||
public breadthfirstTest() {
|
||||
@@ -63,24 +71,25 @@ class MainScene extends Scene {
|
||||
public dijkstraTest() {
|
||||
let graph = new WeightedGridGraph(20, 20);
|
||||
|
||||
graph.weightedNodes.push(new Point(3, 3));
|
||||
graph.weightedNodes.push(new Point(3, 4));
|
||||
graph.weightedNodes.push(new Point(4, 3));
|
||||
graph.weightedNodes.push(new Point(4, 4));
|
||||
graph.weightedNodes.push(new Vector2(3, 3));
|
||||
graph.weightedNodes.push(new Vector2(3, 4));
|
||||
graph.weightedNodes.push(new Vector2(4, 3));
|
||||
graph.weightedNodes.push(new Vector2(4, 4));
|
||||
|
||||
let path = graph.search(new Point(3, 4), new Point(15, 17));
|
||||
let path = graph.search(new Vector2(3, 4), new Vector2(15, 17));
|
||||
console.log(path);
|
||||
}
|
||||
|
||||
public astarTest() {
|
||||
let graph = new AstarGridGraph(20, 20);
|
||||
let graph = new AstarGridGraph(30, 30);
|
||||
|
||||
graph.weightedNodes.push(new Point(3, 3));
|
||||
graph.weightedNodes.push(new Point(3, 4));
|
||||
graph.weightedNodes.push(new Point(4, 3));
|
||||
graph.weightedNodes.push(new Point(4, 4));
|
||||
// graph.weightedNodes.push(new Vector2(3, 3));
|
||||
// graph.weightedNodes.push(new Vector2(3, 4));
|
||||
// graph.weightedNodes.push(new Vector2(4, 3));
|
||||
// graph.weightedNodes.push(new Vector2(4, 4));
|
||||
|
||||
let path = graph.search(new Point(3, 4), new Point(15, 17));
|
||||
console.log(path);
|
||||
let startTime = egret.getTimer();
|
||||
let path = graph.search(new Vector2(1, 1), new Vector2(29, 29));
|
||||
console.log(egret.getTimer() - startTime);
|
||||
}
|
||||
}
|
||||
@@ -37,17 +37,17 @@ class PlayerController extends Component {
|
||||
let camera = SceneManager.scene.camera;
|
||||
let moveLeft: number = 0;
|
||||
let moveRight: number = 0;
|
||||
let speed = 200;
|
||||
let speed = 100;
|
||||
let worldPos = Input.touchPosition;
|
||||
if (worldPos.x < this.spriteRenderer.x){
|
||||
if (worldPos.x < this.spriteRenderer.localPosition.x){
|
||||
moveLeft = -1;
|
||||
} else if(worldPos.x > this.spriteRenderer.x){
|
||||
} else if(worldPos.x > this.spriteRenderer.localPosition.x){
|
||||
moveLeft = 1;
|
||||
}
|
||||
|
||||
if (worldPos.y < this.spriteRenderer.y){
|
||||
if (worldPos.y < this.spriteRenderer.localPosition.y){
|
||||
moveRight = -1;
|
||||
} else if(worldPos.y > this.spriteRenderer.y){
|
||||
} else if(worldPos.y > this.spriteRenderer.localPosition.y){
|
||||
moveRight = 1;
|
||||
}
|
||||
this.mover.move(new Vector2(moveLeft * speed * Time.deltaTime, moveRight * speed * Time.deltaTime));
|
||||
|
||||
@@ -32,7 +32,7 @@ egret_native.egretStart = function () {
|
||||
//The following is automatically modified, please do not modify
|
||||
//----auto option start----
|
||||
entryClassName: "Main",
|
||||
frameRate: 30,
|
||||
frameRate: 60,
|
||||
scaleMode: "fixedWidth",
|
||||
contentWidth: 640,
|
||||
contentHeight: 1136,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
}
|
||||
Vendored
+553
-137
File diff suppressed because it is too large
Load Diff
+2413
-433
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
-2452
File diff suppressed because it is too large
Load Diff
Vendored
+3823
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,12 @@
|
||||
* 计算路径给定的IAstarGraph和开始/目标位置
|
||||
*/
|
||||
class AStarPathfinder {
|
||||
/**
|
||||
* 尽可能从开始到目标找到一条路径。如果没有找到路径,则返回null。
|
||||
* @param graph
|
||||
* @param start
|
||||
* @param goal
|
||||
*/
|
||||
public static search<T>(graph: IAstarGraph<T>, start: T, goal: T){
|
||||
let foundPath = false;
|
||||
let cameFrom = new Map<T, T>();
|
||||
@@ -60,6 +66,12 @@ class AStarPathfinder {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从cameFrom字典重新构造路径
|
||||
* @param cameFrom
|
||||
* @param start
|
||||
* @param goal
|
||||
*/
|
||||
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
|
||||
let path = [];
|
||||
let current = goal;
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
* 基本静态网格图与A*一起使用
|
||||
* 将walls添加到walls HashSet,并将加权节点添加到weightedNodes
|
||||
*/
|
||||
class AstarGridGraph implements IAstarGraph<Point> {
|
||||
public dirs: Point[] = [
|
||||
new Point(1, 0),
|
||||
new Point(0, -1),
|
||||
new Point(-1, 0),
|
||||
new Point(0, 1)
|
||||
class AstarGridGraph implements IAstarGraph<Vector2> {
|
||||
public dirs: Vector2[] = [
|
||||
new Vector2(1, 0),
|
||||
new Vector2(0, -1),
|
||||
new Vector2(-1, 0),
|
||||
new Vector2(0, 1)
|
||||
];
|
||||
|
||||
public walls: Point[] = [];
|
||||
public weightedNodes: Point[] = [];
|
||||
public walls: Vector2[] = [];
|
||||
public weightedNodes: Vector2[] = [];
|
||||
public defaultWeight: number = 1;
|
||||
public weightedNodeWeight = 5;
|
||||
|
||||
private _width;
|
||||
private _height;
|
||||
private _neighbors: Point[] = new Array(4);
|
||||
private _neighbors: Vector2[] = new Array(4);
|
||||
|
||||
constructor(width: number, height: number){
|
||||
this._width = width;
|
||||
@@ -28,27 +28,32 @@ class AstarGridGraph implements IAstarGraph<Point> {
|
||||
* 确保节点在网格图的边界内
|
||||
* @param node
|
||||
*/
|
||||
public isNodeInBounds(node: Point): boolean {
|
||||
public isNodeInBounds(node: Vector2): boolean {
|
||||
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查节点是否可以通过。墙壁是不可逾越的。
|
||||
* 检查节点是否可以通过。walls是不可逾越的。
|
||||
* @param node
|
||||
*/
|
||||
public isNodePassable(node: Point): boolean {
|
||||
public isNodePassable(node: Vector2): boolean {
|
||||
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
|
||||
}
|
||||
|
||||
public search(start: Point, goal: Point){
|
||||
/**
|
||||
* 调用AStarPathfinder.search的快捷方式
|
||||
* @param start
|
||||
* @param goal
|
||||
*/
|
||||
public search(start: Vector2, goal: Vector2){
|
||||
return AStarPathfinder.search(this, start, goal);
|
||||
}
|
||||
|
||||
public getNeighbors(node: Point): Point[] {
|
||||
public getNeighbors(node: Vector2): Vector2[] {
|
||||
this._neighbors.length = 0;
|
||||
|
||||
this.dirs.forEach(dir => {
|
||||
let next = new Point(node.x + dir.x, node.y + dir.y);
|
||||
let next = new Vector2(node.x + dir.x, node.y + dir.y);
|
||||
if (this.isNodeInBounds(next) && this.isNodePassable(next))
|
||||
this._neighbors.push(next);
|
||||
});
|
||||
@@ -56,11 +61,11 @@ class AstarGridGraph implements IAstarGraph<Point> {
|
||||
return this._neighbors;
|
||||
}
|
||||
|
||||
public cost(from: Point, to: Point): number {
|
||||
public cost(from: Vector2, to: Vector2): number {
|
||||
return this.weightedNodes.find((p)=> JSON.stringify(p) == JSON.stringify(to)) ? this.weightedNodeWeight : this.defaultWeight;
|
||||
}
|
||||
|
||||
public heuristic(node: Point, goal: Point) {
|
||||
public heuristic(node: Vector2, goal: Vector2) {
|
||||
return Math.abs(node.x - goal.x) + Math.abs(node.y - goal.y);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
/**
|
||||
* graph的接口,可以提供给AstarPathfinder.search方法
|
||||
*/
|
||||
interface IAstarGraph<T> {
|
||||
/**
|
||||
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点
|
||||
* @param node
|
||||
*/
|
||||
getNeighbors(node: T): Array<T>;
|
||||
/**
|
||||
* 计算从从from到to的成本
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
cost(from: T, to: T): number;
|
||||
/**
|
||||
* 计算从node到to的启发式。参见WeightedGridGraph了解常用的Manhatten方法。
|
||||
* @param node
|
||||
* @param goal
|
||||
*/
|
||||
heuristic(node: T, goal: T);
|
||||
}
|
||||
@@ -1,27 +1,74 @@
|
||||
/**
|
||||
* 使用堆实现最小优先级队列 O(1)复杂度
|
||||
* 这种查找速度比使用字典快5-10倍
|
||||
* 但是,由于IPriorityQueue.contains()是许多寻路算法中调用最多的方法,因此尽可能快地实现它对于我们的应用程序非常重要。
|
||||
*/
|
||||
class PriorityQueue<T extends PriorityQueueNode> {
|
||||
private _numNodes: number;
|
||||
private _nodes: T[];
|
||||
private _numNodesEverEnqueued;
|
||||
|
||||
/**
|
||||
* 实例化一个新的优先级队列
|
||||
* @param maxNodes 允许加入队列的最大节点(执行此操作将导致undefined的行为)
|
||||
*/
|
||||
constructor(maxNodes: number) {
|
||||
this._numNodes = 0;
|
||||
this._nodes = new Array(maxNodes + 1);
|
||||
this._numNodesEverEnqueued = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从队列中删除每个节点。
|
||||
* O(n)复杂度 所有尽可能少调用该方法
|
||||
*/
|
||||
public clear() {
|
||||
this._nodes.splice(1, this._numNodes);
|
||||
this._numNodes = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回队列中的节点数。
|
||||
* O(1)复杂度
|
||||
*/
|
||||
public get count() {
|
||||
return this._numNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回可同时进入此队列的最大项数。一旦你达到这个数字(即。一旦Count == MaxSize),尝试加入另一个项目将导致undefined的行为
|
||||
* O(1)复杂度
|
||||
*/
|
||||
public get maxSize() {
|
||||
return this._nodes.length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回(在O(1)中)给定节点是否在队列中
|
||||
* O (1)复杂度
|
||||
* @param node
|
||||
*/
|
||||
public contains(node: T): boolean {
|
||||
if (!node){
|
||||
console.error("node cannot be null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.queueIndex < 0 || node.queueIndex >= this._nodes.length){
|
||||
console.error("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
|
||||
return false;
|
||||
}
|
||||
|
||||
return (this._nodes[node.queueIndex] == node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点放入优先队列 较低的值放在前面 先入先出
|
||||
* 如果队列已满,则结果undefined。如果节点已经加入队列,则结果undefined。
|
||||
* O(log n)
|
||||
* @param node
|
||||
* @param priority
|
||||
*/
|
||||
public enqueue(node: T, priority: number) {
|
||||
node.priority = priority;
|
||||
this._numNodes++;
|
||||
@@ -31,12 +78,21 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
this.cascadeUp(this._nodes[this._numNodes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除队列头(具有最小优先级的节点;按插入顺序断开连接),并返回它。如果队列为空,结果undefined
|
||||
* O(log n)
|
||||
*/
|
||||
public dequeue(): T {
|
||||
let returnMe = this._nodes[1];
|
||||
this.remove(returnMe);
|
||||
return returnMe;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从队列中删除一个节点。节点不需要是队列的头。如果节点不在队列中,则结果未定义。如果不确定,首先检查Contains()
|
||||
* O(log n)
|
||||
* @param node
|
||||
*/
|
||||
public remove(node: T) {
|
||||
if (node.queueIndex == this._numNodes) {
|
||||
this._nodes[this._numNodes] = null;
|
||||
@@ -52,6 +108,9 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
this.onNodeUpdated(formerLastNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查以确保队列仍然处于有效状态。用于测试/调试队列。
|
||||
*/
|
||||
public isValidQueue(): boolean {
|
||||
for (let i = 1; i < this._nodes.length; i++) {
|
||||
if (this._nodes[i]) {
|
||||
@@ -71,24 +130,29 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
}
|
||||
|
||||
private onNodeUpdated(node: T) {
|
||||
// 将更新后的节点按适当的方式向上或向下冒泡
|
||||
let parentIndex = Math.floor(node.queueIndex / 2);
|
||||
let parentNode = this._nodes[parentIndex];
|
||||
|
||||
if (parentIndex > 0 && this.hasHigherPriority(node, parentNode)) {
|
||||
this.cascadeUp(node);
|
||||
} else {
|
||||
// 注意,如果parentNode == node(即节点是根),则将调用CascadeDown。
|
||||
this.cascadeDown(node);
|
||||
}
|
||||
}
|
||||
|
||||
private cascadeDown(node: T) {
|
||||
// 又名Heapify-down
|
||||
let newParent: T;
|
||||
let finalQueueIndex = node.queueIndex;
|
||||
while (true) {
|
||||
newParent = node;
|
||||
let childLeftIndex = 2 * finalQueueIndex;
|
||||
|
||||
// 检查左子节点的优先级是否高于当前节点
|
||||
if (childLeftIndex > this._numNodes) {
|
||||
// 这可以放在循环之外,但是我们必须检查newParent != node两次
|
||||
node.queueIndex = finalQueueIndex;
|
||||
this._nodes[finalQueueIndex] = node;
|
||||
break;
|
||||
@@ -99,6 +163,7 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
newParent = childLeft;
|
||||
}
|
||||
|
||||
// 检查右子节点的优先级是否高于当前节点或左子节点
|
||||
let childRightIndex = childLeftIndex + 1;
|
||||
if (childRightIndex <= this._numNodes) {
|
||||
let childRight = this._nodes[childRightIndex];
|
||||
@@ -107,13 +172,17 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果其中一个子节点具有更高(更小)的优先级,则交换并继续级联
|
||||
if (newParent != node) {
|
||||
// 将新的父节点移动到它的新索引
|
||||
// 节点将被移动一次,这样做比调用Swap()少一个赋值操作。
|
||||
this._nodes[finalQueueIndex] = newParent;
|
||||
|
||||
let temp = newParent.queueIndex;
|
||||
newParent.queueIndex = finalQueueIndex;
|
||||
finalQueueIndex = temp;
|
||||
} else {
|
||||
// 参见上面的笔记
|
||||
node.queueIndex = finalQueueIndex;
|
||||
this._nodes[finalQueueIndex] = node;
|
||||
break;
|
||||
@@ -121,13 +190,20 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当没有内联时,性能会稍微好一些
|
||||
* @param node
|
||||
*/
|
||||
private cascadeUp(node: T) {
|
||||
// 又名Heapify-up
|
||||
let parent = Math.floor(node.queueIndex / 2);
|
||||
while (parent >= 1) {
|
||||
let parentNode = this._nodes[parent];
|
||||
if (this.hasHigherPriority(parentNode, node))
|
||||
break;
|
||||
|
||||
// 节点具有较低的优先级值,因此将其向上移动到堆中
|
||||
// 出于某种原因,使用Swap()比使用单独的操作更快,如CascadeDown()
|
||||
this.swap(node, parentNode);
|
||||
|
||||
parent = Math.floor(node.queueIndex / 2);
|
||||
@@ -135,14 +211,22 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
}
|
||||
|
||||
private swap(node1: T, node2: T) {
|
||||
// 交换节点
|
||||
this._nodes[node1.queueIndex] = node2;
|
||||
this._nodes[node2.queueIndex] = node1;
|
||||
|
||||
// 交换他们的indicies
|
||||
let temp = node1.queueIndex;
|
||||
node1.queueIndex = node2.queueIndex;
|
||||
node2.queueIndex = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果higher的优先级高于lower,则返回true,否则返回false。
|
||||
* 注意,调用HasHigherPriority(节点,节点)(即。两个参数为同一个节点)将返回false
|
||||
* @param higher
|
||||
* @param lower
|
||||
*/
|
||||
private hasHigherPriority(higher: T, lower: T) {
|
||||
return (higher.priority < lower.priority ||
|
||||
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
///<reference path="../../../Math/Point.ts" />
|
||||
///<reference path="../../../Math/Vector2.ts" />
|
||||
/**
|
||||
* 基本的未加权网格图形用于BreadthFirstPathfinder
|
||||
*/
|
||||
class UnweightedGridGraph implements IUnweightedGraph<Point> {
|
||||
private static readonly CARDINAL_DIRS: Point[] = [
|
||||
new Point(1, 0),
|
||||
new Point(0, -1),
|
||||
new Point(-1, 0),
|
||||
new Point(0, -1)
|
||||
class UnweightedGridGraph implements IUnweightedGraph<Vector2> {
|
||||
private static readonly CARDINAL_DIRS: Vector2[] = [
|
||||
new Vector2(1, 0),
|
||||
new Vector2(0, -1),
|
||||
new Vector2(-1, 0),
|
||||
new Vector2(0, -1)
|
||||
];
|
||||
|
||||
private static readonly COMPASS_DIRS = [
|
||||
new Point(1, 0),
|
||||
new Point(1, -1),
|
||||
new Point(0, -1),
|
||||
new Point(-1, -1),
|
||||
new Point(-1, 0),
|
||||
new Point(-1, 1),
|
||||
new Point(0, 1),
|
||||
new Point(1, 1),
|
||||
new Vector2(1, 0),
|
||||
new Vector2(1, -1),
|
||||
new Vector2(0, -1),
|
||||
new Vector2(-1, -1),
|
||||
new Vector2(-1, 0),
|
||||
new Vector2(-1, 1),
|
||||
new Vector2(0, 1),
|
||||
new Vector2(1, 1),
|
||||
];
|
||||
|
||||
public walls: Point[] = [];
|
||||
public walls: Vector2[] = [];
|
||||
|
||||
private _width: number;
|
||||
private _hegiht: number;
|
||||
|
||||
private _dirs: Point[];
|
||||
private _neighbors: Point[] = new Array(4);
|
||||
private _dirs: Vector2[];
|
||||
private _neighbors: Vector2[] = new Array(4);
|
||||
|
||||
constructor(width: number, height: number, allowDiagonalSearch: boolean = false) {
|
||||
this._width = width;
|
||||
@@ -35,19 +35,19 @@ class UnweightedGridGraph implements IUnweightedGraph<Point> {
|
||||
this._dirs = allowDiagonalSearch ? UnweightedGridGraph.COMPASS_DIRS : UnweightedGridGraph.CARDINAL_DIRS;
|
||||
}
|
||||
|
||||
public isNodeInBounds(node: Point): boolean {
|
||||
public isNodeInBounds(node: Vector2): boolean {
|
||||
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._hegiht;
|
||||
}
|
||||
|
||||
public isNodePassable(node: Point): boolean {
|
||||
public isNodePassable(node: Vector2): boolean {
|
||||
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
|
||||
}
|
||||
|
||||
public getNeighbors(node: Point) {
|
||||
public getNeighbors(node: Vector2) {
|
||||
this._neighbors.length = 0;
|
||||
|
||||
this._dirs.forEach(dir => {
|
||||
let next = new Point(node.x + dir.x, node.y + dir.y);
|
||||
let next = new Vector2(node.x + dir.x, node.y + dir.y);
|
||||
if (this.isNodeInBounds(next) && this.isNodePassable(next))
|
||||
this._neighbors.push(next);
|
||||
});
|
||||
@@ -55,7 +55,7 @@ class UnweightedGridGraph implements IUnweightedGraph<Point> {
|
||||
return this._neighbors;
|
||||
}
|
||||
|
||||
public search(start: Point, goal: Point): Point[] {
|
||||
public search(start: Vector2, goal: Vector2): Vector2[] {
|
||||
return BreadthFirstPathfinder.search(this, start, goal);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,35 @@
|
||||
///<reference path="../../../Math/Point.ts" />
|
||||
///<reference path="../../../Math/Vector2.ts" />
|
||||
/**
|
||||
* 支持一种加权节点的基本网格图
|
||||
*/
|
||||
class WeightedGridGraph implements IWeightedGraph<Point> {
|
||||
class WeightedGridGraph implements IWeightedGraph<Vector2> {
|
||||
public static readonly CARDINAL_DIRS = [
|
||||
new Point(1, 0),
|
||||
new Point(0, -1),
|
||||
new Point(-1, 0),
|
||||
new Point(0, 1)
|
||||
new Vector2(1, 0),
|
||||
new Vector2(0, -1),
|
||||
new Vector2(-1, 0),
|
||||
new Vector2(0, 1)
|
||||
];
|
||||
|
||||
private static readonly COMPASS_DIRS = [
|
||||
new Point(1, 0),
|
||||
new Point(1, -1),
|
||||
new Point(0, -1),
|
||||
new Point(-1, -1),
|
||||
new Point(-1, 0),
|
||||
new Point(-1, 1),
|
||||
new Point(0, 1),
|
||||
new Point(1, 1),
|
||||
new Vector2(1, 0),
|
||||
new Vector2(1, -1),
|
||||
new Vector2(0, -1),
|
||||
new Vector2(-1, -1),
|
||||
new Vector2(-1, 0),
|
||||
new Vector2(-1, 1),
|
||||
new Vector2(0, 1),
|
||||
new Vector2(1, 1),
|
||||
];
|
||||
|
||||
public walls: Point[] = [];
|
||||
public weightedNodes: Point[] = [];
|
||||
public walls: Vector2[] = [];
|
||||
public weightedNodes: Vector2[] = [];
|
||||
public defaultWeight = 1;
|
||||
public weightedNodeWeight = 5;
|
||||
|
||||
private _width: number;
|
||||
private _height: number;
|
||||
private _dirs: Point[];
|
||||
private _neighbors: Point[] = new Array(4);
|
||||
private _dirs: Vector2[];
|
||||
private _neighbors: Vector2[] = new Array(4);
|
||||
|
||||
constructor(width: number, height: number, allowDiagonalSearch: boolean = false){
|
||||
this._width = width;
|
||||
@@ -37,23 +37,23 @@ class WeightedGridGraph implements IWeightedGraph<Point> {
|
||||
this._dirs = allowDiagonalSearch ? WeightedGridGraph.COMPASS_DIRS : WeightedGridGraph.CARDINAL_DIRS;
|
||||
}
|
||||
|
||||
public isNodeInBounds(node: Point){
|
||||
public isNodeInBounds(node: Vector2){
|
||||
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
|
||||
}
|
||||
|
||||
public isNodePassable(node: Point): boolean {
|
||||
public isNodePassable(node: Vector2): boolean {
|
||||
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
|
||||
}
|
||||
|
||||
public search(start: Point, goal: Point){
|
||||
public search(start: Vector2, goal: Vector2){
|
||||
return WeightedPathfinder.search(this, start, goal);
|
||||
}
|
||||
|
||||
public getNeighbors(node: Point): Point[]{
|
||||
public getNeighbors(node: Vector2): Vector2[]{
|
||||
this._neighbors.length = 0;
|
||||
|
||||
this._dirs.forEach(dir => {
|
||||
let next = new Point(node.x + dir.x, node.y + dir.y);
|
||||
let next = new Vector2(node.x + dir.x, node.y + dir.y);
|
||||
if (this.isNodeInBounds(next) && this.isNodePassable(next))
|
||||
this._neighbors.push(next);
|
||||
});
|
||||
@@ -61,7 +61,7 @@ class WeightedGridGraph implements IWeightedGraph<Point> {
|
||||
return this._neighbors;
|
||||
}
|
||||
|
||||
public cost(from: Point, to: Point): number{
|
||||
public cost(from: Vector2, to: Vector2): number{
|
||||
return this.weightedNodes.find(t => JSON.stringify(t) == JSON.stringify(to)) ? this.weightedNodeWeight : this.defaultWeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
class Debug {
|
||||
private static _debugDrawItems: DebugDrawItem[] = [];
|
||||
|
||||
public static drawHollowRect(rectanle: Rectangle, color: number, duration = 0){
|
||||
this._debugDrawItems.push(new DebugDrawItem(rectanle, color, duration));
|
||||
}
|
||||
|
||||
public static render(){
|
||||
if (this._debugDrawItems.length > 0){
|
||||
let debugShape = new egret.Shape();
|
||||
if (SceneManager.scene){
|
||||
SceneManager.scene.addChild(debugShape);
|
||||
}
|
||||
|
||||
for (let i = this._debugDrawItems.length - 1; i >= 0; i --){
|
||||
let item = this._debugDrawItems[i];
|
||||
if (item.draw(debugShape))
|
||||
this._debugDrawItems.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
enum DebugDrawType {
|
||||
line,
|
||||
hollowRectangle,
|
||||
pixel,
|
||||
text
|
||||
}
|
||||
|
||||
class DebugDrawItem {
|
||||
public rectangle: Rectangle;
|
||||
public color: number;
|
||||
public duration: number;
|
||||
public drawType: DebugDrawType;
|
||||
public text: string;
|
||||
public start: Vector2;
|
||||
public end: Vector2;
|
||||
public x: number;
|
||||
public y: number;
|
||||
public size: number;
|
||||
|
||||
constructor(rectangle: Rectangle, color: number, duration: number){
|
||||
this.rectangle = rectangle;
|
||||
this.color = color;
|
||||
this.duration = duration;
|
||||
this.drawType = DebugDrawType.hollowRectangle;
|
||||
}
|
||||
|
||||
public draw(shape: egret.Shape): boolean{
|
||||
switch (this.drawType){
|
||||
case DebugDrawType.line:
|
||||
DrawUtils.drawLine(shape, this.start, this.end, this.color);
|
||||
break;
|
||||
case DebugDrawType.hollowRectangle:
|
||||
DrawUtils.drawHollowRect(shape, this.rectangle, this.color);
|
||||
break;
|
||||
case DebugDrawType.pixel:
|
||||
DrawUtils.drawPixel(shape, new Vector2(this.x, this.y), this.color, this.size);
|
||||
break;
|
||||
case DebugDrawType.text:
|
||||
break;
|
||||
}
|
||||
|
||||
this.duration -= Time.deltaTime;
|
||||
return this.duration < 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ abstract class Component extends egret.DisplayObjectContainer {
|
||||
public entity: Entity;
|
||||
private _enabled: boolean = true;
|
||||
public updateInterval: number = 1;
|
||||
/** 允许用户为实体存入信息 */
|
||||
public userData: any;
|
||||
private _updateOrder = 0;
|
||||
|
||||
public get enabled(){
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
@@ -11,6 +14,10 @@ abstract class Component extends egret.DisplayObjectContainer {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
public get localPosition(){
|
||||
return new Vector2(this.entity.x + this.x, this.entity.y + this.y);
|
||||
}
|
||||
|
||||
public setEnabled(isEnabled: boolean){
|
||||
if (this._enabled != isEnabled){
|
||||
this._enabled = isEnabled;
|
||||
@@ -25,8 +32,23 @@ abstract class Component extends egret.DisplayObjectContainer {
|
||||
return this;
|
||||
}
|
||||
|
||||
public initialize(){
|
||||
/** 更新此实体上组件的顺序 */
|
||||
public get updateOrder(){
|
||||
return this._updateOrder;
|
||||
}
|
||||
/** 更新此实体上组件的顺序 */
|
||||
public set updateOrder(value: number){
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
public setUpdateOrder(updateOrder: number){
|
||||
if (this._updateOrder != updateOrder){
|
||||
this._updateOrder = updateOrder;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public initialize(){
|
||||
}
|
||||
|
||||
public onAddedToEntity(){
|
||||
@@ -45,14 +67,22 @@ abstract class Component extends egret.DisplayObjectContainer {
|
||||
|
||||
}
|
||||
|
||||
public update(){
|
||||
|
||||
}
|
||||
|
||||
public debugRender(){
|
||||
|
||||
}
|
||||
|
||||
public update(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体的位置改变时调用。这允许组件知道它们由于父实体的移动而移动了。
|
||||
* @param comp
|
||||
*/
|
||||
public onEntityTransformChanged(comp: TransformComponent){
|
||||
|
||||
}
|
||||
|
||||
/** 内部使用 运行时不应该调用 */
|
||||
public registerComponent(){
|
||||
this.entity.componentBits.set(ComponentTypeManager.getIndexFor(this), false);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
///<reference path="../Component.ts"/>
|
||||
class Camera extends Component {
|
||||
private _zoom;
|
||||
private _origin: Vector2 = Vector2.zero;
|
||||
@@ -6,6 +5,7 @@ class Camera extends Component {
|
||||
private _minimumZoom = 0.3;
|
||||
private _maximumZoom = 3;
|
||||
|
||||
private _position: Vector2 = Vector2.zero;
|
||||
/**
|
||||
* 如果相机模式为cameraWindow 则会进行缓动移动
|
||||
* 该值为移动速度
|
||||
@@ -67,11 +67,24 @@ class Camera extends Component {
|
||||
}
|
||||
|
||||
public get position(){
|
||||
return this.entity.position;
|
||||
return this._position;
|
||||
}
|
||||
|
||||
public set position(value: Vector2){
|
||||
this.entity.position = value;
|
||||
this._position = value;
|
||||
}
|
||||
|
||||
public get x(){
|
||||
return this._position.x;
|
||||
}
|
||||
public set x(value: number){
|
||||
this._position.x = value;
|
||||
}
|
||||
public get y(){
|
||||
return this._position.y;
|
||||
}
|
||||
public set y(value: number){
|
||||
this._position.y = value;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
||||
@@ -8,12 +8,16 @@ class BoxCollider extends Collider {
|
||||
this.setWidth(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的宽度
|
||||
* @param width
|
||||
*/
|
||||
public setWidth(width: number): BoxCollider{
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width){
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, box.height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
@@ -29,20 +33,28 @@ class BoxCollider extends Collider {
|
||||
this.setHeight(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的高度
|
||||
* @param height
|
||||
*/
|
||||
public setHeight(height: number){
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (height != box.height){
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(box.width, height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 零参数构造函数要求RenderableComponent在实体上,这样碰撞器可以在实体被添加到场景时调整自身的大小。
|
||||
*/
|
||||
constructor(){
|
||||
super();
|
||||
|
||||
// 我们在这里插入一个1x1框作为占位符,直到碰撞器在下一阵被添加到实体并可以获得更精确的自动调整大小数据
|
||||
this.shape = new Box(1, 1);
|
||||
this._colliderRequiresAutoSizing = true;
|
||||
}
|
||||
@@ -51,8 +63,8 @@ class BoxCollider extends Collider {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width || height != box.height){
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
class CircleCollider extends Collider {
|
||||
public get radius(): number{
|
||||
return (this.shape as Circle).radius;
|
||||
}
|
||||
public set radius(value: number){
|
||||
this.setRadius(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个有半径的圆
|
||||
*
|
||||
* @param radius
|
||||
*/
|
||||
constructor(radius?: number){
|
||||
super();
|
||||
|
||||
if (radius)
|
||||
this._colliderRequiresAutoSizing = true;
|
||||
// 我们在这里插入一个1px的圆圈作为占位符
|
||||
// 直到碰撞器被添加到实体并可以获得更精确的自动调整大小数据的下一帧
|
||||
this.shape = new Circle(radius ? radius : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置圆的半径
|
||||
* @param radius
|
||||
*/
|
||||
public setRadius(radius: number): CircleCollider{
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let circle = this.shape as Circle;
|
||||
if (radius != circle.radius){
|
||||
circle.radius = radius;
|
||||
circle._originalRadius = radius;
|
||||
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,89 @@
|
||||
abstract class Collider extends Component{
|
||||
abstract class Collider extends Component {
|
||||
/** 对撞机的基本形状 */
|
||||
public shape: Shape;
|
||||
/** 在处理冲突时,physicsLayer可以用作过滤器。Flags类有帮助位掩码的方法。 */
|
||||
public physicsLayer = 1 << 0;
|
||||
/** 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件 */
|
||||
public isTrigger: boolean;
|
||||
public registeredPhysicsBounds: Rectangle;
|
||||
public shouldColliderScaleAndRotationWithTransform = true;
|
||||
/**
|
||||
* 这个对撞机在物理系统注册时的边界。
|
||||
* 存储这个允许我们始终能够安全地从物理系统中移除对撞机,即使它在试图移除它之前已经被移动了。
|
||||
*/
|
||||
public registeredPhysicsBounds: Rectangle = new Rectangle();
|
||||
/** 如果为true,碰撞器将根据附加的变换缩放和旋转 */
|
||||
public shouldColliderScaleAndRotateWithTransform = true;
|
||||
/** 默认为所有层。 */
|
||||
public collidesWithLayers = Physics.allLayers;
|
||||
|
||||
public _localOffsetLength: number;
|
||||
public _isPositionDirty = true;
|
||||
public _isRotationDirty = true;
|
||||
/** 标记来跟踪我们的实体是否被添加到场景中 */
|
||||
protected _isParentEntityAddedToScene;
|
||||
protected _colliderRequiresAutoSizing;
|
||||
protected _localOffset: Vector2 = new Vector2(0, 0);
|
||||
/** 标记来记录我们是否注册了物理系统 */
|
||||
protected _isColliderRegistered;
|
||||
|
||||
public get bounds(): Rectangle {
|
||||
if (this._isPositionDirty || this._isRotationDirty){
|
||||
this.shape.recalculateBounds(this);
|
||||
this._isPositionDirty = this._isRotationDirty = false;
|
||||
}
|
||||
|
||||
this.shape.recalculateBounds(this);
|
||||
return this.shape.bounds;
|
||||
}
|
||||
|
||||
public get localOffset(){
|
||||
public get localOffset() {
|
||||
return this._localOffset;
|
||||
}
|
||||
|
||||
public set localOffset(value: Vector2){
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器的最终位置。这允许您向一个实体添加多个碰撞器并分别定位它们。
|
||||
*/
|
||||
public set localOffset(value: Vector2) {
|
||||
this.setLocalOffset(value);
|
||||
}
|
||||
|
||||
public setLocalOffset(offset: Vector2){
|
||||
if (this._localOffset != offset){
|
||||
public setLocalOffset(offset: Vector2) {
|
||||
if (this._localOffset != offset) {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
this._localOffset = offset;
|
||||
this._localOffsetLength = this._localOffset.length();
|
||||
this._isPositionDirty = true;
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
}
|
||||
}
|
||||
|
||||
public registerColliderWithPhysicsSystem(){
|
||||
if (this._isParentEntityAddedToScene && !this._isColliderRegistered){
|
||||
/**
|
||||
* 父实体会在不同的时间调用它(当添加到场景,启用,等等)
|
||||
*/
|
||||
public registerColliderWithPhysicsSystem() {
|
||||
// 如果在将我们添加到实体之前更改了origin等属性,则实体可以为null
|
||||
if (this._isParentEntityAddedToScene && !this._isColliderRegistered) {
|
||||
Physics.addCollider(this);
|
||||
this._isColliderRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
public unregisterColliderWithPhysicsSystem(){
|
||||
if (this._isParentEntityAddedToScene && this._isColliderRegistered){
|
||||
/**
|
||||
* 父实体会在不同的时候调用它(从场景中移除,禁用,等等)
|
||||
*/
|
||||
public unregisterColliderWithPhysicsSystem() {
|
||||
if (this._isParentEntityAddedToScene && this._isColliderRegistered) {
|
||||
Physics.removeCollider(this);
|
||||
}
|
||||
this._isColliderRegistered = false;
|
||||
}
|
||||
|
||||
public overlaps(other: Collider){
|
||||
/**
|
||||
* 检查这个形状是否与物理系统中的其他对撞机重叠
|
||||
* @param other
|
||||
*/
|
||||
public overlaps(other: Collider) {
|
||||
return this.shape.overlaps(other.shape);
|
||||
}
|
||||
|
||||
public collidesWith(collider: Collider, motion: Vector2){
|
||||
/**
|
||||
* 检查这个与运动应用的碰撞器(移动向量)是否与碰撞器碰撞。如果是这样,将返回true,并且结果将填充碰撞数据。
|
||||
* @param collider
|
||||
* @param motion
|
||||
*/
|
||||
public collidesWith(collider: Collider, motion: Vector2) {
|
||||
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
|
||||
let oldPosition = this.shape.position;
|
||||
this.shape.position = Vector2.add(this.shape.position, motion);
|
||||
|
||||
@@ -67,49 +91,71 @@ abstract class Collider extends Component{
|
||||
if (result)
|
||||
result.collider = collider;
|
||||
|
||||
// 将图形位置返回到检查前的位置
|
||||
this.shape.position = oldPosition;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public onAddedToEntity(){
|
||||
if (this._colliderRequiresAutoSizing){
|
||||
if (!(this instanceof BoxCollider)){
|
||||
public onAddedToEntity() {
|
||||
if (this._colliderRequiresAutoSizing) {
|
||||
if (!(this instanceof BoxCollider || this instanceof CircleCollider)) {
|
||||
console.error("Only box and circle colliders can be created automatically");
|
||||
}
|
||||
|
||||
let renderable = this.entity.getComponent<RenderableComponent>(RenderableComponent);
|
||||
if (renderable){
|
||||
let renderbaleBounds = renderable.bounds;
|
||||
if (renderable) {
|
||||
let bounds = renderable.bounds;
|
||||
|
||||
let width = renderbaleBounds.width / this.entity.scale.x;
|
||||
let height = renderbaleBounds.height / this.entity.scale.y;
|
||||
// 这里我们需要大小*反尺度,因为当我们自动调整碰撞器的大小时,它需要没有缩放的渲染
|
||||
let width = bounds.width / this.entity.scale.x;
|
||||
let height = bounds.height / this.entity.scale.y;
|
||||
|
||||
if (this instanceof BoxCollider){
|
||||
let boxCollider = this as BoxCollider;
|
||||
// 圆碰撞器需要特别注意原点
|
||||
if (this instanceof CircleCollider){
|
||||
let circleCollider = this as CircleCollider;
|
||||
circleCollider.radius = Math.max(width, height) * 0.5;
|
||||
|
||||
this.localOffset = bounds.location;
|
||||
} else {
|
||||
let boxCollider = this;
|
||||
boxCollider.width = width;
|
||||
boxCollider.height = height;
|
||||
|
||||
this.localOffset = Vector2.subtract(renderbaleBounds.center, this.entity.position);
|
||||
this.localOffset = bounds.location;
|
||||
}
|
||||
} else {
|
||||
console.warn("Collider has no shape and no RenderableComponent. Can't figure out how to size it.");
|
||||
}
|
||||
}
|
||||
|
||||
this._isParentEntityAddedToScene = true;
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
public onRemovedFromEntity(){
|
||||
|
||||
public onRemovedFromEntity() {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
this._isParentEntityAddedToScene = false;
|
||||
}
|
||||
|
||||
public onEnabled(){
|
||||
public onEnabled() {
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
this._isPositionDirty = this._isRotationDirty = true;
|
||||
}
|
||||
|
||||
public onDisabled(){
|
||||
public onDisabled() {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: TransformComponent) {
|
||||
if (this._isColliderRegistered)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
public update(){
|
||||
let renderable = this.entity.getComponent<RenderableComponent>(RenderableComponent);
|
||||
if (renderable){
|
||||
this.$setX(renderable.x + this.localOffset.x);
|
||||
this.$setY(renderable.y + this.localOffset.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 多边形应该以顺时针方式定义
|
||||
*/
|
||||
class PolygonCollider extends Collider {
|
||||
/**
|
||||
* 如果这些点没有居中,它们将以localOffset的差异为居中。
|
||||
* @param points
|
||||
*/
|
||||
constructor(points: Vector2[]){
|
||||
super();
|
||||
|
||||
// 第一点和最后一点决不能相同。我们想要一个开放的多边形
|
||||
let isPolygonClosed = points[0] == points[points.length - 1];
|
||||
|
||||
// 最后一个移除
|
||||
if (isPolygonClosed)
|
||||
points.splice(points.length - 1, 1);
|
||||
|
||||
let center = Polygon.findPolygonCenter(points);
|
||||
this.setLocalOffset(center);
|
||||
Polygon.recenterPolygonVerts(points);
|
||||
this.shape = new Polygon(points);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* 辅助类说明了一种处理移动的方法,它考虑了包括触发器在内的所有冲突。
|
||||
* ITriggerListener接口用于管理对移动过程中违反的任何触发器的回调。
|
||||
* 一个物体只能通过移动器移动。要正确报告触发器的move方法。
|
||||
*
|
||||
* 请注意,多个移动者相互交互将多次调用ITriggerListener。
|
||||
*/
|
||||
class Mover extends Component {
|
||||
private _triggerHelper: ColliderTriggerHelper;
|
||||
|
||||
@@ -5,6 +12,10 @@ class Mover extends Component {
|
||||
this._triggerHelper = new ColliderTriggerHelper(this.entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算修改运动矢量的运动,以考虑移动时可能发生的碰撞
|
||||
* @param motion
|
||||
*/
|
||||
public calculateMovement(motion: Vector2){
|
||||
let collisionResult = new CollisionResult();
|
||||
|
||||
@@ -12,27 +23,35 @@ class Mover extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 移动所有的非触发碰撞器并获得最近的碰撞
|
||||
let colliders: Collider[] = this.entity.getComponents(Collider);
|
||||
for (let i = 0; i < colliders.length; i ++){
|
||||
let collider = colliders[i];
|
||||
|
||||
// 不检测触发器 在我们移动后会重新访问它
|
||||
if (collider.isTrigger)
|
||||
continue;
|
||||
|
||||
// 获取我们在新位置可能发生碰撞的任何东西
|
||||
let bounds = collider.bounds;
|
||||
bounds.x += motion.x;
|
||||
bounds.y += motion.y;
|
||||
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, bounds, collider.collidesWithLayers);
|
||||
let boxcastResult = Physics.boxcastBroadphaseExcludingSelf(collider, bounds, collider.collidesWithLayers);
|
||||
bounds = boxcastResult.bounds;
|
||||
let neighbors = boxcastResult.tempHashSet;
|
||||
|
||||
for (let j = 0; j < neighbors.length; j ++){
|
||||
let neighbor = neighbors[j];
|
||||
// 不检测触发器
|
||||
if (neighbor.isTrigger)
|
||||
continue;
|
||||
|
||||
let _internalcollisionResult = collider.collidesWith(neighbor, motion);
|
||||
if (_internalcollisionResult){
|
||||
// 如果碰撞 则退回之前的移动量
|
||||
motion = Vector2.subtract(motion, _internalcollisionResult.minimumTranslationVector);
|
||||
|
||||
// 如果我们碰到多个对象,为了简单起见,只取第一个。
|
||||
if (_internalcollisionResult.collider){
|
||||
collisionResult = _internalcollisionResult;
|
||||
}
|
||||
@@ -42,18 +61,30 @@ class Mover extends Component {
|
||||
|
||||
ListPool.free(colliders);
|
||||
|
||||
return collisionResult;
|
||||
return {collisionResult: collisionResult, motion: motion};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将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
|
||||
*/
|
||||
public move(motion: Vector2){
|
||||
let collisionResult = this.calculateMovement(motion);
|
||||
let movementResult = this.calculateMovement(motion);
|
||||
let collisionResult = movementResult.collisionResult;
|
||||
motion = movementResult.motion;
|
||||
|
||||
this.applyMovement(motion);
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 只向itriggerlistener报告冲突的移动器
|
||||
* 该对象将始终移动完整的距离
|
||||
*/
|
||||
class ProjectileMover extends Component {
|
||||
private _tempTriggerList: ITriggerListener[] = [];
|
||||
private _collider: Collider;
|
||||
|
||||
public onAddedToEntity(){
|
||||
this._collider = this.entity.getComponent<Collider>(Collider);
|
||||
if (!this._collider)
|
||||
console.warn("ProjectileMover has no Collider. ProjectilMover requires a Collider!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动考虑碰撞的实体
|
||||
* @param motion
|
||||
*/
|
||||
public move(motion: Vector2): boolean{
|
||||
if (!this._collider)
|
||||
return false;
|
||||
|
||||
let didCollide = false;
|
||||
|
||||
// 获取我们在新位置可能发生碰撞的任何东西
|
||||
this.entity.position = Vector2.add(this.entity.position, motion);
|
||||
|
||||
// 获取任何可能在新位置发生碰撞的东西
|
||||
let neighbors = Physics.boxcastBroadphase(this._collider.bounds, this._collider.collidesWithLayers);
|
||||
for (let i = 0; i < neighbors.colliders.length; i ++){
|
||||
let neighbor = neighbors.colliders[i];
|
||||
if (this._collider.overlaps(neighbor)){
|
||||
didCollide = true;
|
||||
this.notifyTriggerListeners(this._collider, neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
private notifyTriggerListeners(self: Collider, other: Collider){
|
||||
// 通知我们重叠的碰撞器实体上的任何侦听器
|
||||
other.entity.getComponents("ITriggerListener", this._tempTriggerList);
|
||||
for (let i = 0; i < this._tempTriggerList.length; i ++){
|
||||
this._tempTriggerList[i].onTriggerEnter(self, other);
|
||||
}
|
||||
this._tempTriggerList.length = 0;
|
||||
|
||||
// 通知此实体上的任何侦听器
|
||||
this.entity.getComponents("ITriggerListener", this._tempTriggerList);
|
||||
for (let i = 0; i < this._tempTriggerList.length; i ++){
|
||||
this._tempTriggerList[i].onTriggerEnter(other, self);
|
||||
}
|
||||
this._tempTriggerList.length = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
///<reference path="./TiledSpriteRenderer.ts"/>
|
||||
class ScrollingSpriteRenderer extends TiledSpriteRenderer {
|
||||
public scrollSpeedX = 15;
|
||||
public scroolSpeedY = 0;
|
||||
private _scrollX = 0;
|
||||
private _scrollY = 0;
|
||||
|
||||
public update(){
|
||||
this._scrollX += this.scrollSpeedX * Time.deltaTime;
|
||||
this._scrollY += this.scroolSpeedY * Time.deltaTime;
|
||||
this.sourceRect.x = this._scrollX;
|
||||
this.sourceRect.y = this._scrollY;
|
||||
}
|
||||
|
||||
public render(camera: Camera) {
|
||||
if (!this.sprite)
|
||||
return;
|
||||
|
||||
super.render(camera);
|
||||
|
||||
let renderTexture = new egret.RenderTexture();
|
||||
let cacheBitmap = new egret.DisplayObjectContainer();
|
||||
cacheBitmap.removeChildren();
|
||||
cacheBitmap.addChild(this.leftTexture);
|
||||
cacheBitmap.addChild(this.rightTexture);
|
||||
|
||||
this.leftTexture.x = this.sourceRect.x;
|
||||
this.rightTexture.x = this.sourceRect.x - this.sourceRect.width;
|
||||
this.leftTexture.y = this.sourceRect.y;
|
||||
this.rightTexture.y = this.sourceRect.y;
|
||||
|
||||
cacheBitmap.cacheAsBitmap = true;
|
||||
renderTexture.drawToTexture(cacheBitmap, new egret.Rectangle(0, 0, this.sourceRect.width, this.sourceRect.height));
|
||||
|
||||
this.bitmap.texture = renderTexture;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class SpriteAnimation {
|
||||
public readonly sprites: Sprite[];
|
||||
public readonly frameRate: number;
|
||||
|
||||
constructor(sprites: Sprite[], frameRate: number){
|
||||
this.sprites = sprites;
|
||||
this.frameRate = frameRate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
///<reference path="./SpriteRenderer.ts" />
|
||||
class SpriteAnimator extends SpriteRenderer {
|
||||
/** 在动画完成时触发,包括动画名称; */
|
||||
public onAnimationCompletedEvent: Function;
|
||||
/** 动画播放速度 */
|
||||
public speed = 1;
|
||||
/** 动画的当前状态 */
|
||||
public animationState = State.none;
|
||||
/** 当前动画 */
|
||||
public currentAnimation: SpriteAnimation;
|
||||
/** 当前动画的名称 */
|
||||
public currentAnimationName: string;
|
||||
/** 当前动画的精灵数组中当前帧的索引 */
|
||||
public currentFrame: number;
|
||||
/** 检查当前动画是否正在运行 */
|
||||
public get isRunning(): boolean{
|
||||
return this.animationState == State.running;
|
||||
}
|
||||
|
||||
private _animations: Map<string, SpriteAnimation> = new Map<string, SpriteAnimation>();
|
||||
private _elapsedTime: number = 0;
|
||||
private _loopMode: LoopMode;
|
||||
|
||||
constructor(sprite?: Sprite){
|
||||
super();
|
||||
|
||||
if (sprite) this.setSprite(sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个SpriteAnimation
|
||||
* @param name
|
||||
* @param animation
|
||||
*/
|
||||
public addAnimation(name: string, animation: SpriteAnimation): SpriteAnimator{
|
||||
if (!this.sprite && animation.sprites.length > 0)
|
||||
this.setSprite(animation.sprites[0]);
|
||||
this._animations[name] = animation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以给定的名称放置动画。如果没有指定循环模式,则默认为循环
|
||||
* @param name
|
||||
* @param loopMode
|
||||
*/
|
||||
public play(name: string, loopMode: LoopMode = null){
|
||||
this.currentAnimation = this._animations[name];
|
||||
this.currentAnimationName = name;
|
||||
this.currentFrame = 0;
|
||||
this.animationState = State.running;
|
||||
|
||||
this.sprite = this.currentAnimation.sprites[0];
|
||||
this._elapsedTime = 0;
|
||||
this._loopMode = loopMode ? loopMode : LoopMode.loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查动画是否正在播放(即动画是活动的)。它可能仍然处于暂停状态)
|
||||
* @param name
|
||||
*/
|
||||
public isAnimationActive(name: string): boolean{
|
||||
return this.currentAnimation && this.currentAnimationName == name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停动画
|
||||
*/
|
||||
public pause(){
|
||||
this.animationState = State.paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* 继续动画
|
||||
*/
|
||||
public unPause(){
|
||||
this.animationState = State.running;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止当前动画并将其设为null
|
||||
*/
|
||||
public stop(){
|
||||
this.currentAnimation = null;
|
||||
this.currentAnimationName = null;
|
||||
this.currentFrame = 0;
|
||||
this.animationState = State.none;
|
||||
}
|
||||
|
||||
public update(){
|
||||
if (this.animationState != State.running || !this.currentAnimation) return;
|
||||
|
||||
let animation = this.currentAnimation;
|
||||
let secondsPerFrame = 1 / (animation.frameRate * this.speed);
|
||||
let iterationDuration = secondsPerFrame * animation.sprites.length;
|
||||
|
||||
this._elapsedTime += Time.deltaTime;
|
||||
let time = Math.abs(this._elapsedTime);
|
||||
|
||||
if (this._loopMode == LoopMode.once && time > iterationDuration ||
|
||||
this._loopMode == LoopMode.pingPongOnce && time > iterationDuration * 2){
|
||||
this.animationState = State.completed;
|
||||
this._elapsedTime = 0;
|
||||
this.currentFrame = 0;
|
||||
this.sprite = animation.sprites[this.currentFrame];
|
||||
return;
|
||||
}
|
||||
|
||||
// 弄清楚我们在哪个坐标系上
|
||||
let i = Math.floor(time / secondsPerFrame);
|
||||
let n = animation.sprites.length;
|
||||
if (n > 2 && (this._loopMode == LoopMode.pingPong || this._loopMode == LoopMode.pingPongOnce)){
|
||||
// pingpong
|
||||
let maxIndex = n - 1;
|
||||
this.currentFrame = maxIndex - Math.abs(maxIndex - i % (maxIndex * 2));
|
||||
}else{
|
||||
this.currentFrame = i % n;
|
||||
}
|
||||
|
||||
this.sprite = animation.sprites[this.currentFrame];
|
||||
}
|
||||
}
|
||||
|
||||
enum LoopMode {
|
||||
/** 在一个循环序列[A][B][C][A][B][C][A][B][C]... */
|
||||
loop,
|
||||
/** [A][B][C]然后暂停,设置时间为0 [A] */
|
||||
once,
|
||||
/** [A][B][C]。当它到达终点时,它会继续播放最后一帧,并且不会停止播放 */
|
||||
clampForever,
|
||||
/** 以一个乒乓循环的方式永远播放这个序列 [A][B][C][B][A][B][C][B]... */
|
||||
pingPong,
|
||||
/** 将顺序向前播放一次,然后返回到开始[A][B][C][B][A],然后暂停并设置时间为0 */
|
||||
pingPongOnce,
|
||||
}
|
||||
|
||||
enum State {
|
||||
none,
|
||||
running,
|
||||
paused,
|
||||
completed,
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
class SpriteRenderer extends RenderableComponent {
|
||||
private _origin: Vector2;
|
||||
private _bitmap: egret.Bitmap;
|
||||
class SpriteRenderer extends RenderableComponent{
|
||||
private _sprite: Sprite;
|
||||
protected bitmap: egret.Bitmap;
|
||||
|
||||
public get origin(){
|
||||
return this._origin;
|
||||
/** 应该由这个精灵显示的精灵 */
|
||||
public get sprite(): Sprite{
|
||||
return this._sprite;
|
||||
}
|
||||
public set origin(value: Vector2){
|
||||
this.setOrigin(value);
|
||||
}
|
||||
public setOrigin(origin: Vector2){
|
||||
if (this._origin != origin){
|
||||
this._origin = origin;
|
||||
}
|
||||
return this;
|
||||
/** 应该由这个精灵显示的精灵 */
|
||||
public set sprite(value: Sprite){
|
||||
this.setSprite(value);
|
||||
}
|
||||
|
||||
public setSprite(sprite: Sprite): SpriteRenderer{
|
||||
this.removeChildren();
|
||||
this._sprite = sprite;
|
||||
if (this._sprite) this._origin = this._sprite.origin;
|
||||
this._bitmap = new egret.Bitmap(sprite.texture2D);
|
||||
this.addChild(this._bitmap);
|
||||
if (this._sprite) {
|
||||
this.anchorOffsetX = this._sprite.origin.x / this._sprite.sourceRect.width;
|
||||
this.anchorOffsetY = this._sprite.origin.y / this._sprite.sourceRect.height;
|
||||
}
|
||||
this.bitmap = new egret.Bitmap(sprite.texture2D);
|
||||
this.addChild(this.bitmap);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -50,8 +48,8 @@ class SpriteRenderer extends RenderableComponent {
|
||||
|
||||
/** 渲染处理 在每个模块中处理各自的渲染逻辑 */
|
||||
public render(camera: Camera){
|
||||
this.x = this.entity.position.x - this.origin.x - camera.position.x + camera.origin.x;
|
||||
this.y = this.entity.position.y - this.origin.y - camera.position.y + camera.origin.y;
|
||||
this.x = -camera.position.x + camera.origin.x;
|
||||
this.y = -camera.position.y + camera.origin.y;
|
||||
}
|
||||
|
||||
public onRemovedFromEntity(){
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
///<reference path="./SpriteRenderer.ts" />
|
||||
/**
|
||||
* 滚动由两张图片组合而成
|
||||
*/
|
||||
class TiledSpriteRenderer extends SpriteRenderer {
|
||||
protected sourceRect: Rectangle;
|
||||
protected leftTexture: egret.Bitmap;
|
||||
protected rightTexture: egret.Bitmap;
|
||||
|
||||
public get scrollX() {
|
||||
return this.sourceRect.x;
|
||||
}
|
||||
public set scrollX(value: number) {
|
||||
this.sourceRect.x = value;
|
||||
}
|
||||
public get scrollY() {
|
||||
return this.sourceRect.y;
|
||||
}
|
||||
public set scrollY(value: number) {
|
||||
this.sourceRect.y = value;
|
||||
}
|
||||
|
||||
constructor(sprite: Sprite) {
|
||||
super();
|
||||
|
||||
this.leftTexture = new egret.Bitmap();
|
||||
this.rightTexture = new egret.Bitmap();
|
||||
this.leftTexture.texture = sprite.texture2D;
|
||||
this.rightTexture.texture = sprite.texture2D;
|
||||
|
||||
this.setSprite(sprite);
|
||||
this.sourceRect = sprite.sourceRect;
|
||||
}
|
||||
|
||||
public render(camera: Camera) {
|
||||
if (!this.sprite)
|
||||
return;
|
||||
|
||||
super.render(camera);
|
||||
|
||||
let renderTexture = new egret.RenderTexture();
|
||||
let cacheBitmap = new egret.DisplayObjectContainer();
|
||||
cacheBitmap.removeChildren();
|
||||
cacheBitmap.addChild(this.leftTexture);
|
||||
cacheBitmap.addChild(this.rightTexture);
|
||||
|
||||
this.leftTexture.x = this.sourceRect.x;
|
||||
this.rightTexture.x = this.sourceRect.x - this.sourceRect.width;
|
||||
this.leftTexture.y = this.sourceRect.y;
|
||||
this.rightTexture.y = this.sourceRect.y;
|
||||
|
||||
cacheBitmap.cacheAsBitmap = true;
|
||||
renderTexture.drawToTexture(cacheBitmap, new egret.Rectangle(0, 0, this.sourceRect.width, this.sourceRect.height));
|
||||
|
||||
this.bitmap.texture = renderTexture;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
enum CoreEvents{
|
||||
/** 当场景发生变化时触发 */
|
||||
SceneChanged,
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
class Entity extends egret.DisplayObjectContainer {
|
||||
private static _idGenerator: number;
|
||||
|
||||
private _position: Vector2 = Vector2.zero;
|
||||
public name: string;
|
||||
public readonly id: number;
|
||||
/** 当前实体所属的场景 */
|
||||
@@ -20,11 +19,13 @@ class Entity extends egret.DisplayObjectContainer {
|
||||
}
|
||||
|
||||
public get position(){
|
||||
return this._position;
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
public set position(value: Vector2){
|
||||
this._position = value;
|
||||
this.$setX(value.x);
|
||||
this.$setY(value.y);
|
||||
this.onEntityTransformChanged(TransformComponent.position);
|
||||
}
|
||||
|
||||
public get scale(){
|
||||
@@ -32,8 +33,18 @@ class Entity extends egret.DisplayObjectContainer {
|
||||
}
|
||||
|
||||
public set scale(value: Vector2){
|
||||
this.scaleX = value.x;
|
||||
this.scaleY = value.y;
|
||||
this.$setScaleX(value.x);
|
||||
this.$setScaleY(value.y);
|
||||
this.onEntityTransformChanged(TransformComponent.scale);
|
||||
}
|
||||
|
||||
public set rotation(value: number){
|
||||
this.$setRotation(value);
|
||||
this.onEntityTransformChanged(TransformComponent.rotation);
|
||||
}
|
||||
|
||||
public get rotation(){
|
||||
return this.$getRotation();
|
||||
}
|
||||
|
||||
public get enabled(){
|
||||
@@ -74,6 +85,11 @@ class Entity extends egret.DisplayObjectContainer {
|
||||
this.id = Entity._idGenerator ++;
|
||||
|
||||
this.componentBits = new BitSet();
|
||||
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
|
||||
}
|
||||
|
||||
private onAddToStage(){
|
||||
this.onEntityTransformChanged(TransformComponent.position);
|
||||
}
|
||||
|
||||
public get updateOrder(){
|
||||
@@ -160,6 +176,10 @@ class Entity extends egret.DisplayObjectContainer {
|
||||
return this.components.getComponents(typeName, componentList);
|
||||
}
|
||||
|
||||
private onEntityTransformChanged(comp: TransformComponent){
|
||||
this.components.onEntityTransformChanged(comp);
|
||||
}
|
||||
|
||||
public removeComponentForType<T extends Component>(type){
|
||||
let comp = this.getComponent<T>(type);
|
||||
if (comp){
|
||||
@@ -195,12 +215,23 @@ class Entity extends egret.DisplayObjectContainer {
|
||||
|
||||
public destroy(){
|
||||
this._isDestoryed = true;
|
||||
this.removeEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
|
||||
|
||||
this.scene.entities.remove(this);
|
||||
this.removeChildren();
|
||||
|
||||
if (this.parent)
|
||||
this.parent.removeChild(this);
|
||||
|
||||
for (let i = this.numChildren - 1; i >= 0; i --){
|
||||
let child = this.getChildAt(i);
|
||||
(child as Component).entity.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TransformComponent {
|
||||
rotation,
|
||||
scale,
|
||||
position
|
||||
}
|
||||
@@ -144,6 +144,9 @@ class Scene extends egret.DisplayObjectContainer {
|
||||
this.entityProcessors.end();
|
||||
|
||||
this.unload();
|
||||
|
||||
if (this.parent)
|
||||
this.parent.removeChild(this);
|
||||
}
|
||||
|
||||
protected async onStart() {
|
||||
|
||||
@@ -4,10 +4,25 @@ class SceneManager {
|
||||
private static _nextScene: Scene;
|
||||
public static sceneTransition: SceneTransition;
|
||||
public static stage: egret.Stage;
|
||||
/** 订阅此事件以在活动场景发生更改时得到通知。 */
|
||||
public static activeSceneChanged: Function;
|
||||
/** 核心发射器。只发出核心级别的事件 */
|
||||
public static emitter: Emitter<CoreEvents>;
|
||||
/** 全局内容管理器加载任何应该停留在场景之间的资产 */
|
||||
public static content: ContentManager;
|
||||
/** 简化对内部类的全局内容实例的访问 */
|
||||
private static _instnace: SceneManager;
|
||||
public static get Instance(){
|
||||
return this._instnace;
|
||||
}
|
||||
|
||||
constructor(stage: egret.Stage) {
|
||||
stage.addEventListener(egret.Event.ENTER_FRAME, SceneManager.update, this);
|
||||
|
||||
SceneManager._instnace = this;
|
||||
SceneManager.emitter = new Emitter<CoreEvents>();
|
||||
SceneManager.content = new ContentManager();
|
||||
|
||||
SceneManager.stage = stage;
|
||||
SceneManager.initialize(stage);
|
||||
}
|
||||
@@ -22,9 +37,12 @@ class SceneManager {
|
||||
if (this._scene == null) {
|
||||
this._scene = value;
|
||||
this._scene.begin();
|
||||
SceneManager.Instance.onSceneChanged();
|
||||
} else {
|
||||
this._nextScene = value;
|
||||
}
|
||||
|
||||
this.registerActiveSceneChanged(this._scene, this._nextScene);
|
||||
}
|
||||
|
||||
public static initialize(stage: egret.Stage) {
|
||||
@@ -48,13 +66,9 @@ class SceneManager {
|
||||
if (SceneManager._nextScene) {
|
||||
SceneManager._scene.end();
|
||||
|
||||
for (let i = 0; i < SceneManager._scene.entities.buffer.length; i++) {
|
||||
let entity = SceneManager._scene.entities.buffer[i];
|
||||
entity.destroy();
|
||||
}
|
||||
|
||||
SceneManager._scene = SceneManager._nextScene;
|
||||
SceneManager._nextScene = null;
|
||||
SceneManager._instnace.onSceneChanged();
|
||||
|
||||
SceneManager._scene.begin();
|
||||
}
|
||||
@@ -81,6 +95,9 @@ class SceneManager {
|
||||
}
|
||||
} else if (this._scene) {
|
||||
this._scene.render();
|
||||
|
||||
Debug.render();
|
||||
|
||||
this._scene.postRender();
|
||||
}
|
||||
}
|
||||
@@ -98,4 +115,17 @@ class SceneManager {
|
||||
this.sceneTransition = sceneTransition;
|
||||
return sceneTransition;
|
||||
}
|
||||
|
||||
public static registerActiveSceneChanged(current: Scene, next: Scene){
|
||||
if (this.activeSceneChanged)
|
||||
this.activeSceneChanged(current, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一个场景结束后,下一个场景开始之前调用
|
||||
*/
|
||||
public onSceneChanged(){
|
||||
SceneManager.emitter.emit(CoreEvents.SceneChanged);
|
||||
Time.sceneChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,35 @@
|
||||
class ComponentList {
|
||||
private _entity: Entity;
|
||||
/** 添加到实体的组件列表 */
|
||||
private _components: Component[] = [];
|
||||
/** 添加到此框架的组件列表。用来对组件进行分组,这样我们就可以同时进行加工 */
|
||||
private _componentsToAdd: Component[] = [];
|
||||
/** 标记要删除此框架的组件列表。用来对组件进行分组,这样我们就可以同时进行加工 */
|
||||
private _componentsToRemove: Component[] = [];
|
||||
private _tempBufferList: Component[] = [];
|
||||
|
||||
constructor(entity: Entity){
|
||||
constructor(entity: Entity) {
|
||||
this._entity = entity;
|
||||
}
|
||||
|
||||
public get count(){
|
||||
public get count() {
|
||||
return this._components.length;
|
||||
}
|
||||
|
||||
public get buffer(){
|
||||
public get buffer() {
|
||||
return this._components;
|
||||
}
|
||||
|
||||
public add(component: Component){
|
||||
public add(component: Component) {
|
||||
this._componentsToAdd.push(component);
|
||||
}
|
||||
|
||||
public remove(component: Component){
|
||||
if (this._componentsToAdd.contains(component)){
|
||||
public remove(component: Component) {
|
||||
if (this._componentsToRemove.contains(component))
|
||||
console.warn(`You are trying to remove a Component (${component}) that you already removed`)
|
||||
|
||||
// 这可能不是一个活动的组件,所以我们必须注意它是否还没有被处理,它可能正在同一帧中被删除
|
||||
if (this._componentsToAdd.contains(component)) {
|
||||
this._componentsToAdd.remove(component);
|
||||
return;
|
||||
}
|
||||
@@ -30,8 +37,11 @@ class ComponentList {
|
||||
this._componentsToRemove.push(component);
|
||||
}
|
||||
|
||||
public removeAllComponents(){
|
||||
for (let i = 0; i < this._components.length; i ++){
|
||||
/**
|
||||
* 立即从组件列表中删除所有组件
|
||||
*/
|
||||
public removeAllComponents() {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
this.handleRemove(this._components[i]);
|
||||
}
|
||||
|
||||
@@ -40,10 +50,11 @@ class ComponentList {
|
||||
this._componentsToRemove.length = 0;
|
||||
}
|
||||
|
||||
public deregisterAllComponents(){
|
||||
for (let i = 0; i < this._components.length; i ++){
|
||||
public deregisterAllComponents() {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
let component = this._components[i];
|
||||
|
||||
// 处理渲染层列表
|
||||
if (component instanceof RenderableComponent)
|
||||
this._entity.scene.renderableComponents.remove(component);
|
||||
|
||||
@@ -52,8 +63,8 @@ class ComponentList {
|
||||
}
|
||||
}
|
||||
|
||||
public registerAllComponents(){
|
||||
for (let i = 0; i < this._components.length; i ++){
|
||||
public registerAllComponents() {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
let component = this._components[i];
|
||||
|
||||
if (component instanceof RenderableComponent)
|
||||
@@ -64,9 +75,12 @@ class ComponentList {
|
||||
}
|
||||
}
|
||||
|
||||
public updateLists(){
|
||||
if (this._componentsToRemove.length > 0){
|
||||
for (let i = 0; i < this._componentsToRemove.length; i ++){
|
||||
/**
|
||||
* 处理任何需要删除或添加的组件
|
||||
*/
|
||||
public updateLists() {
|
||||
if (this._componentsToRemove.length > 0) {
|
||||
for (let i = 0; i < this._componentsToRemove.length; i++) {
|
||||
this.handleRemove(this._componentsToRemove[i]);
|
||||
this._components.remove(this._componentsToRemove[i]);
|
||||
}
|
||||
@@ -74,11 +88,12 @@ class ComponentList {
|
||||
this._componentsToRemove.length = 0;
|
||||
}
|
||||
|
||||
if (this._componentsToAdd.length > 0){
|
||||
for (let i = 0, count = this._componentsToAdd.length; i < count; i ++){
|
||||
if (this._componentsToAdd.length > 0) {
|
||||
for (let i = 0, count = this._componentsToAdd.length; i < count; i++) {
|
||||
let component = this._componentsToAdd[i];
|
||||
if (component instanceof RenderableComponent)
|
||||
this._entity.scene.renderableComponents.add(component);
|
||||
|
||||
this._entity.componentBits.set(ComponentTypeManager.getIndexFor(component));
|
||||
this._entity.scene.entityProcessors.onComponentAdded(this._entity);
|
||||
|
||||
@@ -86,13 +101,16 @@ class ComponentList {
|
||||
this._tempBufferList.push(component);
|
||||
}
|
||||
|
||||
// 在调用onAddedToEntity之前清除,以防添加更多组件
|
||||
this._componentsToAdd.length = 0;
|
||||
|
||||
for (let i = 0; i < this._tempBufferList.length; i++){
|
||||
// 现在所有的组件都添加到了场景中,我们再次循环并调用onAddedToEntity/onEnabled
|
||||
for (let i = 0; i < this._tempBufferList.length; i++) {
|
||||
let component = this._tempBufferList[i];
|
||||
component.onAddedToEntity();
|
||||
|
||||
if (component.enabled){
|
||||
// enabled检查实体和组件
|
||||
if (component.enabled) {
|
||||
component.onEnabled();
|
||||
}
|
||||
}
|
||||
@@ -101,7 +119,19 @@ class ComponentList {
|
||||
}
|
||||
}
|
||||
|
||||
private handleRemove(component: Component){
|
||||
public onEntityTransformChanged(comp: TransformComponent) {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
if (this._components[i].enabled)
|
||||
this._components[i].onEntityTransformChanged(comp);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._componentsToAdd.length; i++) {
|
||||
if (this._componentsToAdd[i].enabled)
|
||||
this._componentsToAdd[i].onEntityTransformChanged(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private handleRemove(component: Component) {
|
||||
if (component instanceof RenderableComponent)
|
||||
this._entity.scene.renderableComponents.remove(component);
|
||||
|
||||
@@ -112,15 +142,23 @@ class ComponentList {
|
||||
component.entity = null;
|
||||
}
|
||||
|
||||
public getComponent<T extends Component>(type, onlyReturnInitializedComponents: boolean): T{
|
||||
for (let i = 0; i < this._components.length; i ++){
|
||||
/**
|
||||
* 获取类型T的第一个组件并返回它
|
||||
* 可以选择跳过检查未初始化的组件(尚未调用onAddedToEntity方法的组件)
|
||||
* 如果没有找到组件,则返回null。
|
||||
* @param type
|
||||
* @param onlyReturnInitializedComponents
|
||||
*/
|
||||
public getComponent<T extends Component>(type, onlyReturnInitializedComponents: boolean): T {
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
let component = this._components[i];
|
||||
if (component instanceof type)
|
||||
return component as T;
|
||||
}
|
||||
|
||||
if (!onlyReturnInitializedComponents){
|
||||
for (let i = 0; i < this._componentsToAdd.length; i ++){
|
||||
// 我们可以选择检查挂起的组件,以防addComponent和getComponent在同一个框架中被调用
|
||||
if (!onlyReturnInitializedComponents) {
|
||||
for (let i = 0; i < this._componentsToAdd.length; i++) {
|
||||
let component = this._componentsToAdd[i];
|
||||
if (component instanceof type)
|
||||
return component as T;
|
||||
@@ -130,31 +168,36 @@ class ComponentList {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getComponents(typeName: string | any, components?){
|
||||
/**
|
||||
* 获取T类型的所有组件,但不使用列表分配
|
||||
* @param typeName
|
||||
* @param components
|
||||
*/
|
||||
public getComponents(typeName: string | any, components?) {
|
||||
if (!components)
|
||||
components = [];
|
||||
|
||||
for (let i = 0; i < this._components.length; i ++){
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
let component = this._components[i];
|
||||
if (typeof(typeName) == "string"){
|
||||
if (egret.is(component, typeName)){
|
||||
if (typeof (typeName) == "string") {
|
||||
if (egret.is(component, typeName)) {
|
||||
components.push(component);
|
||||
}
|
||||
}else{
|
||||
if (component instanceof typeName){
|
||||
} else {
|
||||
if (component instanceof typeName) {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._componentsToAdd.length; i ++){
|
||||
for (let i = 0; i < this._componentsToAdd.length; i++) {
|
||||
let component = this._componentsToAdd[i];
|
||||
if (typeof(typeName) == "string"){
|
||||
if (egret.is(component, typeName)){
|
||||
if (typeof (typeName) == "string") {
|
||||
if (egret.is(component, typeName)) {
|
||||
components.push(component);
|
||||
}
|
||||
}else{
|
||||
if (component instanceof typeName){
|
||||
} else {
|
||||
if (component instanceof typeName) {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
@@ -163,12 +206,19 @@ class ComponentList {
|
||||
return components;
|
||||
}
|
||||
|
||||
public update(){
|
||||
public update() {
|
||||
this.updateLists();
|
||||
for (let i = 0; i < this._components.length; i ++){
|
||||
let component = this._components[i];
|
||||
if (component.enabled && (component.updateInterval == 1 || Time.frameCount % component.updateInterval == 0))
|
||||
component.update();
|
||||
for (let i = 0; i < this._components.length; i++) {
|
||||
let updatable = this._components[i];
|
||||
let updateableComponent;
|
||||
if (updatable instanceof Component)
|
||||
updateableComponent = updatable as Component;
|
||||
|
||||
if (updatable.enabled &&
|
||||
updateableComponent.enabled &&
|
||||
(updateableComponent.updateInterval == 1 ||
|
||||
Time.frameCount % updateableComponent.updateInterval == 0))
|
||||
updatable.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
class ObjectUtils {
|
||||
/**
|
||||
* 对象深度拷贝
|
||||
* @param p any 源对象
|
||||
* @param c any 目标对象, 不传则返回新对象, 传则合并属性, 相同名字的属性则会覆盖
|
||||
*/
|
||||
public static clone<T>(p: any, c: T = null): T {
|
||||
var c = c || <T>{};
|
||||
for (let i in p) {
|
||||
if (typeof p[i] === 'object') {
|
||||
c[i] = p[i] instanceof Array ? [] : {};
|
||||
this.clone(p[i], c[i]);
|
||||
}
|
||||
else {
|
||||
c[i] = p[i];
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
class StringUtils {
|
||||
/**
|
||||
* 匹配中文字符
|
||||
* @param str 需要匹配的字符串
|
||||
* @return
|
||||
*/
|
||||
public static matchChineseWord(str: string): string[] {
|
||||
//中文字符的unicode值[\u4E00-\u9FA5]
|
||||
let patternA: RegExp = /[\u4E00-\u9FA5]+/gim;
|
||||
return str.match(patternA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字符串左端的空白字符
|
||||
* @param target 目标字符串
|
||||
* @return
|
||||
*/
|
||||
public static lTrim(target: string): string {
|
||||
let startIndex: number = 0;
|
||||
while (this.isWhiteSpace(target.charAt(startIndex))) {
|
||||
startIndex++;
|
||||
}
|
||||
return target.slice(startIndex, target.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字符串右端的空白字符
|
||||
* @param target 目标字符串
|
||||
* @return
|
||||
*/
|
||||
public static rTrim(target: string): string {
|
||||
let endIndex: number = target.length - 1;
|
||||
while (this.isWhiteSpace(target.charAt(endIndex))) {
|
||||
endIndex--;
|
||||
}
|
||||
return target.slice(0, endIndex + 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回一个去除2段空白字符的字符串
|
||||
* @param target
|
||||
* @return 返回一个去除2段空白字符的字符串
|
||||
*/
|
||||
public static trim(target: string): string {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
return this.rTrim(this.lTrim(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该字符是否为空白字符
|
||||
* @param str
|
||||
* @return 返回该字符是否为空白字符
|
||||
*/
|
||||
public static isWhiteSpace(str: string): boolean {
|
||||
if (str == " " || str == "\t" || str == "\r" || str == "\n")
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回执行替换后的字符串
|
||||
* @param mainStr 待查找字符串
|
||||
* @param targetStr 目标字符串
|
||||
* @param replaceStr 替换字符串
|
||||
* @param caseMark 是否忽略大小写
|
||||
* @return 返回执行替换后的字符串
|
||||
*/
|
||||
public static replaceMatch(mainStr: string, targetStr: string,
|
||||
replaceStr: string, caseMark: boolean = false): string {
|
||||
let len: number = mainStr.length;
|
||||
let tempStr: string = "";
|
||||
let isMatch: boolean = false;
|
||||
let tempTarget: string = caseMark == true ? targetStr.toLowerCase() : targetStr;
|
||||
for (let i: number = 0; i < len; i++) {
|
||||
isMatch = false;
|
||||
if (mainStr.charAt(i) == tempTarget.charAt(0)) {
|
||||
if (mainStr.substr(i, tempTarget.length) == tempTarget) {
|
||||
isMatch = true;
|
||||
}
|
||||
}
|
||||
if (isMatch) {
|
||||
tempStr += replaceStr;
|
||||
i = i + tempTarget.length - 1;
|
||||
}
|
||||
else {
|
||||
tempStr += mainStr.charAt(i);
|
||||
}
|
||||
}
|
||||
return tempStr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 特殊符号字符串
|
||||
*/
|
||||
private static specialSigns: string[] = [
|
||||
'&', '&',
|
||||
'<', '<',
|
||||
'>', '>',
|
||||
'"', '"',
|
||||
"'", ''',
|
||||
'®', '®',
|
||||
'©', '©',
|
||||
'™', '™',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 用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"
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* trace( StringFormat.zfill('1') );
|
||||
* // 01
|
||||
*
|
||||
* trace( StringFormat.zfill('16', 5) );
|
||||
* // 00016
|
||||
*
|
||||
* trace( StringFormat.zfill('-3', 3) );
|
||||
* // -03
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @param str 要进行处理的字符串
|
||||
* @param width 处理后字符串的长度,
|
||||
* 如果str.length >= width,将不做任何处理直接返回原始的str。
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static zfill(str: string, width: number = 2): string {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
width = Math.floor(width);
|
||||
let slen: number = str.length;
|
||||
if (slen >= width) {
|
||||
return str;
|
||||
}
|
||||
|
||||
let negative: boolean = false;
|
||||
if (str.substr(0, 1) == '-') {
|
||||
negative = true;
|
||||
str = str.substr(1);
|
||||
}
|
||||
|
||||
let len: number = width - slen;
|
||||
for (let i: number = 0; i < len; i++) {
|
||||
str = '0' + str;
|
||||
}
|
||||
|
||||
if (negative) {
|
||||
str = '-' + str;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 翻转字符串
|
||||
* @param str 字符串
|
||||
* @return 翻转后的字符串
|
||||
*/
|
||||
public static reverse(str: string): string {
|
||||
if (str.length > 1)
|
||||
return this.reverse(str.substring(1)) + str.substring(0, 1);
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 截断某段字符串
|
||||
* @param str 目标字符串
|
||||
* @param start 需要截断的起始索引
|
||||
* @param len 截断长度
|
||||
* @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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* 纹理帮助类
|
||||
*/
|
||||
class TextureUtils {
|
||||
public static sharedCanvas: HTMLCanvasElement;
|
||||
public static sharedContext: CanvasRenderingContext2D;
|
||||
|
||||
public static convertImageToCanvas(texture: egret.Texture, rect?: egret.Rectangle): HTMLCanvasElement{
|
||||
if (!this.sharedCanvas){
|
||||
this.sharedCanvas = egret.sys.createCanvas();
|
||||
this.sharedContext = this.sharedCanvas.getContext("2d");
|
||||
}
|
||||
|
||||
let w = texture.$getTextureWidth();
|
||||
let h = texture.$getTextureHeight();
|
||||
if (!rect){
|
||||
rect = egret.$TempRectangle;
|
||||
rect.x = 0;
|
||||
rect.y = 0;
|
||||
rect.width = w;
|
||||
rect.height = h;
|
||||
}
|
||||
|
||||
rect.x = Math.min(rect.x, w - 1);
|
||||
rect.y = Math.min(rect.y, h - 1);
|
||||
rect.width = Math.min(rect.width, w - rect.x);
|
||||
rect.height = Math.min(rect.height, h - rect.y);
|
||||
|
||||
let iWidth = Math.floor(rect.width);
|
||||
let iHeight = Math.floor(rect.height);
|
||||
let surface = this.sharedCanvas;
|
||||
surface["style"]["width"] = iWidth + "px";
|
||||
surface["style"]["height"] = iHeight + "px";
|
||||
this.sharedCanvas.width = iWidth;
|
||||
this.sharedCanvas.height = iHeight;
|
||||
|
||||
if (egret.Capabilities.renderMode == "webgl") {
|
||||
let renderTexture: egret.RenderTexture;
|
||||
//webgl下非RenderTexture纹理先画到RenderTexture
|
||||
if (!(<egret.RenderTexture>texture).$renderBuffer) {
|
||||
if (egret.sys.systemRenderer["renderClear"]) {
|
||||
egret.sys.systemRenderer["renderClear"]();
|
||||
}
|
||||
renderTexture = new egret.RenderTexture();
|
||||
renderTexture.drawToTexture(new egret.Bitmap(texture));
|
||||
}
|
||||
else {
|
||||
renderTexture = <egret.RenderTexture>texture;
|
||||
}
|
||||
//从RenderTexture中读取像素数据,填入canvas
|
||||
let pixels = renderTexture.$renderBuffer.getPixels(rect.x, rect.y, iWidth, iHeight);
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
this.sharedContext.fillStyle =
|
||||
'rgba(' + pixels[i]
|
||||
+ ',' + pixels[i + 1]
|
||||
+ ',' + pixels[i + 2]
|
||||
+ ',' + (pixels[i + 3] / 255) + ')';
|
||||
this.sharedContext.fillRect(x, y, 1, 1);
|
||||
x++;
|
||||
if (x == iWidth) {
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(<egret.RenderTexture>texture).$renderBuffer) {
|
||||
renderTexture.dispose();
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
else {
|
||||
let bitmapData = texture;
|
||||
let offsetX: number = Math.round(bitmapData.$offsetX);
|
||||
let offsetY: number = Math.round(bitmapData.$offsetY);
|
||||
let bitmapWidth: number = bitmapData.$bitmapWidth;
|
||||
let bitmapHeight: number = bitmapData.$bitmapHeight;
|
||||
let $TextureScaleFactor = SceneManager.stage.textureScaleFactor;
|
||||
this.sharedContext.drawImage(bitmapData.$bitmapData.source, bitmapData.$bitmapX + rect.x / $TextureScaleFactor, bitmapData.$bitmapY + rect.y / $TextureScaleFactor,
|
||||
bitmapWidth * rect.width / w, bitmapHeight * rect.height / h, offsetX, offsetY, rect.width, rect.height);
|
||||
return surface;
|
||||
}
|
||||
}
|
||||
|
||||
public static toDataURL(type: string, texture: egret.Texture, rect?: egret.Rectangle, encoderOptions?): string {
|
||||
try {
|
||||
let surface = this.convertImageToCanvas(texture, rect);
|
||||
let result = surface.toDataURL(type, encoderOptions);
|
||||
return result;
|
||||
}
|
||||
catch (e) {
|
||||
egret.$error(1033);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 有些杀毒软件认为 saveToFile 可能是一个病毒文件
|
||||
* @param type
|
||||
* @param texture
|
||||
* @param filePath
|
||||
* @param rect
|
||||
* @param encoderOptions
|
||||
*/
|
||||
public static eliFoTevas(type: string, texture: egret.Texture, filePath: string, rect?: egret.Rectangle, encoderOptions?): void {
|
||||
let surface = this.convertImageToCanvas(texture, rect);
|
||||
let result = (surface as any).toTempFilePathSync({
|
||||
fileType: type.indexOf("png") >= 0 ? "png" : "jpg"
|
||||
});
|
||||
|
||||
wx.getFileSystemManager().saveFile({
|
||||
tempFilePath: result,
|
||||
filePath: `${wx.env.USER_DATA_PATH}/${filePath}`,
|
||||
success: function (res) {
|
||||
//todo
|
||||
}
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getPixel32(texture: egret.Texture, x: number, y: number): number[] {
|
||||
egret.$warn(1041, "getPixel32", "getPixels");
|
||||
return texture.getPixels(x, y);
|
||||
}
|
||||
|
||||
public static getPixels(texture: egret.Texture, x: number, y: number, width: number = 1, height: number = 1): number[] {
|
||||
//webgl环境下不需要转换成canvas获取像素信息
|
||||
if (egret.Capabilities.renderMode == "webgl") {
|
||||
let renderTexture: egret.RenderTexture;
|
||||
//webgl下非RenderTexture纹理先画到RenderTexture
|
||||
if (!(<egret.RenderTexture>texture).$renderBuffer) {
|
||||
renderTexture = new egret.RenderTexture();
|
||||
renderTexture.drawToTexture(new egret.Bitmap(texture));
|
||||
}
|
||||
else {
|
||||
renderTexture = <egret.RenderTexture>texture;
|
||||
}
|
||||
//从RenderTexture中读取像素数据
|
||||
let pixels = renderTexture.$renderBuffer.getPixels(x, y, width, height);
|
||||
return pixels;
|
||||
}
|
||||
try {
|
||||
let surface = this.convertImageToCanvas(texture);
|
||||
let result = this.sharedContext.getImageData(x, y, width, height).data;
|
||||
return <number[]><any>result;
|
||||
}
|
||||
catch (e) {
|
||||
egret.$error(1039);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,38 @@
|
||||
/** 提供帧定时信息 */
|
||||
class Time {
|
||||
/** deltaTime的未缩放版本。不受时间尺度的影响 */
|
||||
public static unscaledDeltaTime;
|
||||
/** 前一帧到当前帧的时间增量,按时间刻度进行缩放 */
|
||||
public static deltaTime: number = 0;
|
||||
/** 时间刻度缩放 */
|
||||
public static timeScale = 1;
|
||||
public static frameCount = 0;;
|
||||
/** 已传递的帧总数 */
|
||||
public static frameCount = 0;
|
||||
|
||||
private static _lastTime = 0;
|
||||
/** 自场景加载以来的总时间 */
|
||||
public static _timeSinceSceneLoad;
|
||||
|
||||
public static update(currentTime: number){
|
||||
let dt = (currentTime - this._lastTime) / 1000;
|
||||
this.deltaTime = dt * this.timeScale;
|
||||
this.unscaledDeltaTime = dt;
|
||||
this._timeSinceSceneLoad += dt;
|
||||
this.frameCount ++;
|
||||
|
||||
this._lastTime = currentTime;
|
||||
}
|
||||
|
||||
public static sceneChanged(){
|
||||
this._timeSinceSceneLoad = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许在间隔检查。只应该使用高于delta的间隔值,否则它将始终返回true。
|
||||
* @param interval
|
||||
*/
|
||||
public static checkEvery(interval: number){
|
||||
// 我们减去了delta,因为timeSinceSceneLoad已经包含了这个update ticks delta
|
||||
return (this._timeSinceSceneLoad / interval) > ((this._timeSinceSceneLoad - this.deltaTime) / interval);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
class TimeUtils {
|
||||
/**
|
||||
* 计算月份ID
|
||||
* @param d 指定计算日期
|
||||
* @returns 月ID
|
||||
*/
|
||||
public static monthId(d: Date = null): number {
|
||||
d = d ? d : new Date();
|
||||
let y = d.getFullYear();
|
||||
let m = d.getMonth() + 1;
|
||||
let g = m < 10 ? "0" : "";
|
||||
return parseInt(y + g + m);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算日期ID
|
||||
* @param d 指定计算日期
|
||||
* @returns 日期ID
|
||||
*/
|
||||
public static dateId(t: Date = null): number {
|
||||
t = t ? t : new Date();
|
||||
let m: number = t.getMonth() + 1;
|
||||
let a = m < 10 ? "0" : "";
|
||||
let d: number = t.getDate();
|
||||
let b = d < 10 ? "0" : "";
|
||||
return parseInt(t.getFullYear() + a + m + b + d);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算周ID
|
||||
* @param d 指定计算日期
|
||||
* @returns 周ID
|
||||
*/
|
||||
public static weekId(d: Date = null, first: boolean = true): number {
|
||||
d = d ? d : new Date();
|
||||
let c: Date = new Date();
|
||||
c.setTime(d.getTime());
|
||||
c.setDate(1); c.setMonth(0);//当年第一天
|
||||
|
||||
let year: number = c.getFullYear();
|
||||
let firstDay: number = c.getDay();
|
||||
if (firstDay == 0) {
|
||||
firstDay = 7;
|
||||
}
|
||||
let max: boolean = false;
|
||||
if (firstDay <= 4) {
|
||||
max = firstDay > 1;
|
||||
c.setDate(c.getDate() - (firstDay - 1));
|
||||
} else {
|
||||
c.setDate(c.getDate() + 7 - firstDay + 1);
|
||||
}
|
||||
let num: number = this.diffDay(d, c, false);
|
||||
if (num < 0) {
|
||||
c.setDate(1); c.setMonth(0);//当年第一天
|
||||
c.setDate(c.getDate() - 1);
|
||||
return this.weekId(c, false);
|
||||
}
|
||||
let week: number = num / 7;
|
||||
let weekIdx: number = Math.floor(week) + 1;
|
||||
if (weekIdx == 53) {
|
||||
c.setTime(d.getTime());
|
||||
c.setDate(c.getDate() - 1);
|
||||
let endDay: number = c.getDay();
|
||||
if (endDay == 0) {
|
||||
endDay = 7;
|
||||
}
|
||||
if (first && (!max || endDay < 4)) {
|
||||
c.setFullYear(c.getFullYear() + 1);
|
||||
c.setDate(1); c.setMonth(0);//当年第一天
|
||||
return this.weekId(c, false);
|
||||
}
|
||||
}
|
||||
let g: string = weekIdx > 9 ? "" : "0";
|
||||
let s: string = year + "00" + g + weekIdx;//加上00防止和月份ID冲突
|
||||
return parseInt(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算俩日期时间差,如果a比b小,返回负数
|
||||
*/
|
||||
public static diffDay(a: Date, b: Date, fixOne: boolean = false): number {
|
||||
let x = (a.getTime() - b.getTime()) / 86400000;
|
||||
return fixOne ? Math.ceil(x) : Math.floor(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本周一 凌晨时间
|
||||
*/
|
||||
public static getFirstDayOfWeek(d?: Date): Date {
|
||||
d = d ? d : new Date();
|
||||
let day = d.getDay() || 7;
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1 - day, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当日凌晨时间
|
||||
*/
|
||||
public static getFirstOfDay(d?: Date): Date {
|
||||
d = d ? d : new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取次日凌晨时间
|
||||
*/
|
||||
public static getNextFirstOfDay(d?: Date): Date {
|
||||
return new Date(this.getFirstOfDay(d).getTime() + 86400000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 2018-12-12
|
||||
*/
|
||||
public static formatDate(date: Date): string {
|
||||
let y = date.getFullYear();
|
||||
let m: any = date.getMonth() + 1;
|
||||
m = m < 10 ? '0' + m : m;
|
||||
let d: any = date.getDate();
|
||||
d = d < 10 ? ('0' + d) : d;
|
||||
return y + '-' + m + '-' + d;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 2018-12-12 12:12:12
|
||||
*/
|
||||
public static formatDateTime(date: Date): string {
|
||||
let y = date.getFullYear();
|
||||
let m: any = date.getMonth() + 1;
|
||||
m = m < 10 ? ('0' + m) : m;
|
||||
let d: any = date.getDate();
|
||||
d = d < 10 ? ('0' + d) : d;
|
||||
let h = date.getHours();
|
||||
let i: any = date.getMinutes();
|
||||
i = i < 10 ? ('0' + i) : i;
|
||||
let s: any = date.getSeconds();
|
||||
s = s < 10 ? ('0' + s) : s;
|
||||
return y + '-' + m + '-' + d + ' ' + h + ':' + i + ":" + s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns s 2018-12-12 或者 2018-12-12 12:12:12
|
||||
*/
|
||||
public static parseDate(s: string): Date {
|
||||
let t = Date.parse(s);
|
||||
if (!isNaN(t)) {
|
||||
return new Date(Date.parse(s.replace(/-/g, "/")));
|
||||
} else {
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 秒数转换为时间形式。
|
||||
* @param time 秒数
|
||||
* @param partition 分隔符
|
||||
* @param showHour 是否显示小时
|
||||
* @return 返回一个以分隔符分割的时, 分, 秒
|
||||
*
|
||||
* 比如: time = 4351; secondToTime(time)返回字符串01:12:31;
|
||||
*/
|
||||
public static secondToTime(time: number = 0, partition: string = ":", showHour: boolean = true): string {
|
||||
let hours: number = Math.floor(time / 3600);
|
||||
let minutes: number = Math.floor(time % 3600 / 60);
|
||||
let seconds: number = Math.floor(time % 3600 % 60);
|
||||
|
||||
let h: string = hours.toString();
|
||||
let m: string = minutes.toString();
|
||||
let s: string = seconds.toString();
|
||||
|
||||
if (hours < 10) h = "0" + h;
|
||||
if (minutes < 10) m = "0" + m;
|
||||
if (seconds < 10) s = "0" + s;
|
||||
|
||||
let timeStr: string;
|
||||
if (showHour)
|
||||
timeStr = h + partition + m + partition + s;
|
||||
else
|
||||
timeStr = m + partition + s;
|
||||
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 时间形式转换为毫秒数。
|
||||
* @param time 以指定分隔符分割的时间字符串
|
||||
* @param partition 分隔符
|
||||
* @return 毫秒数显示的字符串
|
||||
* @throws Error Exception
|
||||
*
|
||||
* 用法1 trace(MillisecondTransform.timeToMillisecond("00:60:00"))
|
||||
* 输出 3600000
|
||||
*
|
||||
*
|
||||
* 用法2 trace(MillisecondTransform.timeToMillisecond("00.60.00","."))
|
||||
* 输出 3600000
|
||||
*/
|
||||
public static timeToMillisecond(time: string, partition: string = ":"): string {
|
||||
let _ary: any[] = time.split(partition);
|
||||
let timeNum: number = 0;
|
||||
let len: number = _ary.length;
|
||||
for (let i: number = 0; i < len; i++) {
|
||||
let n: number = <number>_ary[i];
|
||||
timeNum += n * Math.pow(60, (len - 1 - i));
|
||||
}
|
||||
timeNum *= 1000;
|
||||
return timeNum.toString();
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ declare interface Array<T> {
|
||||
* 移除数组元素
|
||||
* @param element 数组元素
|
||||
*/
|
||||
remove(element): boolean;
|
||||
remove(element: T): boolean;
|
||||
/**
|
||||
* 移除特定索引数组元素
|
||||
* @param index 索引
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
class GraphicsCapabilities {
|
||||
public supportsTextureFilterAnisotropic: boolean;
|
||||
public supportsNonPowerOfTwo: boolean;
|
||||
public supportsDepth24: boolean;
|
||||
public supportsPackedDepthStencil: boolean;
|
||||
public supportsDepthNonLinear: boolean;
|
||||
public supportsTextureMaxLevel: boolean;
|
||||
public supportsS3tc: boolean;
|
||||
public supportsDxt1: boolean;
|
||||
public supportsPvrtc: boolean;
|
||||
public supportsAtitc: boolean;
|
||||
public supportsFramebufferObjectARB: boolean;
|
||||
class GraphicsCapabilities extends egret.Capabilities {
|
||||
|
||||
public initialize(device: GraphicsDevice){
|
||||
this.platformInitialize(device);
|
||||
}
|
||||
|
||||
private platformInitialize(device: GraphicsDevice){
|
||||
let gl: WebGLRenderingContext = new egret.sys.RenderBuffer().context.getInstance();
|
||||
this.supportsNonPowerOfTwo = false;
|
||||
this.supportsTextureFilterAnisotropic = gl.getExtension("EXT_texture_filter_anisotropic") != null;
|
||||
this.supportsDepth24 = true;
|
||||
this.supportsPackedDepthStencil = true;
|
||||
this.supportsDepthNonLinear = false;
|
||||
this.supportsTextureMaxLevel = true;
|
||||
this.supportsS3tc = gl.getExtension("WEBGL_compressed_texture_s3tc") != null ||
|
||||
gl.getExtension("WEBGL_compressed_texture_s3tc_srgb") != null;
|
||||
this.supportsDxt1 = this.supportsS3tc;
|
||||
this.supportsPvrtc = false;
|
||||
this.supportsAtitc = gl.getExtension("WEBGL_compressed_texture_astc") != null;
|
||||
this.supportsFramebufferObjectARB = false;
|
||||
let capabilities = this;
|
||||
capabilities["isMobile"] = true;
|
||||
|
||||
let systemInfo = wx.getSystemInfoSync();
|
||||
let systemStr = systemInfo.system.toLowerCase();
|
||||
if (systemStr.indexOf("ios") > -1){
|
||||
capabilities["os"] = "iOS";
|
||||
} else if(systemStr.indexOf("android") > -1){
|
||||
capabilities["os"] = "Android";
|
||||
}
|
||||
|
||||
let language = systemInfo.language;
|
||||
if (language.indexOf('zh') > -1){
|
||||
language = "zh-CN";
|
||||
} else {
|
||||
language = "en-US";
|
||||
}
|
||||
capabilities["language"] = language;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ abstract class SceneTransition {
|
||||
this.isNewSceneLoaded = true;
|
||||
}
|
||||
|
||||
public tickEffectProgressProperty(filter: egret.CustomFilter, duration: number, easeType: Function, reverseDirection = false){
|
||||
public tickEffectProgressProperty(filter: egret.CustomFilter, duration: number, easeType: Function, reverseDirection = false): Promise<boolean>{
|
||||
return new Promise((resolve)=>{
|
||||
let start = reverseDirection ? 1 : 0;
|
||||
let end = reverseDirection ? 0 : 1;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
///<reference path="./BaseView.ts" />
|
||||
/** 用于承载fui界面 */
|
||||
class BaseFuiView extends BaseView {
|
||||
/** 界面名称 */
|
||||
protected _name: string;
|
||||
|
||||
constructor(name: string){
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/** 用于表示单例类 */
|
||||
class BaseSingle {
|
||||
private static _instance: any;
|
||||
|
||||
public static getInstance<T>(): T {
|
||||
if (this._instance == null) {
|
||||
this._instance = new this();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**清除fgui元素 */
|
||||
protected clearFuiObj(obj: fairygui.GObject): boolean {
|
||||
if (obj) {
|
||||
egret.Tween.removeTweens(obj.displayObject);
|
||||
if (obj.displayObject && obj.displayObject.parent) {
|
||||
obj.displayObject.parent.removeChild(obj.displayObject);
|
||||
}
|
||||
obj.dispose();
|
||||
obj = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/** 所有视图的基类 */
|
||||
class BaseView extends egret.DisplayObjectContainer {
|
||||
/** 界面数据 */
|
||||
protected _data: any;
|
||||
/** 在打开界面前触发 */
|
||||
protected init(){
|
||||
|
||||
}
|
||||
|
||||
/** 窗口打开时触发 */
|
||||
public show(data?: any) {
|
||||
|
||||
}
|
||||
|
||||
/** 刷新界面数据 由mvc控制 */
|
||||
public refreshData(data?: any){
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
/** 刷新界面逻辑 */
|
||||
public refreshView(){
|
||||
|
||||
}
|
||||
|
||||
/** 关闭窗口 */
|
||||
public close() {
|
||||
|
||||
}
|
||||
|
||||
/** 销毁窗口 */
|
||||
public destroy(){
|
||||
if (this.parent){
|
||||
this.parent.removeChild(this);
|
||||
}
|
||||
|
||||
/** 循环删除此界面下所有节点 */
|
||||
while (this.numChildren > 0) {
|
||||
this.removeChildAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
class ViewManager extends BaseSingle {
|
||||
private _openDic: BaseFuiView[] = [];
|
||||
|
||||
/**
|
||||
* 刷新界面
|
||||
* @param viewClass 界面类型
|
||||
* @param data 界面数据
|
||||
*/
|
||||
public refreshView(viewClass: any, data?: any){
|
||||
let view = this.getView<BaseFuiView>(viewClass);
|
||||
|
||||
if (view){
|
||||
/** 压入数据 */
|
||||
view.refreshData(data);
|
||||
/** 执行刷新逻辑 */
|
||||
view.refreshView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开界面
|
||||
* @param viewClass 界面类型
|
||||
* @param data 界面数据
|
||||
* @param complete 界面加载完成回调
|
||||
*/
|
||||
public openView(viewClass: any, data?: any, complete?: Function){
|
||||
let newView = this.getView<BaseFuiView>(viewClass);
|
||||
if (!newView){
|
||||
newView = new viewClass();
|
||||
}
|
||||
|
||||
/** 如果界面已打开 则执行刷新界面 */
|
||||
if (this.existView(viewClass)){
|
||||
newView.refreshData(data);
|
||||
newView.refreshView();
|
||||
return;
|
||||
}
|
||||
|
||||
this._openDic.push(newView);
|
||||
// TODO: 加载资源
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取界面 可能为null
|
||||
* @param viewClass 界面类型
|
||||
*/
|
||||
public getView<T>(viewClass: any): T {
|
||||
let result: any = this._openDic.firstOrDefault(a => {
|
||||
return a instanceof viewClass;
|
||||
});
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 界面是否存在
|
||||
* @param viewClass 界面类型
|
||||
*/
|
||||
public existView(viewClass: any): boolean {
|
||||
return this._openDic.findIndex(a => {
|
||||
return a instanceof viewClass;
|
||||
}) != -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/** 贝塞尔帮助类 */
|
||||
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 Vector2.add(Vector2.add(Vector2.multiply(new Vector2(oneMinusT * oneMinusT), p0),
|
||||
Vector2.multiply(new Vector2(2 * oneMinusT * t), p1)), Vector2.multiply(new Vector2(t * t), p2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到二次贝塞尔函数的一阶导数
|
||||
* @param p0
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param t
|
||||
*/
|
||||
public static getFirstDerivative(p0: Vector2, p1: Vector2, p2: Vector2, t: number) {
|
||||
return Vector2.add(Vector2.multiply(new Vector2(2 * (1 - t)), Vector2.subtract(p1, p0)),
|
||||
Vector2.multiply(new Vector2(2 * t), Vector2.subtract(p2, p1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到一个三次贝塞尔函数的一阶导数
|
||||
* @param start
|
||||
* @param firstControlPoint
|
||||
* @param secondControlPoint
|
||||
* @param end
|
||||
* @param t
|
||||
*/
|
||||
public static getFirstDerivativeThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
|
||||
end: Vector2, t: number) {
|
||||
t = MathHelper.clamp01(t);
|
||||
let oneMunusT = 1 - t;
|
||||
return Vector2.add(Vector2.add(Vector2.multiply(new Vector2(3 * oneMunusT * oneMunusT), Vector2.subtract(firstControlPoint, start)),
|
||||
Vector2.multiply(new Vector2(6 * oneMunusT * t), Vector2.subtract(secondControlPoint, firstControlPoint))),
|
||||
Vector2.multiply(new Vector2(3 * t * t), Vector2.subtract(end, secondControlPoint)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算一个三次贝塞尔
|
||||
* @param start
|
||||
* @param firstControlPoint
|
||||
* @param secondControlPoint
|
||||
* @param end
|
||||
* @param t
|
||||
*/
|
||||
public static getPointThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
|
||||
end: Vector2, t: number) {
|
||||
t = MathHelper.clamp01(t);
|
||||
let oneMunusT = 1 - t;
|
||||
return Vector2.add(Vector2.add(Vector2.add(Vector2.multiply(new Vector2(oneMunusT * oneMunusT * oneMunusT), start),
|
||||
Vector2.multiply(new Vector2(3 * oneMunusT * oneMunusT * t), firstControlPoint)),
|
||||
Vector2.multiply(new Vector2(3 * oneMunusT * t * t), secondControlPoint)),
|
||||
Vector2.multiply(new Vector2(t * t * t), end));
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归地细分bezier曲线,直到满足距离校正
|
||||
* 在这种算法中,平面切片的点要比曲面切片少。返回完成后应返回到ListPool的合并列表。
|
||||
* @param start
|
||||
* @param firstCtrlPoint
|
||||
* @param secondCtrlPoint
|
||||
* @param end
|
||||
* @param distanceTolerance
|
||||
*/
|
||||
public static getOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
|
||||
end: Vector2, distanceTolerance: number = 1) {
|
||||
let points = ListPool.obtain<Vector2>();
|
||||
points.push(start);
|
||||
this.recursiveGetOptimizedDrawingPoints(start, firstCtrlPoint, secondCtrlPoint, end, points, distanceTolerance);
|
||||
points.push(end);
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归地细分bezier曲线,直到满足距离校正。在这种算法中,平面切片的点要比曲面切片少。
|
||||
* @param start
|
||||
* @param firstCtrlPoint
|
||||
* @param secondCtrlPoint
|
||||
* @param end
|
||||
* @param points
|
||||
* @param distanceTolerance
|
||||
*/
|
||||
private static recursiveGetOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
|
||||
end: Vector2, points: Vector2[], distanceTolerance: number) {
|
||||
// 计算线段的所有中点
|
||||
let pt12 = Vector2.divide(Vector2.add(start, firstCtrlPoint), new Vector2(2));
|
||||
let pt23 = Vector2.divide(Vector2.add(firstCtrlPoint, secondCtrlPoint), new Vector2(2));
|
||||
let pt34 = Vector2.divide(Vector2.add(secondCtrlPoint, end), new Vector2(2));
|
||||
|
||||
// 计算新半直线的中点
|
||||
let pt123 = Vector2.divide(Vector2.add(pt12, pt23), new Vector2(2));
|
||||
let pt234 = Vector2.divide(Vector2.add(pt23, pt34), new Vector2(2));
|
||||
|
||||
// 最后再细分最后两个中点。如果我们满足我们的距离公差,这将是我们使用的最后一点。
|
||||
let pt1234 = Vector2.divide(Vector2.add(pt123, pt234), new Vector2(2));
|
||||
|
||||
// 试着用一条直线来近似整个三次曲线
|
||||
let deltaLine = Vector2.subtract(end, start);
|
||||
|
||||
let d2 = Math.abs(((firstCtrlPoint.x, end.x) * deltaLine.y - (firstCtrlPoint.y - end.y) * deltaLine.x));
|
||||
let d3 = Math.abs(((secondCtrlPoint.x - end.x) * deltaLine.y - (secondCtrlPoint.y - end.y) * deltaLine.x));
|
||||
|
||||
if ((d2 + d3) * (d2 + d3) < distanceTolerance * (deltaLine.x * deltaLine.x + deltaLine.y * deltaLine.y)) {
|
||||
points.push(pt1234);
|
||||
return;
|
||||
}
|
||||
|
||||
// 继续细分
|
||||
this.recursiveGetOptimizedDrawingPoints(start, pt12, pt123, pt1234, points, distanceTolerance);
|
||||
this.recursiveGetOptimizedDrawingPoints(pt1234, pt234, pt34, end, points, distanceTolerance);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class Flags {
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static isFlagSet(self: number, flag: number){
|
||||
public static isFlagSet(self: number, flag: number): boolean{
|
||||
return (self & flag) != 0;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class Flags {
|
||||
* @param self
|
||||
* @param flag
|
||||
*/
|
||||
public static isUnshiftedFlagSet(self: number, flag: number){
|
||||
public static isUnshiftedFlagSet(self: number, flag: number): boolean{
|
||||
flag = 1 << flag;
|
||||
return (self & flag) != 0;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,29 @@ class MathHelper {
|
||||
return new Vector2(Math.cos(radians) * radians + circleCenter.x, Math.sin(radians) * radians + circleCenter.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果值为偶数,返回true
|
||||
* @param value
|
||||
*/
|
||||
public static isEven(value: number){
|
||||
return value % 2 == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数值限定在0-1之间
|
||||
* @param value
|
||||
*/
|
||||
public static clamp01(value: number){
|
||||
if (value < 0)
|
||||
return 0;
|
||||
|
||||
if (value > 1)
|
||||
return 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static angleBetweenVectors(from: Vector2, to: Vector2){
|
||||
return Math.atan2(to.y - from.y, to.x - from.x);
|
||||
}
|
||||
}
|
||||
@@ -155,8 +155,13 @@ class Matrix2D {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static createTranslation(xPosition: number, yPosition: number, result?: Matrix2D){
|
||||
result = result ? result : new Matrix2D();
|
||||
/**
|
||||
* 创建一个新的tranlation
|
||||
* @param xPosition
|
||||
* @param yPosition
|
||||
*/
|
||||
public static createTranslation(xPosition: number, yPosition: number){
|
||||
let result = new Matrix2D();
|
||||
|
||||
result.m11 = 1;
|
||||
result.m12 = 0;
|
||||
@@ -170,6 +175,14 @@ class Matrix2D {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据position 创建一个translation
|
||||
* @param position
|
||||
*/
|
||||
public static createTranslationVector(position: Vector2){
|
||||
return this.createTranslation(position.x, position.y);
|
||||
}
|
||||
|
||||
public static createRotation(radians: number, result?: Matrix2D){
|
||||
result = new Matrix2D();
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
class Point {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(x?: number, y?: number){
|
||||
this.x = x ? x : 0;
|
||||
this.y = y ? y : this.x;
|
||||
}
|
||||
}
|
||||
+71
-104
@@ -1,36 +1,21 @@
|
||||
class Rectangle {
|
||||
public x: number;
|
||||
public y: number;
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
private _tempMat: Matrix2D;
|
||||
private _transformMat: Matrix2D;
|
||||
|
||||
public get left() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public get right() {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
public get top() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public get bottom() {
|
||||
return this.y + this.height;
|
||||
class Rectangle extends egret.Rectangle {
|
||||
/**
|
||||
* 获取矩形的最大点,即右下角
|
||||
*/
|
||||
public get max() {
|
||||
return new Vector2(this.right, this.bottom);
|
||||
}
|
||||
|
||||
/** 中心点坐标 */
|
||||
public get center() {
|
||||
return new Vector2(this.x + (this.width / 2), this.y + (this.height / 2));
|
||||
}
|
||||
|
||||
/** 左上角的坐标 */
|
||||
public get location() {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
/** 左上角的坐标 */
|
||||
public set location(value: Vector2) {
|
||||
this.x = value.x;
|
||||
this.y = value.y;
|
||||
@@ -45,48 +30,56 @@ class Rectangle {
|
||||
this.height = value.y;
|
||||
}
|
||||
|
||||
constructor(x?: number, y?: number, width?: number, height?: number) {
|
||||
this.x = x ? x : 0;
|
||||
this.y = y ? y : 0;
|
||||
this.width = width ? width : 0;
|
||||
this.height = height ? height : 0;
|
||||
}
|
||||
|
||||
public intersects(value: Rectangle) {
|
||||
/**
|
||||
* 是否与另一个矩形相交
|
||||
* @param value
|
||||
*/
|
||||
public intersects(value: egret.Rectangle) {
|
||||
return value.left < this.right &&
|
||||
this.left < value.right &&
|
||||
value.top < this.bottom &&
|
||||
this.top < value.bottom;
|
||||
}
|
||||
|
||||
public contains(value: Vector2) {
|
||||
return ((((this.x <= value.x) && (value.x < (this.x + this.width))) &&
|
||||
(this.y <= value.y)) &&
|
||||
(value.y < (this.y + this.height)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所提供的矩形是否在此矩形的边界内
|
||||
* @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(){
|
||||
public getHalfSize() {
|
||||
return new Vector2(this.width * 0.5, this.height * 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个矩形的最小/最大点(左上角,右下角的点)
|
||||
* @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);
|
||||
}
|
||||
|
||||
public getClosestPointOnRectangleBorderToPoint(point: Point): { res: Vector2, edgeNormal: Vector2 } {
|
||||
let edgeNormal = new Vector2(0, 0);
|
||||
/**
|
||||
* 获取矩形边界上与给定点最近的点
|
||||
* @param point
|
||||
*/
|
||||
public getClosestPointOnRectangleBorderToPoint(point: Vector2): { res: Vector2, edgeNormal: Vector2 } {
|
||||
let edgeNormal = Vector2.zero;
|
||||
|
||||
let res = new Vector2(0, 0);
|
||||
// 对于每个轴,如果点在盒子外面
|
||||
let res = new Vector2();
|
||||
res.x = MathHelper.clamp(point.x, this.left, this.right);
|
||||
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
|
||||
|
||||
if (this.contains(res)) {
|
||||
// 如果点在矩形内,我们需要推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;
|
||||
@@ -107,68 +100,50 @@ class Rectangle {
|
||||
edgeNormal.x = 1;
|
||||
}
|
||||
} else {
|
||||
if (res.x == this.left) {
|
||||
edgeNormal.x = -1;
|
||||
}
|
||||
if (res.x == this.right) {
|
||||
edgeNormal.x = 1;
|
||||
}
|
||||
if (res.y == this.top) {
|
||||
edgeNormal.y = -1;
|
||||
}
|
||||
if (res.y == this.bottom) {
|
||||
edgeNormal.y = 1;
|
||||
}
|
||||
if (res.x == this.left) edgeNormal.x = -1;
|
||||
if (res.x == this.right) edgeNormal.x = 1;
|
||||
if (res.y == this.top) edgeNormal.y = -1;
|
||||
if (res.y == this.bottom) edgeNormal.y = 1;
|
||||
}
|
||||
|
||||
return { res: res, edgeNormal: edgeNormal };
|
||||
}
|
||||
|
||||
public calculateBounds(parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
|
||||
rotation: number, width: number, height: number) {
|
||||
if (rotation == 0) {
|
||||
this.x = parentPosition.x + position.x - origin.x * scale.x;
|
||||
this.y = parentPosition.y + position.y - origin.y * scale.y;
|
||||
this.width = width * scale.x;
|
||||
this.height = height * scale.y;
|
||||
} else {
|
||||
let worldPosX = parentPosition.x + position.x;
|
||||
let worldPosY = parentPosition.y + position.y;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public getClosestPointOnBoundsToOrigin() {
|
||||
let max = this.max;
|
||||
let minDist = Math.abs(this.location.x);
|
||||
let boundsPoint = new Vector2(this.location.x, 0);
|
||||
|
||||
this._transformMat = Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y);
|
||||
this._tempMat = Matrix2D.createScale(scale.x, scale.y);
|
||||
this._transformMat = Matrix2D.multiply(this._transformMat, this._tempMat);
|
||||
this._tempMat = Matrix2D.createRotation(rotation);
|
||||
this._transformMat = Matrix2D.multiply(this._transformMat, this._tempMat);
|
||||
this._tempMat = Matrix2D.createTranslation(worldPosX, worldPosY);
|
||||
this._transformMat = Matrix2D.multiply(this._transformMat, this._tempMat);
|
||||
|
||||
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);
|
||||
|
||||
topLeft = Vector2Ext.transformR(topLeft, this._transformMat);
|
||||
topRight = Vector2Ext.transformR(topRight, this._transformMat);
|
||||
bottomLeft = Vector2Ext.transformR(bottomLeft, this._transformMat);
|
||||
bottomRight = Vector2Ext.transformR(bottomRight, this._transformMat);
|
||||
|
||||
let minX = Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
|
||||
let maxX = Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
|
||||
let minY = Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
|
||||
let maxY = Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
|
||||
|
||||
this.location = new Vector2(minX, minY);
|
||||
this.width = maxX - minX;
|
||||
this.height = maxY - minY;
|
||||
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 points
|
||||
*/
|
||||
public static rectEncompassingPoints(points: Vector2[]) {
|
||||
// 我们需要求出x/y的最小值/最大值
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
let maxX = Number.NEGATIVE_INFINITY;
|
||||
@@ -177,19 +152,11 @@ class Rectangle {
|
||||
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.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;
|
||||
}
|
||||
if (pt.y < minY) minY = pt.y;
|
||||
if (pt.y > maxY) maxY = pt.y;
|
||||
}
|
||||
|
||||
return this.fromMinMax(minX, minY, maxX, maxY);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** 移动器使用的帮助器类,用于管理触发器碰撞器交互并调用itriggerlistener。 */
|
||||
class ColliderTriggerHelper {
|
||||
private _entity: Entity;
|
||||
/** 存储当前帧中发生的所有活动交集对 */
|
||||
@@ -18,7 +19,9 @@ class ColliderTriggerHelper {
|
||||
for (let i = 0; i < colliders.length; i++) {
|
||||
let collider = colliders[i];
|
||||
|
||||
let neighbors = Physics.boxcastBroadphase(collider.bounds, collider.collidesWithLayers);
|
||||
let boxcastResult = Physics.boxcastBroadphase(collider.bounds, collider.collidesWithLayers);
|
||||
collider.bounds = boxcastResult.rect;
|
||||
let neighbors = boxcastResult.colliders;
|
||||
for (let j = 0; j < neighbors.length; j++) {
|
||||
let neighbor = neighbors[j];
|
||||
if (!collider.isTrigger && !neighbor.isTrigger)
|
||||
|
||||
@@ -2,13 +2,16 @@ class Physics {
|
||||
private static _spatialHash: SpatialHash;
|
||||
/** 调用reset并创建一个新的SpatialHash时使用的单元格大小 */
|
||||
public static spatialHashCellSize = 100;
|
||||
|
||||
/** 接受layerMask的所有方法的默认值 */
|
||||
public static readonly allLayers: number = -1;
|
||||
|
||||
public static reset(){
|
||||
this._spatialHash = new SpatialHash(this.spatialHashCellSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SpatialHash中移除所有碰撞器
|
||||
*/
|
||||
public static clear(){
|
||||
this._spatialHash.clear();
|
||||
}
|
||||
@@ -18,23 +21,44 @@ class Physics {
|
||||
}
|
||||
|
||||
public static boxcastBroadphase(rect: Rectangle, layerMask: number = this.allLayers){
|
||||
return this._spatialHash.aabbBroadphase(rect, null, layerMask);
|
||||
let boxcastResult = this._spatialHash.aabbBroadphase(rect, null, layerMask);
|
||||
return {colliders: boxcastResult.tempHashSet, rect: boxcastResult.bounds};
|
||||
}
|
||||
|
||||
public static boxcastBroadphaseExcludingSelf(collider: Collider, rect: Rectangle, layerMask = this.allLayers){
|
||||
return this._spatialHash.aabbBroadphase(rect, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* debug绘制空间散列的内容
|
||||
* @param secondsToDisplay
|
||||
*/
|
||||
public static debugDraw(secondsToDisplay){
|
||||
this._spatialHash.debugDraw(secondsToDisplay, 2);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
///<reference path="./Polygon.ts" />
|
||||
/**
|
||||
* 多边形的特殊情况。在进行SAT碰撞检查时,我们只需要检查2个轴而不是8个轴
|
||||
*/
|
||||
class Box extends Polygon {
|
||||
public width: number;
|
||||
public height: number;
|
||||
@@ -9,7 +12,13 @@ class Box extends Polygon {
|
||||
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);
|
||||
@@ -21,10 +30,40 @@ class Box extends Polygon {
|
||||
return verts;
|
||||
}
|
||||
|
||||
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.isRectToCircle(this.bounds, other.position, other.radius);
|
||||
}
|
||||
|
||||
return super.overlaps(other);
|
||||
}
|
||||
|
||||
public collidesWithShape(other: Shape){
|
||||
// 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测
|
||||
if (this.isUnrotated && other instanceof Box && other.isUnrotated){
|
||||
return ShapeCollisions.boxToBox(this, other);
|
||||
}
|
||||
|
||||
// TODO: 让 minkowski 运行于 cricleToBox
|
||||
|
||||
return super.collidesWithShape(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新框点,重新计算中心,设置宽度/高度
|
||||
* @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;
|
||||
|
||||
@@ -39,7 +78,7 @@ class Box extends Polygon {
|
||||
|
||||
public containsPoint(point: Vector2){
|
||||
if (this.isUnrotated)
|
||||
return this.bounds.contains(point);
|
||||
return this.bounds.contains(point.x, point.y);
|
||||
|
||||
return super.containsPoint(point);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
///<reference path="./Shape.ts" />
|
||||
class Circle extends Shape {
|
||||
public radius: number;
|
||||
private _originalRadius: number;
|
||||
public _originalRadius: number;
|
||||
public center = new Vector2();
|
||||
|
||||
constructor(radius: number) {
|
||||
super();
|
||||
@@ -32,7 +33,7 @@ class Circle extends Shape {
|
||||
public recalculateBounds(collider: Collider) {
|
||||
this.center = collider.localOffset;
|
||||
|
||||
if (collider.shouldColliderScaleAndRotationWithTransform) {
|
||||
if (collider.shouldColliderScaleAndRotateWithTransform) {
|
||||
let scale = collider.entity.scale;
|
||||
let hasUnitScale = scale.x == 1 && scale.y == 1;
|
||||
let maxScale = Math.max(scale.x, scale.y);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
class CollisionResult {
|
||||
public collider: Collider;
|
||||
public minimumTranslationVector: Vector2;
|
||||
public normal: Vector2;
|
||||
public point: Vector2;
|
||||
public minimumTranslationVector: Vector2 = Vector2.zero;
|
||||
public normal: Vector2 = Vector2.zero;
|
||||
public point: Vector2 = Vector2.zero;
|
||||
|
||||
public invertResult(){
|
||||
this.minimumTranslationVector = Vector2.negate(this.minimumTranslationVector);
|
||||
|
||||
@@ -5,6 +5,7 @@ class Polygon extends Shape {
|
||||
private _polygonCenter: Vector2;
|
||||
private _areEdgeNormalsDirty = true;
|
||||
protected _originalPoints: Vector2[];
|
||||
public center = new Vector2();
|
||||
|
||||
public _edgeNormals: Vector2[];
|
||||
public get edgeNormals(){
|
||||
@@ -92,6 +93,10 @@ class Polygon extends Shape {
|
||||
throw new Error(`overlaps of Pologon to ${other} are not supported`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到多边形的中心。注意,这对于正则多边形是准确的。不规则多边形没有中心。
|
||||
* @param points
|
||||
*/
|
||||
public static findPolygonCenter(points: Vector2[]) {
|
||||
let x = 0, y = 0;
|
||||
|
||||
@@ -103,6 +108,23 @@ class Polygon extends Shape {
|
||||
return new Vector2(x / points.length, y / points.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定位多边形的点
|
||||
* @param points
|
||||
*/
|
||||
public static recenterPolygonVerts(points: Vector2[]){
|
||||
let center = this.findPolygonCenter(points);
|
||||
for (let i = 0; i < points.length; i ++)
|
||||
points[i] = Vector2.subtract(points[i], center);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迭代多边形的所有边,并得到任意边上离点最近的点。
|
||||
* 通过最近点的平方距离和它所在的边的法线返回。
|
||||
* 点应该在多边形的空间中(点-多边形.位置)
|
||||
* @param points
|
||||
* @param point
|
||||
*/
|
||||
public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2): { closestPoint, distanceSquared, edgeNormal } {
|
||||
let distanceSquared = Number.MAX_VALUE;
|
||||
let edgeNormal = new Vector2(0, 0);
|
||||
@@ -121,6 +143,7 @@ class Polygon extends Shape {
|
||||
distanceSquared = tempDistanceSquared;
|
||||
closestPoint = closest;
|
||||
|
||||
// 求直线的法线
|
||||
let line = Vector2.subtract(points[j], points[i]);
|
||||
edgeNormal.x = -line.y;
|
||||
edgeNormal.y = line.x;
|
||||
@@ -136,7 +159,13 @@ class Polygon extends Shape {
|
||||
return ShapeCollisions.pointToPoly(point, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本质上,这个算法所做的就是从一个点发射一条射线。
|
||||
* 如果它与奇数条多边形边相交,我们就知道它在多边形内部。
|
||||
* @param point
|
||||
*/
|
||||
public containsPoint(point: Vector2) {
|
||||
// 将点归一化到多边形坐标空间中
|
||||
point = Vector2.subtract(point, this.position);
|
||||
|
||||
let isInside = false;
|
||||
@@ -168,9 +197,10 @@ class Polygon extends Shape {
|
||||
}
|
||||
|
||||
public recalculateBounds(collider: Collider) {
|
||||
this.center = collider.localOffset;
|
||||
|
||||
if (collider.shouldColliderScaleAndRotationWithTransform){
|
||||
// 如果我们没有旋转或不关心TRS我们使用localOffset作为中心,我们会从那开始
|
||||
// this.center = collider.localOffset;
|
||||
let localOffset = collider.localOffset;
|
||||
if (collider.shouldColliderScaleAndRotateWithTransform){
|
||||
let hasUnitScale = true;
|
||||
let tempMat: Matrix2D;
|
||||
let combinedMatrix = Matrix2D.createTranslation(-this._polygonCenter.x, -this._polygonCenter.y);
|
||||
@@ -180,31 +210,34 @@ class Polygon extends Shape {
|
||||
combinedMatrix = Matrix2D.multiply(combinedMatrix, tempMat);
|
||||
|
||||
hasUnitScale = false;
|
||||
|
||||
// 缩放偏移量并将其设置为中心。如果我们有旋转,它会在下面重置
|
||||
let scaledOffset = Vector2.multiply(collider.localOffset, collider.entity.scale);
|
||||
this.center = scaledOffset;
|
||||
localOffset = scaledOffset;
|
||||
}
|
||||
|
||||
if (collider.entity.rotation != 0){
|
||||
tempMat = Matrix2D.createRotation(collider.entity.rotation);
|
||||
tempMat = Matrix2D.createRotation(collider.entity.rotation, tempMat);
|
||||
combinedMatrix = Matrix2D.multiply(combinedMatrix, tempMat);
|
||||
|
||||
// 为了处理偏移原点的旋转我们只需要将圆心在(0,0)附近移动我们的偏移使角度为0
|
||||
// 我们还需要处理这里的比例所以我们先对偏移进行缩放以得到合适的长度。
|
||||
let offsetAngle = Math.atan2(collider.localOffset.y, collider.localOffset.x) * MathHelper.Rad2Deg;
|
||||
let offsetLength = hasUnitScale ? collider._localOffsetLength : (Vector2.multiply(collider.localOffset, collider.entity.scale)).length();
|
||||
this.center = MathHelper.pointOnCirlce(Vector2.zero, offsetLength, MathHelper.toDegrees(collider.entity.rotation) + offsetAngle);
|
||||
localOffset = MathHelper.pointOnCirlce(Vector2.zero, offsetLength, MathHelper.toDegrees(collider.entity.rotation) + offsetAngle);
|
||||
}
|
||||
|
||||
tempMat = Matrix2D.createTranslation(this._polygonCenter.x, this._polygonCenter.y);
|
||||
combinedMatrix = Matrix2D.multiply(combinedMatrix, tempMat);
|
||||
|
||||
// 最后变换原始点
|
||||
Vector2Ext.transform(this._originalPoints, combinedMatrix, this.points);
|
||||
this.isUnrotated = collider.entity.rotation == 0;
|
||||
|
||||
if (collider._isRotationDirty)
|
||||
this._areEdgeNormalsDirty = true;
|
||||
}
|
||||
|
||||
this.position = Vector2.add(collider.entity.position, this.center);
|
||||
this.position = Vector2.add(collider.entity.position, localOffset);
|
||||
this.bounds = Rectangle.rectEncompassingPoints(this.points);
|
||||
this.bounds.location = Vector2.add(this.bounds.location, this.position);
|
||||
this.center = localOffset;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
abstract class Shape {
|
||||
public bounds: Rectangle;
|
||||
public position: Vector2;
|
||||
public center: Vector2;
|
||||
public bounds: Rectangle = new Rectangle();
|
||||
public position: Vector2 = Vector2.zero;
|
||||
public abstract center: Vector2;
|
||||
|
||||
public abstract recalculateBounds(collider: Collider);
|
||||
public abstract pointCollidesWithShape(point: Vector2): CollisionResult;
|
||||
|
||||
@@ -15,13 +15,17 @@ class ShapeCollisions {
|
||||
let polygonOffset = Vector2.subtract(first.position, second.position);
|
||||
let axis: Vector2;
|
||||
|
||||
// 循环穿过两个多边形的所有边
|
||||
for (let edgeIndex = 0; edgeIndex < firstEdges.length + secondEdges.length; edgeIndex++) {
|
||||
// 1. 找出当前多边形是否相交
|
||||
// 多边形的归一化轴垂直于缓存给我们的当前边
|
||||
if (edgeIndex < firstEdges.length) {
|
||||
axis = firstEdges[edgeIndex];
|
||||
} else {
|
||||
axis = secondEdges[edgeIndex - firstEdges.length];
|
||||
}
|
||||
|
||||
// 求多边形在当前轴上的投影
|
||||
let minA = 0;
|
||||
let minB = 0;
|
||||
let maxA = 0;
|
||||
@@ -34,17 +38,24 @@ class ShapeCollisions {
|
||||
minB = tb.min;
|
||||
maxB = tb.max;
|
||||
|
||||
// 将区间设为第二个多边形的空间。由轴上投影的位置差偏移。
|
||||
let relativeIntervalOffset = Vector2.dot(polygonOffset, axis);
|
||||
minA += relativeIntervalOffset;
|
||||
maxA += relativeIntervalOffset;
|
||||
|
||||
// 检查多边形投影是否正在相交
|
||||
intervalDist = this.intervalDistance(minA, maxA, minB, maxB);
|
||||
if (intervalDist > 0)
|
||||
isIntersecting = false;
|
||||
|
||||
// 对于多对多数据类型转换,添加一个Vector2?参数称为deltaMovement。为了提高速度,我们这里不使用它
|
||||
// TODO: 现在找出多边形是否会相交。只要检查速度就行了
|
||||
|
||||
// 如果多边形不相交,也不会相交,退出循环
|
||||
if (!isIntersecting)
|
||||
return null;
|
||||
|
||||
// 检查当前间隔距离是否为最小值。如果是,则存储间隔距离和当前距离。这将用于计算最小平移向量
|
||||
intervalDist = Math.abs(intervalDist);
|
||||
if (intervalDist < minIntervalDistance) {
|
||||
minIntervalDistance = intervalDist;
|
||||
@@ -55,8 +66,9 @@ class ShapeCollisions {
|
||||
}
|
||||
}
|
||||
|
||||
// 利用最小平移向量对多边形进行推入。
|
||||
result.normal = translationAxis;
|
||||
result.minimumTranslationVector = Vector2.multiply(new Vector2(-translationAxis), new Vector2(minIntervalDistance));
|
||||
result.minimumTranslationVector = Vector2.multiply(new Vector2(-translationAxis.x, -translationAxis.y), new Vector2(minIntervalDistance));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -252,4 +264,39 @@ class ShapeCollisions {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param first
|
||||
* @param second
|
||||
*/
|
||||
public static boxToBox(first: Box, second: Box){
|
||||
let result = new CollisionResult();
|
||||
|
||||
let minkowskiDiff = this.minkowskiDifference(first, second);
|
||||
if (minkowskiDiff.contains(0, 0)){
|
||||
// 计算MTV。如果它是零,我们就可以称它为非碰撞
|
||||
result.minimumTranslationVector = minkowskiDiff.getClosestPointOnBoundsToOrigin();
|
||||
|
||||
if (result.minimumTranslationVector.x == 0 && result.minimumTranslationVector.y == 0)
|
||||
return null;
|
||||
|
||||
result.normal = new Vector2(-result.minimumTranslationVector.x, -result.minimumTranslationVector.y);
|
||||
result.normal.normalize();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static minkowskiDifference(first: Box, second: Box){
|
||||
// 我们需要第一个框的左上角
|
||||
// 碰撞器只会修改运动的位置所以我们需要用位置来计算出运动是什么。
|
||||
let positionOffset = Vector2.subtract(first.position, Vector2.add(first.bounds.location, Vector2.divide(first.bounds.size, new Vector2(2))));
|
||||
let topLeft = Vector2.subtract(Vector2.add(first.bounds.location, positionOffset), second.bounds.max);
|
||||
let fullSize = Vector2.add(first.bounds.size, second.bounds.size);
|
||||
|
||||
return new Rectangle(topLeft.x, topLeft.y, fullSize.x, fullSize.y)
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,15 @@ class SpatialHash {
|
||||
public gridBounds: Rectangle = new Rectangle();
|
||||
|
||||
private _raycastParser: RaycastResultParser;
|
||||
/** 散列中每个单元格的大小 */
|
||||
private _cellSize: number;
|
||||
/** 1除以单元格大小。缓存结果,因为它被大量使用。 */
|
||||
private _inverseCellSize: number;
|
||||
private _overlapTestCircle: Circle;
|
||||
/** 缓存的循环用于重叠检查 */
|
||||
private _overlapTestCircle: Circle = new Circle(0);
|
||||
/** 用于返回冲突信息的共享HashSet */
|
||||
private _tempHashSet: Collider[] = [];
|
||||
/** 保存所有数据的字典 */
|
||||
private _cellDict: NumberDictionary = new NumberDictionary();
|
||||
|
||||
constructor(cellSize: number = 100) {
|
||||
@@ -14,6 +19,10 @@ class SpatialHash {
|
||||
this._raycastParser = new RaycastResultParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SpatialHash中删除对象
|
||||
* @param collider
|
||||
*/
|
||||
public remove(collider: Collider) {
|
||||
let bounds = collider.registeredPhysicsBounds;
|
||||
let p1 = this.cellCoords(bounds.x, bounds.y);
|
||||
@@ -21,6 +30,7 @@ class SpatialHash {
|
||||
|
||||
for (let x = p1.x; x <= p2.x; x++) {
|
||||
for (let y = p1.y; y <= p2.y; y++) {
|
||||
// 单元格应该始终存在,因为这个碰撞器应该在所有查询的单元格中
|
||||
let cell = this.cellAtPosition(x, y);
|
||||
if (!cell)
|
||||
console.error(`removing Collider [${collider}] from a cell that it is not present in`);
|
||||
@@ -30,22 +40,28 @@ class SpatialHash {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象添加到SpatialHash
|
||||
* @param collider
|
||||
*/
|
||||
public register(collider: Collider) {
|
||||
let bounds = collider.bounds;
|
||||
collider.registeredPhysicsBounds = bounds;
|
||||
let p1 = this.cellCoords(bounds.x, bounds.y);
|
||||
let p2 = this.cellCoords(bounds.right, bounds.bottom);
|
||||
|
||||
if (!this.gridBounds.contains(new Vector2(p1.x, p1.y))) {
|
||||
// 更新边界以跟踪网格大小
|
||||
if (!this.gridBounds.contains(p1.x, p1.y)) {
|
||||
this.gridBounds = RectangleExt.union(this.gridBounds, p1);
|
||||
}
|
||||
|
||||
if (!this.gridBounds.contains(new Vector2(p2.x, p2.y))) {
|
||||
if (!this.gridBounds.contains(p2.x, p2.y)) {
|
||||
this.gridBounds = RectangleExt.union(this.gridBounds, p2);
|
||||
}
|
||||
|
||||
for (let x = p1.x; x <= p2.x; x++) {
|
||||
for (let y = p1.y; y <= p2.y; y++) {
|
||||
// 如果没有单元格,我们需要创建它
|
||||
let c = this.cellAtPosition(x, y, true);
|
||||
c.push(collider);
|
||||
}
|
||||
@@ -56,6 +72,13 @@ class SpatialHash {
|
||||
this._cellDict.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取位于指定圆内的所有碰撞器
|
||||
* @param circleCenter
|
||||
* @param radius
|
||||
* @param results
|
||||
* @param layerMask
|
||||
*/
|
||||
public overlapCircle(circleCenter: Vector2, radius: number, results: Collider[], layerMask) {
|
||||
let bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2);
|
||||
|
||||
@@ -63,16 +86,29 @@ class SpatialHash {
|
||||
this._overlapTestCircle.position = circleCenter;
|
||||
|
||||
let resultCounter = 0;
|
||||
let potentials = this.aabbBroadphase(bounds, null, layerMask);
|
||||
let aabbBroadphaseResult = this.aabbBroadphase(bounds, null, layerMask);
|
||||
bounds = aabbBroadphaseResult.bounds;
|
||||
let potentials = aabbBroadphaseResult.tempHashSet;
|
||||
for (let i = 0; i < potentials.length; i++) {
|
||||
let collider = potentials[i];
|
||||
if (collider instanceof BoxCollider) {
|
||||
results[resultCounter] = collider;
|
||||
resultCounter++;
|
||||
} else if (collider instanceof CircleCollider) {
|
||||
if (collider.shape.overlaps(this._overlapTestCircle)){
|
||||
results[resultCounter] = collider;
|
||||
resultCounter ++;
|
||||
}
|
||||
} else if(collider instanceof PolygonCollider) {
|
||||
if (collider.shape.overlaps(this._overlapTestCircle)){
|
||||
results[resultCounter] = collider;
|
||||
resultCounter ++;
|
||||
}
|
||||
} else {
|
||||
throw new Error("overlapCircle against this collider type is not implemented!");
|
||||
}
|
||||
|
||||
// 如果我们所有的结果数据有了则返回
|
||||
if (resultCounter == results.length)
|
||||
return resultCounter;
|
||||
}
|
||||
@@ -80,6 +116,12 @@ class SpatialHash {
|
||||
return resultCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回边框与单元格相交的所有对象
|
||||
* @param bounds
|
||||
* @param excludeCollider
|
||||
* @param layerMask
|
||||
*/
|
||||
public aabbBroadphase(bounds: Rectangle, excludeCollider: Collider, layerMask: number) {
|
||||
this._tempHashSet.length = 0;
|
||||
|
||||
@@ -92,9 +134,11 @@ class SpatialHash {
|
||||
if (!cell)
|
||||
continue;
|
||||
|
||||
// 当cell不为空。循环并取回所有碰撞器
|
||||
for (let i = 0; i < cell.length; i++) {
|
||||
let collider = cell[i];
|
||||
|
||||
// 如果它是自身或者如果它不匹配我们的层掩码 跳过这个碰撞器
|
||||
if (collider == excludeCollider || !Flags.isFlagSet(layerMask, collider.physicsLayer))
|
||||
continue;
|
||||
|
||||
@@ -106,9 +150,16 @@ class SpatialHash {
|
||||
}
|
||||
}
|
||||
|
||||
return this._tempHashSet;
|
||||
return {tempHashSet: this._tempHashSet, bounds: bounds};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取世界空间x,y值的单元格。
|
||||
* 如果单元格为空且createCellIfEmpty为true,则会创建一个新的单元格
|
||||
* @param x
|
||||
* @param y
|
||||
* @param createCellIfEmpty
|
||||
*/
|
||||
private cellAtPosition(x: number, y: number, createCellIfEmpty: boolean = false) {
|
||||
let cell: Collider[] = this._cellDict.tryGetValue(x, y);
|
||||
if (!cell) {
|
||||
@@ -120,8 +171,32 @@ class SpatialHash {
|
||||
return cell;
|
||||
}
|
||||
|
||||
private cellCoords(x: number, y: number): Point {
|
||||
return new Point(Math.floor(x * this._inverseCellSize), Math.floor(y * this._inverseCellSize));
|
||||
/**
|
||||
* 获取单元格的x,y值作为世界空间的x,y值
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
private cellCoords(x: number, y: number): Vector2 {
|
||||
return new Vector2(Math.floor(x * this._inverseCellSize), Math.floor(y * this._inverseCellSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* debug绘制空间散列的内容
|
||||
* @param secondsToDisplay
|
||||
* @param textScale
|
||||
*/
|
||||
public debugDraw(secondsToDisplay: number, textScale: number = 1){
|
||||
for (let x = this.gridBounds.x; x <= this.gridBounds.right; x ++){
|
||||
for (let y = this.gridBounds.y; y <= this.gridBounds.bottom; y ++){
|
||||
let cell = this.cellAtPosition(x, y);
|
||||
if (cell && cell.length > 0)
|
||||
this.debugDrawCellDetails(x, y, cell.length, secondsToDisplay, textScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private debugDrawCellDetails(x: number, y: number, cellCount: number, secondsToDisplay = 0.5, textScale = 1){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +204,10 @@ class RaycastResultParser {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装一个Unit32,列表碰撞器字典
|
||||
* 它的主要目的是将int、int x、y坐标散列到单个Uint32键中,使用O(1)查找。
|
||||
*/
|
||||
class NumberDictionary {
|
||||
private _store: Map<number, Collider[]> = new Map<number, Collider[]>();
|
||||
|
||||
@@ -141,6 +220,10 @@ class NumberDictionary {
|
||||
return Long.fromNumber(x).shiftLeft(32).or(this.intToUint(y)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param i
|
||||
*/
|
||||
private intToUint(i) {
|
||||
if (i >= 0)
|
||||
return i;
|
||||
@@ -152,6 +235,10 @@ class NumberDictionary {
|
||||
this._store.set(this.getKey(x, y), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用蛮力方法从字典存储列表中移除碰撞器
|
||||
* @param obj
|
||||
*/
|
||||
public remove(obj: Collider) {
|
||||
this._store.forEach(list => {
|
||||
if (list.contains(obj))
|
||||
@@ -163,6 +250,9 @@ class NumberDictionary {
|
||||
return this._store.get(this.getKey(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除字典数据
|
||||
*/
|
||||
public clear() {
|
||||
this._store.clear();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 支持标题安全区的布局类。
|
||||
*/
|
||||
class Layout {
|
||||
public clientArea: Rectangle;
|
||||
public safeArea: Rectangle;
|
||||
|
||||
constructor(){
|
||||
this.clientArea = new Rectangle(0, 0, SceneManager.stage.stageWidth, SceneManager.stage.stageHeight);
|
||||
this.safeArea = this.clientArea;
|
||||
}
|
||||
|
||||
public place(size: Vector2, horizontalMargin: number, verticalMargine: number, alignment: Alignment){
|
||||
let rc = new Rectangle(0, 0, size.x, size.y);
|
||||
if ((alignment & Alignment.left) != 0){
|
||||
rc.x = this.clientArea.x + (this.clientArea.width * horizontalMargin);
|
||||
}else if((alignment & Alignment.right) != 0){
|
||||
rc.x = this.clientArea.x + (this.clientArea.width * (1 - horizontalMargin)) - rc.width;
|
||||
} else if((alignment & Alignment.horizontalCenter) != 0){
|
||||
rc.x = this.clientArea.x + (this.clientArea.width - rc.width) / 2 + (horizontalMargin * this.clientArea.width);
|
||||
}else{
|
||||
|
||||
}
|
||||
|
||||
if ((alignment & Alignment.top) != 0){
|
||||
rc.y = this.clientArea.y + (this.clientArea.height * verticalMargine);
|
||||
}else if((alignment & Alignment.bottom) != 0){
|
||||
rc.y = this.clientArea.y + (this.clientArea.height * (1 - verticalMargine)) - rc.height;
|
||||
} else if((alignment & Alignment.verticalCenter) != 0){
|
||||
rc.y = this.clientArea.y + (this.clientArea.height - rc.height) / 2 + (verticalMargine * this.clientArea.height);
|
||||
}else{
|
||||
|
||||
}
|
||||
|
||||
// 确保布局区域在安全区域内。
|
||||
if (rc.left < this.safeArea.left)
|
||||
rc.x = this.safeArea.left;
|
||||
|
||||
if (rc.right > this.safeArea.right)
|
||||
rc.x = this.safeArea.right - rc.width;
|
||||
|
||||
if (rc.top < this.safeArea.top)
|
||||
rc.y = this.safeArea.top;
|
||||
|
||||
if (rc.bottom > this.safeArea.bottom)
|
||||
rc.y = this.safeArea.bottom - rc.height;
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
enum Alignment {
|
||||
none = 0,
|
||||
left = 1,
|
||||
right = 2,
|
||||
horizontalCenter = 4,
|
||||
top = 8,
|
||||
bottom = 16,
|
||||
verticalCenter = 32,
|
||||
topLeft = top | left,
|
||||
topRight = top | right,
|
||||
topCenter = top | horizontalCenter,
|
||||
bottomLeft = bottom | left,
|
||||
bottomRight = bottom | right,
|
||||
bottomCenter = bottom | horizontalCenter,
|
||||
centerLeft = verticalCenter | left,
|
||||
centerRight = verticalCenter | right,
|
||||
center = verticalCenter | horizontalCenter
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
namespace stopwatch {
|
||||
/**
|
||||
* 记录时间的持续时间,一些设计灵感来自物理秒表。
|
||||
*/
|
||||
export class Stopwatch {
|
||||
/**
|
||||
* 秒表启动的系统时间。
|
||||
* undefined,如果秒表尚未启动,或已复位。
|
||||
*/
|
||||
private _startSystemTime: number | undefined;
|
||||
/**
|
||||
* 秒表停止的系统时间。
|
||||
* undefined,如果秒表目前没有停止,尚未开始,或已复位。
|
||||
*/
|
||||
private _stopSystemTime: number | undefined;
|
||||
/** 自上次复位以来,秒表已停止的系统时间总数。 */
|
||||
private _stopDuration: number = 0;
|
||||
/**
|
||||
* 用秒表计时,当前等待的切片开始的时间。
|
||||
* undefined,如果秒表尚未启动,或已复位。
|
||||
*/
|
||||
private _pendingSliceStartStopwatchTime: number | undefined;
|
||||
/**
|
||||
* 记录自上次复位以来所有已完成切片的结果。
|
||||
*/
|
||||
private _completeSlices: Slice[] = [];
|
||||
|
||||
constructor(private readonly getSystemTime = _defaultSystemTimeGetter) { }
|
||||
|
||||
public getState() {
|
||||
if (this._startSystemTime === undefined) {
|
||||
return State.IDLE;
|
||||
} else if (this._stopSystemTime === undefined) {
|
||||
return State.RUNNING;
|
||||
} else {
|
||||
return State.STOPPED;
|
||||
}
|
||||
}
|
||||
|
||||
public isIdle(){
|
||||
return this.getState() === State.IDLE;
|
||||
}
|
||||
|
||||
public isRunning(){
|
||||
return this.getState() === State.RUNNING;
|
||||
}
|
||||
|
||||
public isStopped(){
|
||||
return this.getState() === State.STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public slice(){
|
||||
return this.recordPendingSlice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自上次复位以来该秒表已完成/记录的所有片的列表。
|
||||
*/
|
||||
public getCompletedSlices(): Slice[] {
|
||||
return Array.from(this._completeSlices);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自上次重置以来该秒表已完成/记录的所有片的列表,以及当前挂起的片。
|
||||
*/
|
||||
public getCompletedAndPendingSlices(): Slice[] {
|
||||
return [...this._completeSlices, this.getPendingSlice()];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关于这个秒表当前挂起的切片的详细信息。
|
||||
*/
|
||||
public getPendingSlice(): Slice {
|
||||
return this.calculatePendingSlice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前秒表时间。这是这个秒表自上次复位以来运行的系统时间总数。
|
||||
*/
|
||||
public getTime(){
|
||||
return this.caculateStopwatchTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定秒表时间的当前挂起片。
|
||||
* @param endStopwatchTime
|
||||
*/
|
||||
private calculatePendingSlice(endStopwatchTime?: number): Slice {
|
||||
if (this._pendingSliceStartStopwatchTime === undefined){
|
||||
return Object.freeze({startTime: 0, endTime: 0, duration: 0});
|
||||
}
|
||||
|
||||
if (endStopwatchTime === undefined){
|
||||
endStopwatchTime = this.getTime();
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
startTime: this._pendingSliceStartStopwatchTime,
|
||||
endTime: endStopwatchTime,
|
||||
duration: endStopwatchTime - this._pendingSliceStartStopwatchTime
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定系统时间的当前秒表时间。
|
||||
* @param endSystemTime
|
||||
*/
|
||||
private caculateStopwatchTime(endSystemTime?: number){
|
||||
if (this._startSystemTime === undefined)
|
||||
return 0;
|
||||
|
||||
if (endSystemTime === undefined)
|
||||
endSystemTime = this.getSystemTimeOfCurrentStopwatchTime();
|
||||
|
||||
return endSystemTime - this._startSystemTime - this._stopDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与当前秒表时间等效的系统时间。
|
||||
* 如果该秒表当前停止,则返回该秒表停止时的系统时间。
|
||||
*/
|
||||
private getSystemTimeOfCurrentStopwatchTime(){
|
||||
return this._stopSystemTime === undefined ? this.getSystemTime() : this._stopSystemTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完全重置这个秒表到它的初始状态。清除所有记录的运行持续时间、切片等。
|
||||
*/
|
||||
public reset(){
|
||||
this._startSystemTime = this._pendingSliceStartStopwatchTime = this._stopSystemTime = undefined;
|
||||
this._stopDuration = 0;
|
||||
this._completeSlices = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始(或继续)运行秒表。
|
||||
* @param forceReset
|
||||
*/
|
||||
public start(forceReset: boolean = false){
|
||||
if (forceReset){
|
||||
this.reset();
|
||||
}
|
||||
|
||||
if (this._stopSystemTime !== undefined){
|
||||
const systemNow = this.getSystemTime();
|
||||
const stopDuration = systemNow - this._stopSystemTime;
|
||||
|
||||
this._stopDuration += stopDuration;
|
||||
this._stopSystemTime = undefined;
|
||||
} else if (this._startSystemTime === undefined){
|
||||
const systemNow = this.getSystemTime();
|
||||
this._startSystemTime = systemNow;
|
||||
this._pendingSliceStartStopwatchTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param recordPendingSlice
|
||||
*/
|
||||
public stop(recordPendingSlice: boolean = false){
|
||||
if (this._startSystemTime === undefined){
|
||||
return 0;
|
||||
}
|
||||
|
||||
const systemTimeOfStopwatchTime = this.getSystemTimeOfCurrentStopwatchTime();
|
||||
|
||||
if (recordPendingSlice){
|
||||
this.recordPendingSlice(this.caculateStopwatchTime(systemTimeOfStopwatchTime));
|
||||
}
|
||||
|
||||
this._stopSystemTime = systemTimeOfStopwatchTime;
|
||||
return this.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束/记录当前挂起的片的私有实现。
|
||||
* @param endStopwatchTime
|
||||
*/
|
||||
private recordPendingSlice(endStopwatchTime?: number){
|
||||
if (this._pendingSliceStartStopwatchTime !== undefined){
|
||||
if (endStopwatchTime === undefined){
|
||||
endStopwatchTime = this.getTime();
|
||||
}
|
||||
|
||||
const slice = this.calculatePendingSlice(endStopwatchTime);
|
||||
|
||||
this._pendingSliceStartStopwatchTime = slice.endTime;
|
||||
this._completeSlices.push(slice);
|
||||
return slice;
|
||||
} else {
|
||||
return this.calculatePendingSlice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回某个系统的“当前时间”的函数。
|
||||
* 惟一的要求是,对该函数的每次调用都必须返回一个大于或等于前一次对该函数的调用的数字。
|
||||
*/
|
||||
export type GetTimeFunc = () => number;
|
||||
|
||||
enum State {
|
||||
/** 秒表尚未启动,或已复位。 */
|
||||
IDLE = "IDLE",
|
||||
/** 秒表正在运行。 */
|
||||
RUNNING = "RUNNING",
|
||||
/** 秒表以前还在跑,但现在已经停了。 */
|
||||
STOPPED = "STOPPED"
|
||||
}
|
||||
|
||||
export function setDefaultSystemTimeGetter(systemTimeGetter: GetTimeFunc = Date.now) {
|
||||
_defaultSystemTimeGetter = systemTimeGetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由秒表记录的单个“薄片”的测量值
|
||||
*/
|
||||
interface Slice {
|
||||
/** 秒表显示的时间在这一片开始的时候。 */
|
||||
readonly startTime: number;
|
||||
/** 秒表在这片片尾的时间。 */
|
||||
readonly endTime: number;
|
||||
/** 该切片的运行时间 */
|
||||
readonly duration: number;
|
||||
}
|
||||
|
||||
/** 所有新实例的默认“getSystemTime”实现 */
|
||||
let _defaultSystemTimeGetter: GetTimeFunc = Date.now;
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* 通过使用这个类,您可以直观地找到瓶颈和基本的CPU使用情况。
|
||||
*/
|
||||
class TimeRuler {
|
||||
/** 最大条数 8 */
|
||||
public static readonly maxBars = 0;
|
||||
/** */
|
||||
public static readonly maxSamples = 256;
|
||||
/** 每条的最大嵌套调用 */
|
||||
public static readonly maxNestCall = 32;
|
||||
/** 条的高度(以像素为单位) */
|
||||
public static readonly barHeight = 8;
|
||||
/** 最大显示帧 */
|
||||
public static readonly maxSampleFrames = 4;
|
||||
/** 持续时间(帧数)为采取抓拍日志。 */
|
||||
public static readonly logSnapDuration = 120;
|
||||
public static readonly barPadding = 2;
|
||||
public static readonly autoAdjustDelay = 30;
|
||||
public static Instance: TimeRuler;
|
||||
private _frameKey = 'frame';
|
||||
private _logKey = 'log';
|
||||
|
||||
/** 每帧的日志 */
|
||||
private _logs: FrameLog[];
|
||||
/** 当前显示帧计数 */
|
||||
private sampleFrames: number;
|
||||
/** 获取/设置目标样本帧。 */
|
||||
public targetSampleFrames: number;
|
||||
/** 获取/设置计时器标尺宽度。 */
|
||||
public width: number;
|
||||
public enabled: true;
|
||||
/** TimerRuler画的位置。 */
|
||||
private _position: Vector2;
|
||||
/** 上一帧日志 */
|
||||
private _prevLog: FrameLog;
|
||||
/** 当前帧日志 */
|
||||
private _curLog: FrameLog;
|
||||
/** 当前帧数量 */
|
||||
private frameCount: number;
|
||||
/** */
|
||||
private markers: MarkerInfo[] = [];
|
||||
/** 秒表用来测量时间。 */
|
||||
private stopwacth: stopwatch.Stopwatch = new stopwatch.Stopwatch();
|
||||
/** 从标记名映射到标记id的字典。 */
|
||||
private _markerNameToIdMap: Map<string, number> = new Map<string, number>();
|
||||
/**
|
||||
* 你想在游戏开始时调用StartFrame更新方法。
|
||||
* 当游戏在固定时间步进模式下运行缓慢时,更新会多次调用。
|
||||
* 在这种情况下,我们应该忽略StartFrame调用。
|
||||
* 为此,我们只需一直跟踪StartFrame调用的次数,直到Draw被调用。
|
||||
*/
|
||||
private _updateCount: number;
|
||||
/** */
|
||||
public showLog = false;
|
||||
private _frameAdjust: number;
|
||||
|
||||
constructor() {
|
||||
TimeRuler.Instance = this;
|
||||
this._logs = new Array<FrameLog>(2);
|
||||
for (let i = 0; i < this._logs.length; ++i)
|
||||
this._logs[i] = new FrameLog();
|
||||
|
||||
this.sampleFrames = this.targetSampleFrames = 1;
|
||||
this.width = SceneManager.stage.stageWidth * 0.8;
|
||||
this.onGraphicsDeviceReset();
|
||||
}
|
||||
|
||||
private onGraphicsDeviceReset() {
|
||||
let layout = new Layout();
|
||||
this._position = layout.place(new Vector2(this.width, TimeRuler.barHeight), 0, 0.01, Alignment.bottomCenter).location;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public startFrame() {
|
||||
// 当这个方法被多次调用时,我们跳过重置帧。
|
||||
let lock = new LockUtils(this._frameKey);
|
||||
lock.lock().then(() => {
|
||||
this._updateCount = parseInt(egret.localStorage.getItem(this._frameKey), 10);
|
||||
let count = this._updateCount;
|
||||
count += 1;
|
||||
egret.localStorage.setItem(this._frameKey, count.toString());
|
||||
if (this.enabled && (1 < count && count < TimeRuler.maxSampleFrames))
|
||||
return;
|
||||
|
||||
// 更新当前帧日志。
|
||||
this._prevLog = this._logs[this.frameCount++ & 0x1];
|
||||
this._curLog = this._logs[this.frameCount & 0x1];
|
||||
|
||||
let endFrameTime = this.stopwacth.getTime();
|
||||
// 更新标记并创建日志。
|
||||
for (let barIndex = 0; barIndex < this._prevLog.bars.length; ++barIndex) {
|
||||
let prevBar = this._prevLog.bars[barIndex];
|
||||
let nextBar = this._curLog.bars[barIndex];
|
||||
|
||||
// 重新打开在前一帧中没有调用结束标记的标记。
|
||||
for (let nest = 0; nest < prevBar.nestCount; ++nest) {
|
||||
let markerIdx = prevBar.markerNests[nest];
|
||||
prevBar.markers[markerIdx].endTime = endFrameTime;
|
||||
nextBar.markerNests[nest] = nest;
|
||||
nextBar.markers[nest].markerId = prevBar.markers[markerIdx].markerId;
|
||||
nextBar.markers[nest].beginTime = 0;
|
||||
nextBar.markers[nest].endTime = -1;
|
||||
nextBar.markers[nest].color = prevBar.markers[markerIdx].color;
|
||||
}
|
||||
|
||||
// 更新日志标记
|
||||
for (let markerIdx = 0; markerIdx < prevBar.markCount; ++markerIdx) {
|
||||
let duration = prevBar.markers[markerIdx].endTime - prevBar.markers[markerIdx].beginTime;
|
||||
let markerId = prevBar.markers[markerIdx].markerId;
|
||||
let m = this.markers[markerId];
|
||||
|
||||
m.logs[barIndex].color = prevBar.markers[markerIdx].color;
|
||||
if (!m.logs[barIndex].initialized) {
|
||||
m.logs[barIndex].min = duration;
|
||||
m.logs[barIndex].max = duration;
|
||||
m.logs[barIndex].avg = duration;
|
||||
m.logs[barIndex].initialized = true;
|
||||
} else {
|
||||
m.logs[barIndex].min = Math.min(m.logs[barIndex].min, duration);
|
||||
m.logs[barIndex].max = Math.min(m.logs[barIndex].max, duration);
|
||||
m.logs[barIndex].avg += duration;
|
||||
m.logs[barIndex].avg *= 0.5;
|
||||
|
||||
if (m.logs[barIndex].samples++ >= TimeRuler.logSnapDuration) {
|
||||
m.logs[barIndex].snapMin = m.logs[barIndex].min;
|
||||
m.logs[barIndex].snapMax = m.logs[barIndex].max;
|
||||
m.logs[barIndex].snapAvg = m.logs[barIndex].avg;
|
||||
m.logs[barIndex].samples = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextBar.markCount = prevBar.nestCount;
|
||||
nextBar.nestCount = prevBar.nestCount;
|
||||
}
|
||||
|
||||
this.stopwacth.reset();
|
||||
this.stopwacth.start();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始测量时间。
|
||||
* @param markerName
|
||||
* @param color
|
||||
*/
|
||||
public beginMark(markerName: string, color: number, barIndex: number = 0) {
|
||||
let lock = new LockUtils(this._frameKey);
|
||||
lock.lock().then(() => {
|
||||
if (barIndex < 0 || barIndex >= TimeRuler.maxBars)
|
||||
throw new Error("barIndex argument out of range");
|
||||
|
||||
let bar = this._curLog.bars[barIndex];
|
||||
if (bar.markCount >= TimeRuler.maxSamples) {
|
||||
throw new Error("exceeded sample count. either set larger number to timeruler.maxsaple or lower sample count");
|
||||
}
|
||||
|
||||
if (bar.nestCount >= TimeRuler.maxNestCall) {
|
||||
throw new Error("exceeded nest count. either set larger number to timeruler.maxnestcall or lower nest calls");
|
||||
}
|
||||
|
||||
// 获取注册的标记
|
||||
let markerId = this._markerNameToIdMap.get(markerName);
|
||||
if (!markerId) {
|
||||
// 如果此标记未注册,则注册此标记。
|
||||
markerId = this.markers.length;
|
||||
this._markerNameToIdMap.set(markerName, markerId);
|
||||
}
|
||||
|
||||
bar.markerNests[bar.nestCount++] = bar.markCount;
|
||||
bar.markers[bar.markCount].markerId = markerId;
|
||||
bar.markers[bar.markCount].color = color;
|
||||
bar.markers[bar.markCount].beginTime = this.stopwacth.getTime();
|
||||
bar.markers[bar.markCount].endTime = -1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param markerName
|
||||
* @param barIndex
|
||||
*/
|
||||
public endMark(markerName: string, barIndex: number = 0) {
|
||||
let lock = new LockUtils(this._frameKey);
|
||||
lock.lock().then(() => {
|
||||
if (barIndex < 0 || barIndex >= TimeRuler.maxBars)
|
||||
throw new Error("barIndex argument out of range");
|
||||
|
||||
let bar = this._curLog.bars[barIndex];
|
||||
if (bar.nestCount <= 0) {
|
||||
throw new Error("call beginMark method before calling endMark method");
|
||||
}
|
||||
|
||||
let markerId = this._markerNameToIdMap.get(markerName);
|
||||
if (!markerId) {
|
||||
throw new Error(`Marker ${markerName} is not registered. Make sure you specifed same name as you used for beginMark method`);
|
||||
}
|
||||
|
||||
let markerIdx = bar.markerNests[--bar.nestCount];
|
||||
if (bar.markers[markerIdx].markerId != markerId) {
|
||||
throw new Error("Incorrect call order of beginMark/endMark method. beginMark(A), beginMark(B), endMark(B), endMark(A) But you can't called it like beginMark(A), beginMark(B), endMark(A), endMark(B).");
|
||||
}
|
||||
|
||||
bar.markers[markerIdx].endTime = this.stopwacth.getTime();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定bar索引和标记名称的平均时间。
|
||||
* @param barIndex
|
||||
* @param markerName
|
||||
*/
|
||||
public getAverageTime(barIndex: number, markerName: string) {
|
||||
if (barIndex < 0 || barIndex >= TimeRuler.maxBars) {
|
||||
throw new Error("barIndex argument out of range");
|
||||
}
|
||||
let result = 0;
|
||||
let markerId = this._markerNameToIdMap.get(markerName);
|
||||
if (markerId) {
|
||||
result = this.markers[markerId].logs[barIndex].avg;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public resetLog() {
|
||||
let lock = new LockUtils(this._logKey);
|
||||
lock.lock().then(() => {
|
||||
let count = parseInt(egret.localStorage.getItem(this._logKey), 10);
|
||||
count += 1;
|
||||
egret.localStorage.setItem(this._logKey, count.toString());
|
||||
this.markers.forEach(markerInfo => {
|
||||
for (let i = 0; i < markerInfo.logs.length; ++i){
|
||||
markerInfo.logs[i].initialized = false;
|
||||
markerInfo.logs[i].snapMin = 0;
|
||||
markerInfo.logs[i].snapMax = 0;
|
||||
markerInfo.logs[i].snapAvg = 0;
|
||||
|
||||
markerInfo.logs[i].min = 0;
|
||||
markerInfo.logs[i].max = 0;
|
||||
markerInfo.logs[i].avg = 0;
|
||||
|
||||
markerInfo.logs[i].samples = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(position: Vector2 = this._position, width: number = this.width){
|
||||
egret.localStorage.setItem(this._frameKey, "0");
|
||||
|
||||
if (!this.showLog)
|
||||
return;
|
||||
|
||||
let height = 0;
|
||||
let maxTime = 0;
|
||||
this._prevLog.bars.forEach(bar => {
|
||||
if (bar.markCount > 0){
|
||||
height += TimeRuler.barHeight + TimeRuler.barPadding * 2;
|
||||
maxTime = Math.max(maxTime, bar.markers[bar.markCount - 1].endTime);
|
||||
}
|
||||
})
|
||||
|
||||
const frameSpan = 1 / 60 * 1000;
|
||||
let sampleSpan = this.sampleFrames * frameSpan;
|
||||
|
||||
if (maxTime > sampleSpan){
|
||||
this._frameAdjust = Math.max(0, this._frameAdjust) + 1;
|
||||
}else{
|
||||
this._frameAdjust = Math.min(0, this._frameAdjust) - 1;
|
||||
}
|
||||
|
||||
if (Math.max(this._frameAdjust) > TimeRuler.autoAdjustDelay){
|
||||
this.sampleFrames = Math.min(TimeRuler.maxSampleFrames, this.sampleFrames);
|
||||
this.sampleFrames = Math.max(this.targetSampleFrames, (maxTime / frameSpan) + 1);
|
||||
|
||||
this._frameAdjust = 0;
|
||||
}
|
||||
|
||||
let msToPs = width / sampleSpan;
|
||||
let startY = position.y - (height - TimeRuler.barHeight);
|
||||
let y = startY;
|
||||
|
||||
// TODO: draw
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志信息
|
||||
*/
|
||||
class FrameLog {
|
||||
public bars: MarkerCollection[];
|
||||
|
||||
constructor() {
|
||||
this.bars = new Array<MarkerCollection>(TimeRuler.maxBars);
|
||||
for (let i = 0; i < TimeRuler.maxBars; ++i)
|
||||
this.bars[i] = new MarkerCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记的集合
|
||||
*/
|
||||
class MarkerCollection {
|
||||
public markers: Marker[] = new Array<Marker>(TimeRuler.maxSamples);
|
||||
public markCount: number;
|
||||
public markerNests: number[] = new Array<number>(TimeRuler.maxNestCall);
|
||||
public nestCount: number;
|
||||
}
|
||||
|
||||
class Marker {
|
||||
public markerId: number;
|
||||
public beginTime: number;
|
||||
public endTime: number;
|
||||
public color: number;
|
||||
}
|
||||
|
||||
class MarkerInfo {
|
||||
public name: string;
|
||||
public logs: MarkerLog[] = new Array<MarkerLog>(TimeRuler.maxBars);
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
class MarkerLog {
|
||||
public snapMin: number;
|
||||
public snapMax: number;
|
||||
public snapAvg: number;
|
||||
public min: number;
|
||||
public max: number;
|
||||
public avg: number;
|
||||
public samples: number;
|
||||
public color: number;
|
||||
public initialized: boolean;
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
class ArrayUtils {
|
||||
/**
|
||||
* 执行冒泡排序
|
||||
* @param ary
|
||||
* 算法参考 -- http://www.hiahia.org/datastructure/paixu/paixu8.3.1.1-1.htm
|
||||
*/
|
||||
public static bubbleSort(ary: number[]): void {
|
||||
let isExchange: Boolean = false;
|
||||
for (let i: number = 0; i < ary.length; i++) {
|
||||
isExchange = false;
|
||||
for (let j: number = ary.length - 1; j > i; j--) {
|
||||
if (ary[j] < ary[j - 1]) {
|
||||
let temp: number = ary[j];
|
||||
ary[j] = ary[j - 1];
|
||||
ary[j - 1] = temp;
|
||||
isExchange = true;
|
||||
}
|
||||
}
|
||||
if (!isExchange)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行插入排序
|
||||
* @param ary
|
||||
*/
|
||||
public static insertionSort(ary: number[]): void {
|
||||
let len: number = ary.length;
|
||||
for (let i: number = 1; i < len; i++) {
|
||||
let val: number = ary[i];
|
||||
for (var j: number = i; j > 0 && ary[j - 1] > val; j--) {
|
||||
ary[j] = ary[j - 1];
|
||||
}
|
||||
ary[j] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行二分搜索
|
||||
* @param ary 搜索的数组(必须排序过)
|
||||
* @param value 需要搜索的值
|
||||
* @return 返回匹配结果的数组索引
|
||||
*/
|
||||
public static binarySearch(ary: number[], value: number): number {
|
||||
let startIndex: number = 0;
|
||||
let endIndex: number = ary.length;
|
||||
let sub: number = (startIndex + endIndex) >> 1;
|
||||
while (startIndex < endIndex) {
|
||||
if (value <= ary[sub]) endIndex = sub;
|
||||
else if (value >= ary[sub]) startIndex = sub + 1;
|
||||
sub = (startIndex + endIndex) >> 1;
|
||||
}
|
||||
if (ary[startIndex] == value) return startIndex;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回匹配项的索引
|
||||
* @param ary
|
||||
* @param num
|
||||
* @return 返回匹配项的索引
|
||||
*/
|
||||
public static findElementIndex(ary: any[], num: any): any {
|
||||
let len: number = ary.length;
|
||||
for (let i: number = 0; i < len; ++i) {
|
||||
if (ary[i] == num)
|
||||
return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中最大值的索引
|
||||
* @param ary
|
||||
* @return 返回数组中最大值的索引
|
||||
*/
|
||||
public static getMaxElementIndex(ary: number[]): number {
|
||||
let matchIndex: number = 0;
|
||||
let len: number = ary.length;
|
||||
for (let j: number = 1; j < len; j++) {
|
||||
if (ary[j] > ary[matchIndex])
|
||||
matchIndex = j;
|
||||
}
|
||||
return matchIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中最小值的索引
|
||||
* @param ary
|
||||
* @return 返回数组中最小值的索引
|
||||
*/
|
||||
public static getMinElementIndex(ary: number[]): number {
|
||||
let matchIndex: number = 0;
|
||||
let len: number = ary.length;
|
||||
for (let j: number = 1; j < len; j++) {
|
||||
if (ary[j] < ary[matchIndex])
|
||||
matchIndex = j;
|
||||
}
|
||||
return matchIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个"唯一性"数组
|
||||
* @param ary 需要唯一性的数组
|
||||
* @return 唯一性的数组
|
||||
* 比如: [1, 2, 2, 3, 4]
|
||||
* 返回: [1, 2, 3, 4]
|
||||
*/
|
||||
public static getUniqueAry(ary: number[]): number[] {
|
||||
let uAry: number[] = [];
|
||||
let newAry: number[] = [];
|
||||
let count = ary.length;
|
||||
for (let i: number = 0; i < count; ++i) {
|
||||
let value: number = ary[i];
|
||||
if (uAry.indexOf(value) == -1) uAry.push(value);
|
||||
}
|
||||
|
||||
count = uAry.length;
|
||||
for (let i: number = count - 1; i >= 0; --i) {
|
||||
newAry.unshift(uAry[i]);
|
||||
}
|
||||
return newAry;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回2个数组中不同的部分
|
||||
* 比如数组A = [1, 2, 3, 4, 6]
|
||||
* 数组B = [0, 2, 1, 3, 4]
|
||||
* 返回[6, 0]
|
||||
* @param aryA
|
||||
* @param aryB
|
||||
* @return
|
||||
*/
|
||||
public static getDifferAry(aryA: number[], aryB: number[]): number[] {
|
||||
aryA = this.getUniqueAry(aryA);
|
||||
aryB = this.getUniqueAry(aryB);
|
||||
let ary: number[] = aryA.concat(aryB);
|
||||
let uObj: Object = new Object();
|
||||
let newAry: number[] = [];
|
||||
let count: number = ary.length;
|
||||
for (let j: number = 0; j < count; ++j) {
|
||||
if (!uObj[ary[j]]) {
|
||||
uObj[ary[j]] = new Object();
|
||||
uObj[ary[j]].count = 0;
|
||||
uObj[ary[j]].key = ary[j];
|
||||
uObj[ary[j]].count++;
|
||||
}
|
||||
else {
|
||||
if (uObj[ary[j]] instanceof Object) {
|
||||
uObj[ary[j]].count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i in uObj) {
|
||||
if (uObj[i].count != 2) {
|
||||
newAry.unshift(uObj[i].key);
|
||||
}
|
||||
}
|
||||
return newAry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换数组元素
|
||||
* @param array 目标数组
|
||||
* @param index1 交换后的索引
|
||||
* @param index2 交换前的索引
|
||||
*/
|
||||
public static swap(array: any[], index1: number, index2: number): void {
|
||||
let temp: any = array[index1];
|
||||
array[index1] = array[index2];
|
||||
array[index2] = temp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清除列表
|
||||
* @param ary 列表
|
||||
*/
|
||||
public static clearList(ary: any[]): void {
|
||||
if (!ary) return;
|
||||
let length: number = ary.length;
|
||||
for (let i: number = length - 1; i >= 0; i -= 1) {
|
||||
ary.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆一个数组
|
||||
* @param ary 需要克隆的数组
|
||||
* @return 克隆的数组
|
||||
*/
|
||||
public static cloneList(ary: any[]): any[] {
|
||||
if (!ary) return null;
|
||||
return ary.slice(0, ary.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断2个数组是否相同
|
||||
* @param ary1 数组1
|
||||
* @param ary2 数组2
|
||||
* @return 是否相同
|
||||
*/
|
||||
public static equals(ary1: number[], ary2: number[]): Boolean {
|
||||
if (ary1 == ary2) return true;
|
||||
let length: number = ary1.length;
|
||||
if (length != ary2.length) return false;
|
||||
while (length--) {
|
||||
if (ary1[length] != ary2[length])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据索引插入元素,索引和索引后的元素都向后移动一位
|
||||
* @param index 插入索引
|
||||
* @param value 插入的元素
|
||||
* @return 插入的元素 未插入则返回空
|
||||
*/
|
||||
public static insert(ary: any[], index: number, value: any): any {
|
||||
if (!ary) return null;
|
||||
let length: number = ary.length;
|
||||
if (index > length) index = length;
|
||||
if (index < 0) index = 0;
|
||||
if (index == length) ary.push(value); //插入最后
|
||||
else if (index == 0) ary.unshift(value); //插入头
|
||||
else {
|
||||
for (let i: number = length - 1; i >= index; i -= 1) {
|
||||
ary[i + 1] = ary[i];
|
||||
}
|
||||
ary[index] = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
class Base64Utils {
|
||||
private static _keyNum = "0123456789+/";
|
||||
private static _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
private static _keyAll = Base64Utils._keyNum + Base64Utils._keyStr;
|
||||
/**
|
||||
* 加密
|
||||
* @param input
|
||||
*/
|
||||
public static encode = function (input) {
|
||||
let output = "";
|
||||
let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
input = this._utf8_encode(input);
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
output = output +
|
||||
this._keyAll.charAt(enc1) + this._keyAll.charAt(enc2) +
|
||||
this._keyAll.charAt(enc3) + this._keyAll.charAt(enc4);
|
||||
}
|
||||
return this._keyStr.charAt(Math.floor((Math.random() * this._keyStr.length))) + output;
|
||||
}
|
||||
|
||||
private static _utf8_encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
let utftext = "";
|
||||
for (let n = 0; n < string.length; n++) {
|
||||
let c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
|
||||
}
|
||||
return utftext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码
|
||||
* @param input
|
||||
* @param isNotStr
|
||||
*/
|
||||
public static decode(input, isNotStr: boolean = true) {
|
||||
let output = "";
|
||||
let chr1, chr2, chr3;
|
||||
let enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
input = this.getConfKey(input);
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
while (i < input.length) {
|
||||
enc1 = this._keyAll.indexOf(input.charAt(i++));
|
||||
enc2 = this._keyAll.indexOf(input.charAt(i++));
|
||||
enc3 = this._keyAll.indexOf(input.charAt(i++));
|
||||
enc4 = this._keyAll.indexOf(input.charAt(i++));
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
output = output + String.fromCharCode(chr1);
|
||||
if (enc3 != 64) {
|
||||
if (chr2 == 0) {
|
||||
if (isNotStr) output = output + String.fromCharCode(chr2);
|
||||
} else {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
}
|
||||
|
||||
if (enc4 != 64) {
|
||||
if (chr3 == 0) {
|
||||
if (isNotStr) output = output + String.fromCharCode(chr3);
|
||||
} else {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
}
|
||||
}
|
||||
output = this._utf8_decode(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static _utf8_decode(utftext) {
|
||||
let string = "";
|
||||
let i = 0;
|
||||
let c = 0;
|
||||
let c1 = 0;
|
||||
let c2 = 0;
|
||||
let c3 = 0;
|
||||
while (i < utftext.length) {
|
||||
c = utftext.charCodeAt(i);
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c);
|
||||
i++;
|
||||
} else if ((c > 191) && (c < 224)) {
|
||||
c2 = utftext.charCodeAt(i + 1);
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
} else {
|
||||
c2 = utftext.charCodeAt(i + 1);
|
||||
c3 = utftext.charCodeAt(i + 2);
|
||||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
private static getConfKey(key): string {
|
||||
return key.slice(1, key.length);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
declare class fui {}
|
||||
|
||||
class ContentManager {
|
||||
protected loadedAssets: Map<string, any> = new Map<string, any>();
|
||||
|
||||
@@ -29,7 +27,7 @@ class ContentManager {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/** 各种辅助方法来辅助绘图 */
|
||||
class DrawUtils {
|
||||
public static drawLine(shape: egret.Shape, start: Vector2, end: Vector2, color: number, thickness: number = 1){
|
||||
this.drawLineAngle(shape, start, MathHelper.angleBetweenVectors(start, end), Vector2.distance(start, end), color, thickness);
|
||||
}
|
||||
|
||||
public static drawLineAngle(shape: egret.Shape, start: Vector2, radians: number, length: number, color: number, thickness = 1){
|
||||
shape.graphics.beginFill(color);
|
||||
shape.graphics.drawRect(start.x, start.y, 1, 1);
|
||||
shape.graphics.endFill();
|
||||
|
||||
shape.scaleX = length;
|
||||
shape.scaleY = thickness;
|
||||
shape.$anchorOffsetX = 0;
|
||||
shape.$anchorOffsetY = 0;
|
||||
shape.rotation = radians;
|
||||
}
|
||||
|
||||
public static drawHollowRect(shape: egret.Shape, rect: Rectangle, color: number, thickness = 1){
|
||||
this.drawHollowRectR(shape, rect.x, rect.y, rect.width, rect.height, color, thickness);
|
||||
}
|
||||
|
||||
public static drawHollowRectR(shape: egret.Shape, x: number, y: number, width: number, height: number, color: number, thickness = 1){
|
||||
let tl = new Vector2(x, y).round();
|
||||
let tr = new Vector2(x + width, y).round();
|
||||
let br = new Vector2(x + width, y + height).round();
|
||||
let bl = new Vector2(x, y + height).round();
|
||||
|
||||
this.drawLine(shape, tl, tr, color, thickness);
|
||||
this.drawLine(shape, tr, br, color, thickness);
|
||||
this.drawLine(shape, br, bl, color, thickness);
|
||||
this.drawLine(shape, bl, tl, color, thickness);
|
||||
}
|
||||
|
||||
public static drawPixel(shape: egret.Shape, position: Vector2, color: number, size: number = 1){
|
||||
let destRect = new Rectangle(position.x, position.y, size, size);
|
||||
if (size != 1){
|
||||
destRect.x -= size * 0.5;
|
||||
destRect.y -= size * 0.5;
|
||||
}
|
||||
|
||||
shape.graphics.beginFill(color);
|
||||
shape.graphics.drawRect(destRect.x, destRect.y, destRect.width, destRect.height);
|
||||
shape.graphics.endFill();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
/**
|
||||
* 用于事件管理
|
||||
*/
|
||||
class Emitter<T> {
|
||||
private _messageTable: Map<T, Function[]>;
|
||||
private _messageTable: Map<T, FuncPack[]>;
|
||||
|
||||
constructor(){
|
||||
this._messageTable = new Map<T, Function[]>();
|
||||
this._messageTable = new Map<T, FuncPack[]>();
|
||||
}
|
||||
|
||||
public addObserver(eventType: T, handler: Function){
|
||||
let list: Function[] = this._messageTable.get(eventType);
|
||||
/**
|
||||
* 开始监听项
|
||||
* @param eventType 监听类型
|
||||
* @param handler 监听函数
|
||||
* @param context 监听上下文
|
||||
*/
|
||||
public addObserver(eventType: T, handler: Function, context: any){
|
||||
let list: FuncPack[] = this._messageTable.get(eventType);
|
||||
if (!list){
|
||||
list = [];
|
||||
this._messageTable.set(eventType, list);
|
||||
@@ -14,18 +23,46 @@ class Emitter<T> {
|
||||
|
||||
if (list.contains(handler))
|
||||
console.warn("您试图添加相同的观察者两次");
|
||||
list.push(handler);
|
||||
list.push(new FuncPack(handler, context));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听项
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public removeObserver(eventType: T, handler: Function){
|
||||
this._messageTable.get(eventType).remove(handler);
|
||||
let messageData = this._messageTable.get(eventType);
|
||||
let index = messageData.findIndex(data => data.func == handler);
|
||||
if (index != -1)
|
||||
messageData.removeAt(index);
|
||||
}
|
||||
|
||||
public emit(eventType: T, data: any){
|
||||
let list: Function[] = this._messageTable.get(eventType);
|
||||
/**
|
||||
* 触发该事件
|
||||
* @param eventType 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
public emit(eventType: T, data?: any){
|
||||
let list: FuncPack[] = this._messageTable.get(eventType);
|
||||
if (list){
|
||||
for (let i = list.length - 1; i >= 0; i --)
|
||||
list[i](data);
|
||||
list[i].func.call(list[i].context, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于包装事件的一个小类
|
||||
*/
|
||||
class FuncPack {
|
||||
/** 函数 */
|
||||
public func: Function;
|
||||
/** 上下文 */
|
||||
public context: any;
|
||||
|
||||
constructor(func: Function, context: any){
|
||||
this.func = func;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
class KeyboardUtils {
|
||||
/**
|
||||
* 键盘事件类型
|
||||
*/
|
||||
public static TYPE_KEY_DOWN: number = 0;
|
||||
public static TYPE_KEY_UP: number = 1;
|
||||
|
||||
//存放按下注册数据的字典
|
||||
private static keyDownDict: Object;
|
||||
//存放按起注册数据的字典
|
||||
private static keyUpDict: Object;
|
||||
|
||||
/**
|
||||
* 键值字符串枚举
|
||||
*/
|
||||
public static A: string = "A";
|
||||
public static B: string = "B";
|
||||
public static C: string = "C";
|
||||
public static D: string = "D";
|
||||
public static E: string = "E";
|
||||
public static F: string = "F";
|
||||
public static G: string = "G";
|
||||
public static H: string = "H";
|
||||
public static I: string = "I";
|
||||
public static J: string = "J";
|
||||
public static K: string = "K";
|
||||
public static L: string = "L";
|
||||
public static M: string = "M";
|
||||
public static N: string = "N";
|
||||
public static O: string = "O";
|
||||
public static P: string = "P";
|
||||
public static Q: string = "Q";
|
||||
public static R: string = "R";
|
||||
public static S: string = "S";
|
||||
public static T: string = "T";
|
||||
public static U: string = "U";
|
||||
public static V: string = "V";
|
||||
public static W: string = "W";
|
||||
public static X: string = "X";
|
||||
public static Y: string = "Y";
|
||||
public static Z: string = "Z";
|
||||
|
||||
public static ESC: string = "Esc";
|
||||
public static F1: string = "F1";
|
||||
public static F2: string = "F2";
|
||||
public static F3: string = "F3";
|
||||
public static F4: string = "F4";
|
||||
public static F5: string = "F5";
|
||||
public static F6: string = "F6";
|
||||
public static F7: string = "F7";
|
||||
public static F8: string = "F8";
|
||||
public static F9: string = "F9";
|
||||
public static F10: string = "F10";
|
||||
public static F11: string = "F11";
|
||||
public static F12: string = "F12";
|
||||
|
||||
public static NUM_1: string = "1";
|
||||
public static NUM_2: string = "2";
|
||||
public static NUM_3: string = "3";
|
||||
public static NUM_4: string = "4";
|
||||
public static NUM_5: string = "5";
|
||||
public static NUM_6: string = "6";
|
||||
public static NUM_7: string = "7";
|
||||
public static NUM_8: string = "8";
|
||||
public static NUM_9: string = "9";
|
||||
public static NUM_0: string = "0";
|
||||
|
||||
public static TAB: string = "Tab";
|
||||
public static CTRL: string = "Ctrl";
|
||||
public static ALT: string = "Alt";
|
||||
public static SHIFT: string = "Shift";
|
||||
public static CAPS_LOCK: string = "Caps Lock";
|
||||
public static ENTER: string = "Enter";
|
||||
public static SPACE: string = "Space";
|
||||
public static BACK_SPACE: string = "Back Space";
|
||||
|
||||
public static INSERT: string = "Insert";
|
||||
public static DELETE: string = "Page Down";
|
||||
public static HOME: string = "Home";
|
||||
public static END: string = "Page Down";
|
||||
public static PAGE_UP: string = "Page Up";
|
||||
public static PAGE_DOWN: string = "Page Down";
|
||||
|
||||
public static LEFT: string = "Left";
|
||||
public static RIGHT: string = "Right";
|
||||
public static UP: string = "Up";
|
||||
public static DOWN: string = "Down";
|
||||
|
||||
public static PAUSE_BREAK: string = "Pause Break";
|
||||
public static NUM_LOCK: string = "Num Lock";
|
||||
public static SCROLL_LOCK: string = "Scroll Lock";
|
||||
|
||||
public static WINDOWS: string = "Windows";
|
||||
|
||||
|
||||
public static init(): void {
|
||||
this.keyDownDict = {};
|
||||
this.keyUpDict = {};
|
||||
document.addEventListener("keydown", this.onKeyDonwHander);
|
||||
document.addEventListener("keyup", this.onKeyUpHander);
|
||||
}
|
||||
|
||||
private static onKeyDonwHander(event: KeyboardEvent): void {
|
||||
if (!this.keyDownDict) return;
|
||||
var key: string = this.keyCodeToString(event.keyCode);
|
||||
var o: Object = this.keyDownDict[key];
|
||||
if (o) {
|
||||
var fun: Function = o["fun"];
|
||||
var thisObj: any = o["thisObj"];
|
||||
var args: any = o["args"];
|
||||
fun.apply(thisObj, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static onKeyUpHander(event: KeyboardEvent): void {
|
||||
if (!this.keyUpDict) return;
|
||||
var key: string = this.keyCodeToString(event.keyCode);
|
||||
var o: Object = this.keyUpDict[key];
|
||||
if (o) {
|
||||
var fun: Function = o["fun"];
|
||||
var thisObj: any = o["thisObj"];
|
||||
var args: any = o["args"];
|
||||
fun.apply(thisObj, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注册按键
|
||||
* @param key 键值
|
||||
* @param fun 回调方法
|
||||
* @param type 按键类型 TYPE_KEY_DOWN、TYPE_KEY_UP
|
||||
*/
|
||||
public static registerKey(key: string, fun: Function, thisObj: any, type: number = 0, ...args): void {
|
||||
var keyDict: Object = type ? this.keyUpDict : this.keyDownDict;
|
||||
keyDict[key] = { "fun": fun, args: args, "thisObj": thisObj };
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销按键
|
||||
* @param key 键值
|
||||
* @param type 注销的类型
|
||||
*/
|
||||
public static unregisterKey(key: string, type: number = 0): void {
|
||||
var keyDict: Object = type ? this.keyUpDict : this.keyDownDict;
|
||||
delete keyDict[key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据keyCode或charCode获取相应的字符串代号
|
||||
* @param keyCode
|
||||
* @return 键盘所指字符串代号
|
||||
*/
|
||||
private static keyCodeToString(keyCode: number): string {
|
||||
switch (keyCode) {
|
||||
case 8:
|
||||
return this.BACK_SPACE;
|
||||
case 9:
|
||||
return this.TAB;
|
||||
case 13:
|
||||
return this.ENTER;
|
||||
case 16:
|
||||
return this.SHIFT;
|
||||
case 17:
|
||||
return this.CTRL;
|
||||
case 19:
|
||||
return this.PAUSE_BREAK;
|
||||
case 20:
|
||||
return this.CAPS_LOCK;
|
||||
case 27:
|
||||
return this.ESC;
|
||||
case 32:
|
||||
return this.SPACE;
|
||||
case 33:
|
||||
return this.PAGE_UP;
|
||||
case 34:
|
||||
return this.PAGE_DOWN;
|
||||
case 35:
|
||||
return this.END;
|
||||
case 36:
|
||||
return this.HOME;
|
||||
case 37:
|
||||
return this.LEFT;
|
||||
case 38:
|
||||
return this.UP;
|
||||
case 39:
|
||||
return this.RIGHT;
|
||||
case 40:
|
||||
return this.DOWN;
|
||||
case 45:
|
||||
return this.INSERT;
|
||||
case 46:
|
||||
return this.DELETE;
|
||||
case 91:
|
||||
return this.WINDOWS;
|
||||
case 112:
|
||||
return this.F1;
|
||||
case 113:
|
||||
return this.F2;
|
||||
case 114:
|
||||
return this.F3;
|
||||
case 115:
|
||||
return this.F4;
|
||||
case 116:
|
||||
return this.F5;
|
||||
case 117:
|
||||
return this.F6;
|
||||
case 118:
|
||||
return this.F7;
|
||||
case 119:
|
||||
return this.F8;
|
||||
case 120:
|
||||
return this.F9;
|
||||
case 122:
|
||||
return this.F11;
|
||||
case 123:
|
||||
return this.F12;
|
||||
case 144:
|
||||
return this.NUM_LOCK;
|
||||
case 145:
|
||||
return this.SCROLL_LOCK;
|
||||
default:
|
||||
return String.fromCharCode(keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 销毁方法
|
||||
*/
|
||||
public static destroy(): void {
|
||||
this.keyDownDict = null;
|
||||
this.keyUpDict = null;
|
||||
document.removeEventListener("keydown", this.onKeyDonwHander);
|
||||
document.removeEventListener("keyup", this.onKeyUpHander);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
const THREAD_ID = `${Math.floor(Math.random() * 1000)}-${Date.now()}`;
|
||||
const setItem = egret.localStorage.setItem.bind(localStorage);
|
||||
const getItem = egret.localStorage.getItem.bind(localStorage);
|
||||
const removeItem = egret.localStorage.removeItem.bind(localStorage);
|
||||
|
||||
const nextTick = fn => {
|
||||
setTimeout(fn, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* 利用共享区域实现快速锁
|
||||
*/
|
||||
class LockUtils {
|
||||
private _keyX: string;
|
||||
private _keyY: string;
|
||||
constructor(key){
|
||||
this._keyX = `mutex_key_${key}_X`;
|
||||
this._keyY = `mutex_key_${key}_Y`;
|
||||
}
|
||||
|
||||
public lock(){
|
||||
return new Promise((resolve, reject) => {
|
||||
const fn = () => {
|
||||
setItem(this._keyX, THREAD_ID);
|
||||
if (!getItem(this._keyY) === null){
|
||||
// restart
|
||||
nextTick(fn);
|
||||
}
|
||||
setItem(this._keyY, THREAD_ID);
|
||||
if (getItem(this._keyX) !== THREAD_ID){
|
||||
// delay
|
||||
setTimeout(()=>{
|
||||
if (getItem(this._keyY) !== THREAD_ID){
|
||||
// restart
|
||||
nextTick(fn);
|
||||
return;
|
||||
}
|
||||
// critical section
|
||||
resolve();
|
||||
removeItem(this._keyY);
|
||||
}, 10);
|
||||
} else {
|
||||
resolve();
|
||||
removeItem(this._keyY);
|
||||
}
|
||||
};
|
||||
|
||||
fn();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
class RandomUtils {
|
||||
/**
|
||||
* 在 start 与 stop之间取一个随机整数,可以用step指定间隔, 但不包括较大的端点(start与stop较大的一个)
|
||||
* 如
|
||||
* this.randrange(1, 10, 3)
|
||||
* 则返回的可能是 1 或 4 或 7 , 注意 这里面不会返回10,因为是10是大端点
|
||||
*
|
||||
* @param start
|
||||
* @param stop
|
||||
* @param step
|
||||
* @return 假设 start < stop, [start, stop) 区间内的随机整数
|
||||
*
|
||||
*/
|
||||
public static randrange(start: number, stop: number, step: number = 1): number {
|
||||
if (step == 0)
|
||||
throw new Error('step 不能为 0');
|
||||
|
||||
let width: number = stop - start;
|
||||
if (width == 0)
|
||||
throw new Error('没有可用的范围(' + start + ',' + stop + ')');
|
||||
if (width < 0)
|
||||
width = start - stop;
|
||||
|
||||
let n: number = Math.floor((width + step - 1) / step);
|
||||
return Math.floor(this.random() * n) * step + Math.min(start, stop);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回a 到 b直间的随机整数,包括 a 和 b
|
||||
* @param a
|
||||
* @param b
|
||||
* @return [a, b] 直接的随机整数
|
||||
*
|
||||
*/
|
||||
public static randint(a: number, b: number): number {
|
||||
a = Math.floor(a);
|
||||
b = Math.floor(b);
|
||||
if (a > b)
|
||||
a++;
|
||||
else
|
||||
b++;
|
||||
return this.randrange(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 a - b之间的随机数,不包括 Math.max(a, b)
|
||||
* @param a
|
||||
* @param b
|
||||
* @return 假设 a < b, [a, b)
|
||||
*/
|
||||
public static randnum(a: number, b: number): number {
|
||||
return this.random() * (b - a) + a;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打乱数组
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
public static shuffle(array: any[]): any[] {
|
||||
array.sort(this._randomCompare);
|
||||
return array;
|
||||
}
|
||||
|
||||
private static _randomCompare(a: Object, b: Object): number {
|
||||
return (this.random() > .5) ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从序列中随机取一个元素
|
||||
* @param sequence 可以是 数组、 vector,等只要是有length属性,并且可以用数字索引获取元素的对象,
|
||||
* 另外,字符串也是允许的。
|
||||
* @return 序列中的某一个元素
|
||||
*
|
||||
*/
|
||||
public static choice(sequence: any): any {
|
||||
if (!sequence.hasOwnProperty("length"))
|
||||
throw new Error('无法对此对象执行此操作');
|
||||
let index: number = Math.floor(this.random() * sequence.length);
|
||||
if (sequence instanceof String)
|
||||
return String(sequence).charAt(index);
|
||||
else
|
||||
return sequence[index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对列表中的元素进行随机采æ ?
|
||||
* <pre>
|
||||
* this.sample([1, 2, 3, 4, 5], 3) // Choose 3 elements
|
||||
* [4, 1, 5]
|
||||
* </pre>
|
||||
* @param sequence
|
||||
* @param num
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static sample(sequence: any[], num: number): any[] {
|
||||
let len: number = sequence.length;
|
||||
if (num <= 0 || len < num)
|
||||
throw new Error("采样数量不够");
|
||||
|
||||
let selected: any[] = [];
|
||||
let indices: any[] = [];
|
||||
for (let i: number = 0; i < num; i++) {
|
||||
let index: number = Math.floor(this.random() * len);
|
||||
while (indices.indexOf(index) >= 0)
|
||||
index = Math.floor(this.random() * len);
|
||||
|
||||
selected.push(sequence[index]);
|
||||
indices.push(index);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 0.0 - 1.0 之间的随机数,等同于 Math.random()
|
||||
* @return Math.random()
|
||||
*
|
||||
*/
|
||||
public static random(): number {
|
||||
return Math.random();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算概率
|
||||
* @param chance 概率
|
||||
* @return
|
||||
*/
|
||||
public static boolean(chance: number = .5): boolean {
|
||||
return (this.random() < chance) ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,7 @@
|
||||
class RectangleExt {
|
||||
public static union(first: Rectangle, point: Point){
|
||||
public static union(first: Rectangle, point: Vector2){
|
||||
let rect = new Rectangle(point.x, point.y, 0, 0);
|
||||
return this.unionR(first, rect);
|
||||
}
|
||||
|
||||
public static unionR(value1: Rectangle, value2: Rectangle){
|
||||
let result = new Rectangle();
|
||||
result.x = Math.min(value1.x, value2.x);
|
||||
result.y = Math.min(value1.y, value2.y);
|
||||
result.width = Math.max(value1.right, value2.right) - result.x;
|
||||
result.height = Math.max(value1.bottom, value2.bottom) - result.y;
|
||||
|
||||
return result;
|
||||
let rectResult = first.union(rect);
|
||||
return new Rectangle(rectResult.x, rectResult.y, rectResult.width, rectResult.height);
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,15 @@ class Vector2Ext {
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过指定的矩阵对Vector2的数组中的向量应用变换,并将结果放置在另一个数组中。
|
||||
* @param sourceArray
|
||||
* @param sourceIndex
|
||||
* @param matrix
|
||||
* @param destinationArray
|
||||
* @param destinationIndex
|
||||
* @param length
|
||||
*/
|
||||
public static transformA(sourceArray: Vector2[], sourceIndex: number, matrix: Matrix2D,
|
||||
destinationArray: Vector2[], destinationIndex: number, length: number) {
|
||||
for (let i = 0; i < length; i ++){
|
||||
@@ -60,6 +69,12 @@ class Vector2Ext {
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过指定的矩阵对Vector2的数组中的所有向量应用变换,并将结果放到另一个数组中。
|
||||
* @param sourceArray
|
||||
* @param matrix
|
||||
* @param destinationArray
|
||||
*/
|
||||
public static transform(sourceArray: Vector2[], matrix: Matrix2D, destinationArray: Vector2[]) {
|
||||
this.transformA(sourceArray, 0, matrix, destinationArray, 0, sourceArray.length);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"module": "system",
|
||||
"target": "es5",
|
||||
"declaration": true,
|
||||
|
||||
Reference in New Issue
Block a user