Compare commits

..

36 Commits

Author SHA1 Message Date
yhh dccd4e21b6 base64辅助类 2020-07-17 15:40:28 +08:00
yhh c96e8b3a04 新增Time/String/Array/Keyboard/Random/Object/Texture辅助类 2020-07-17 14:34:42 +08:00
YHH e686ba64d7 Update README.md 2020-07-17 11:55:29 +08:00
yhh 14cb9cd257 贝塞尔曲线 2020-07-17 11:07:57 +08:00
yhh 13e7737cb9 防止删除一个空的函数发生未定义报错 2020-07-15 10:56:06 +08:00
yhh dd65c60921 Merge branch 'master' of https://github.com/esengine/egret-framework 2020-07-15 10:53:38 +08:00
yhh 7dffb4d94a #12 fix Emitter类移除监听时是否有错 2020-07-15 10:53:30 +08:00
YHH 476204a296 Update README.md 2020-07-13 17:13:53 +08:00
yhh 983c8fbc99 iupdatable无法判断接口 去除改为component支持 2020-07-13 12:48:42 +08:00
YHH c30e591f6e Update README.md 2020-07-13 10:56:25 +08:00
YHH d9a1b5578c Update README.md 2020-07-13 10:56:06 +08:00
YHH f0e04b6981 emitter 支持 context 2020-07-12 23:41:10 +08:00
YHH 14598f08c7 sceneChanged事件 2020-07-12 23:30:48 +08:00
YHH 032b293085 新增analysis用于分析游戏缺陷 2020-07-12 14:51:20 +08:00
YHH d9bb76c105 Merge branch 'master' of https://github.com/esengine/egret-framework
# Conflicts:
#	demo/libs/framework/framework.d.ts
#	demo/libs/framework/framework.min.js
#	demo/src/game/MainScene.ts
#	source/bin/framework.d.ts
#	source/bin/framework.min.js
2020-07-12 09:46:07 +08:00
YHH 20392c8ab6 Set theme jekyll-theme-slate 2020-07-10 12:19:47 +08:00
yhh 583e03d025 Merge branch 'develop' 2020-07-10 11:25:11 +08:00
yhh f6c2d81a83 新增IUpdatable接口 用于减少update所带来的的性能损耗 2020-07-10 11:24:42 +08:00
YHH e703ff4e6c Merge pull request #11 from esengine/develop
Develop
2020-07-09 16:37:13 +08:00
yhh 877fc4c9bf astar 注释 2020-07-09 16:36:42 +08:00
yhh a80bb4b6f3 绘制帮助类 2020-07-09 16:16:04 +08:00
YHH 9b9d210109 Merge pull request #10 from esengine/develop
新增 circleCollider与 polygonCollider
2020-07-09 15:13:58 +08:00
yhh 817b703d4f 新增 circleCollider与 polygonCollider 2020-07-09 15:11:30 +08:00
YHH 88a25453e6 Merge pull request #9 from esengine/develop
对boxcollider碰撞支持
2020-07-09 14:16:52 +08:00
yhh 6e3eb1189a 修复boxcollider碰撞问题 2020-07-09 14:16:10 +08:00
yhh aea50926a9 优化spriteRenderer渲染方法 2020-07-08 18:12:17 +08:00
yhh 299c1b8e7d 修复切换场景未移除问题 2020-07-08 15:15:15 +08:00
YHH 3f6ab79894 滚动精灵 2020-07-07 21:40:57 +08:00
yhh b14fee1685 box重载overlaps 2020-07-07 18:54:19 +08:00
yhh ace8fb685d box 重载 collidesWith 2020-07-07 12:18:51 +08:00
YHH 1870ee5e45 Merge pull request #8 from esengine/develop
Develop
2020-07-03 17:53:08 +08:00
yhh 8be65fa685 update reademe 2020-07-03 17:52:26 +08:00
yhh cf4e76b12d 新增tiledspriterenderer 2020-07-03 17:51:18 +08:00
YHH c156463f10 Merge pull request #7 from esengine/develop
新增动画 移除不相关的库
2020-07-03 16:48:34 +08:00
yhh e7796550c6 移除资源 2020-07-03 16:47:24 +08:00
yhh c3c9181400 新增动画 移除不相关的库 2020-07-03 16:45:52 +08:00
87 changed files with 13542 additions and 25538 deletions
+39 -30
View File
@@ -3,29 +3,12 @@
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/esengine/egret-framework.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/esengine/egret-framework/context:javascript)
```
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/esengine/egret-framework.svg?logo=lgtm&logoWidth=18)](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)
+1
View File
@@ -0,0 +1 @@
theme: jekyll-theme-slate
+10
View File
@@ -0,0 +1,10 @@
{
"version": "0.1.0",
"command": "egret",
"isShellCommand": true,
"showOutput": "silent",
"args": [
"build"
],
"problemMatcher": "$tsc"
}
+1 -1
View File
@@ -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"
-2452
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+553 -137
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+12 -2
View File
@@ -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
View File
@@ -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);
}
}
+5 -5
View File
@@ -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));
+1 -1
View File
@@ -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,
+3
View File
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "./node_modules/typescript/lib"
}
+553 -137
View File
File diff suppressed because it is too large Load Diff
+2413 -433
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
-2452
View File
File diff suppressed because it is too large Load Diff
+3823
View File
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 HashSetweightedNodes
*/
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);
}
/**
*
* undefinedundefined
* 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的优先级高于lowertruefalse
* 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;
}
}
+22
View File
@@ -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);
}
}
}
}
+46
View File
@@ -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;
}
}
+35 -5
View File
@@ -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);
+16 -3
View File
@@ -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);
}
}
+34 -3
View File
@@ -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;
}
}
+142
View File
@@ -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,
}
+16 -18
View File
@@ -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;
}
}
+4
View File
@@ -0,0 +1,4 @@
enum CoreEvents{
/** 当场景发生变化时触发 */
SceneChanged,
}
+36 -5
View File
@@ -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
}
+3
View File
@@ -144,6 +144,9 @@ class Scene extends egret.DisplayObjectContainer {
this.entityProcessors.end();
this.unload();
if (this.parent)
this.parent.removeChild(this);
}
protected async onStart() {
+35 -5
View File
@@ -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();
}
}
+90 -40
View File
@@ -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();
}
}
}
+20
View File
@@ -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;
}
}
+238
View File
@@ -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[] = [
'&', '&amp;',
'<', '&lt;',
'>', '&gt;',
'"', '&quot;',
"'", '&apos;',
'®', '&reg;',
'©', '&copy;',
'™', '&trade;',
];
/**
* 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 >= widthstr
* @return
*
*/
public static zfill(str: string, width: number = 2): string {
if (!str) {
return str;
}
width = Math.floor(width);
let slen: number = str.length;
if (slen >= width) {
return str;
}
let negative: boolean = false;
if (str.substr(0, 1) == '-') {
negative = true;
str = str.substr(1);
}
let len: number = width - slen;
for (let i: number = 0; i < len; i++) {
str = '0' + str;
}
if (negative) {
str = '-' + str;
}
return str;
}
/**
*
* @param str
* @return
*/
public static reverse(str: string): string {
if (str.length > 1)
return this.reverse(str.substring(1)) + str.substring(0, 1);
else
return str;
}
/**
*
* @param str
* @param start
* @param 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
}
}
+154
View File
@@ -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);
}
}
}
+22 -1
View File
@@ -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);
}
}
+209
View File
@@ -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();
}
}
+1 -1
View File
@@ -48,7 +48,7 @@ declare interface Array<T> {
*
* @param element
*/
remove(element): boolean;
remove(element: T): boolean;
/**
*
* @param index
+19 -25
View File
@@ -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;
-11
View File
@@ -1,11 +0,0 @@
///<reference path="./BaseView.ts" />
/** 用于承载fui界面 */
class BaseFuiView extends BaseView {
/** 界面名称 */
protected _name: string;
constructor(name: string){
super();
this.name = name;
}
}
-25
View File
@@ -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;
}
}
-41
View File
@@ -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);
}
}
}
-64
View File
@@ -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;
}
}
+121
View File
@@ -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);
}
}
+2 -2
View File
@@ -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;
}
+22
View File
@@ -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);
}
}
+15 -2
View File
@@ -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();
-9
View File
@@ -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
View File
@@ -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);
+4 -1
View File
@@ -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)
+26 -2
View File
@@ -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);
}
}
+40 -1
View File
@@ -1,4 +1,7 @@
///<reference path="./Polygon.ts" />
/**
* SAT碰撞检查时28
*/
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);
}
+3 -2
View File
@@ -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);
+3 -3
View File
@@ -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);
+43 -10
View File
@@ -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;
}
}
+3 -3
View File
@@ -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)
}
}
+97 -7
View File
@@ -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
* intint xy坐标散列到单个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();
}
+69
View File
@@ -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
}
+233
View File
@@ -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;
}
+342
View File
@@ -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;
}
+241
View File
@@ -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;
}
}
+124
View File
@@ -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 -3
View File
@@ -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() {
+46
View File
@@ -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();
}
}
+46 -9
View File
@@ -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;
}
}
+239
View File
@@ -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_DOWNTYPE_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);
}
}
+51
View File
@@ -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();
});
}
}
+134
View File
@@ -0,0 +1,134 @@
class RandomUtils {
/**
* start stop之间取一个随机整数step指定间隔 start与stop较大的一个
*
* this.randrange(1, 10, 3)
* 1 4 7 , 1010
*
* @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 vectorlength属性
*
* @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;
}
}
+3 -12
View File
@@ -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);
}
}
+15
View File
@@ -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
View File
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"module": "system",
"target": "es5",
"declaration": true,