Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14b70b307c | |||
| a39b98b5d9 | |||
| 3492bbdf5e | |||
| 7d0bcbcb32 | |||
| c02a3aa071 | |||
| a105bb11ca | |||
| 3fbbba7ac2 | |||
| 7cd38ea54e | |||
| 42852c5dba | |||
| 6a3622a5ef | |||
| 03b568e28d | |||
| e7fb9e0d6b | |||
| 463c64c628 | |||
| 4025bc8554 | |||
| 1bf822725a | |||
| 359d7ae223 | |||
| c611e31f7e | |||
| 834ad565e1 | |||
| ab3f38c6a8 | |||
| d0199e357b | |||
| 8e3bcc1257 | |||
| a4fe9f5798 | |||
| c692f0157e | |||
| bc6626865e | |||
| 40fe7a57db | |||
| 514572f291 | |||
| 5994f0bee3 | |||
| b3e1f2c446 | |||
| ed13186779 | |||
| d730851d97 | |||
| 60921f703b | |||
| 506f8ddc0f | |||
| 149a3e5833 | |||
| 2b13e5ee7d | |||
| 6be43fc9ac | |||
| d4c244daf5 | |||
| 79c5d6990c | |||
| 347626a8ea | |||
| e61dd0c16b | |||
| 1b52bc5fd1 | |||
| 814234ca61 | |||
| 15c0844e29 | |||
| 5b8f414a45 | |||
| 6b8569b0b5 | |||
| 2a38858838 | |||
| e76d5815f2 | |||
| dccd4e21b6 | |||
| c96e8b3a04 | |||
| e686ba64d7 | |||
| 14cb9cd257 | |||
| 13e7737cb9 | |||
| dd65c60921 | |||
| 7dffb4d94a | |||
| 476204a296 | |||
| 983c8fbc99 | |||
| c30e591f6e | |||
| d9a1b5578c | |||
| f0e04b6981 | |||
| 14598f08c7 | |||
| 032b293085 | |||
| d9bb76c105 | |||
| 20392c8ab6 | |||
| 583e03d025 | |||
| f6c2d81a83 | |||
| e703ff4e6c | |||
| 877fc4c9bf | |||
| a80bb4b6f3 | |||
| 9b9d210109 | |||
| 817b703d4f | |||
| 88a25453e6 | |||
| 6e3eb1189a | |||
| 1e3b2763e8 | |||
| a3dacd04f0 | |||
| aea50926a9 | |||
| 299c1b8e7d | |||
| 3f6ab79894 | |||
| b14fee1685 | |||
| ace8fb685d | |||
| 1870ee5e45 | |||
| 8be65fa685 | |||
| cf4e76b12d | |||
| c156463f10 | |||
| e7796550c6 | |||
| c3c9181400 | |||
| 4f4d508685 | |||
| f36a1cdb27 | |||
| 28941f2395 | |||
| 7f02272304 | |||
| 02d86e7b0a | |||
| 4c8b6add98 | |||
| 60d646c392 | |||
| 5e871bc501 | |||
| 549db2fcfe | |||
| d9840d60ef | |||
| 124cf3f66c | |||
| cd78b8de59 | |||
| a3209de000 | |||
| 2908be1705 | |||
| 009a01ebf4 | |||
| 3fdbf3bfef | |||
| c8ecf3cc69 | |||
| 088891c5ca | |||
| 099aa749e1 | |||
| db7aac2b90 | |||
| 366bcf8efc | |||
| a4f1ae351f | |||
| a63d8598d8 | |||
| d7385654ef | |||
| 7399b9f5ca | |||
| 795bfab1aa | |||
| fb5906afe0 | |||
| e38cd0f3ca | |||
| 481112cfc2 | |||
| 3d9730d956 | |||
| 60646edd6b | |||
| 9bd5a99c81 | |||
| d22c5775c2 | |||
| 981e149ca5 | |||
| b83b1a5b21 | |||
| 09e6ace142 | |||
| e83bb087ea | |||
| feaac83ee7 | |||
| 915056203d | |||
| 231276e39b |
@@ -1,2 +1,6 @@
|
||||
/source/node_modules
|
||||
/demo/bin-debug
|
||||
/demo/bin-release
|
||||
/.idea
|
||||
/.vscode
|
||||
/demo_wxgame
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,23 +1,14 @@
|
||||
# egret-framework
|
||||
用于egret的一套框架 包含众多游戏中可能用到的系统
|
||||
|
||||
## 当前版本功能
|
||||
|
||||
[](https://lgtm.com/projects/g/esengine/egret-framework/context:javascript)
|
||||
|
||||
这是一套用于egret的游戏框架,里面包含ECS框架用于管理场景实体,一些常用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] 移动组件
|
||||
- [ ] 刚体组件
|
||||
@@ -28,16 +19,43 @@
|
||||
- [ ] 网格弹簧组件
|
||||
- [ ] 相机震动组件
|
||||
- [ ] 霓虹灯组件
|
||||
- [ ] 跟随相机组件
|
||||
- [ ] 系统列表
|
||||
- [ ] 被动系统
|
||||
- [ ] 协调系统
|
||||
- [ ] 数学库
|
||||
- [ ] 贝塞尔曲线
|
||||
- [ ] 快速随机数类
|
||||
- [ ] 浮点助手类
|
||||
- [ ] 高性能数组
|
||||
- [x] 跟随相机组件
|
||||
- [x] 系统列表
|
||||
- [x] 被动系统
|
||||
- [x] 协调系统
|
||||
- [x] A*寻路(AStar)
|
||||
- [x] 常用碰撞检测
|
||||
- [x] 数学库
|
||||
- [x] 简易矩阵类
|
||||
- [x] 简易2d 向量类
|
||||
- [x] 掩码实用类
|
||||
- [x] 贝塞尔曲线
|
||||
- [x] 快速随机数类
|
||||
- [x] BreadthFirst 寻路算法
|
||||
- [x] Dijkstra 寻路算法
|
||||
- [x] 事件处理器
|
||||
|
||||
## 关于egret用ecs框架(typescript/javascript)
|
||||
ecs 是功能强大的实体组件系统。typescript与其他语言不同,因此我对ecs的设计尽可能的支持typescript特性。虽然ecs拥有标准实体组件系统,但在细节上有很大不同。创建标准ecs通常处于原始速度、缓存位置和其他性能原因。使用typescript,我们没有struct,因为没有必要匹配标准实体组件系统的设计方式,因为我们对内存布局没有那种控制。
|
||||
|
||||
ecs更灵活,可以更好的集中、组织、排序和过滤游戏中的对象。ecs让您拥有轻量级实体和组件,这些组件可以由系统批量处理。
|
||||
例如,您在制作一个射手,您可能会有几十到几百个子弹,这些作为轻量级实体由系统批量处理。
|
||||
|
||||
所以ecs在设计当中拥有四种重要类型:世界(Scene),过滤器(Matcher),系统(System)和实体(Entity)
|
||||
|
||||
## 世界(Scene)
|
||||
Scene是ecs包含系统和实体最外面的容器。
|
||||
|
||||
## 实体(Entity)
|
||||
实体只由系统处理。
|
||||
|
||||
## 组件(Component)
|
||||
组件应该只包含数据而没有逻辑代码。对数据进行逻辑是系统的工作。
|
||||
|
||||
## 系统(System)
|
||||
ecs中的系统会不断的更新实体。系统使用过滤器选择某些实体,然后仅更新那些选择的实体。
|
||||
|
||||
## 作者其他库(egret)
|
||||
|
||||
- [x] [行为树/实用AI 系统](https://github.com/esengine/egret-BehaviourTree-ai)
|
||||
- [行为树/实用AI 系统](https://github.com/esengine/egret-BehaviourTree-ai)
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 5.1.x | :white_check_mark: |
|
||||
| 5.0.x | :x: |
|
||||
| 4.0.x | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-slate
|
||||
@@ -0,0 +1,2 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/demo.iml" filepath="$PROJECT_DIR$/.idea/demo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "egret",
|
||||
"isShellCommand": true,
|
||||
"showOutput": "silent",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"problemMatcher": "$tsc"
|
||||
}
|
||||
@@ -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"
|
||||
@@ -77,7 +77,7 @@
|
||||
* "calculateCanvasScaleFactor": //a function return canvas scale factor
|
||||
* }
|
||||
**/
|
||||
egret.runEgret({ renderMode: "webgl", audioType: 0, calculateCanvasScaleFactor:function(context) {
|
||||
egret.runEgret({ renderMode: "canvas", audioType: 0, calculateCanvasScaleFactor:function(context) {
|
||||
var backingStore = context.backingStorePixelRatio ||
|
||||
context.webkitBackingStorePixelRatio ||
|
||||
context.mozBackingStorePixelRatio ||
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
"bin-debug/Main.js",
|
||||
"bin-debug/Platform.js",
|
||||
"bin-debug/ThemeAdapter.js",
|
||||
"bin-debug/game/CoreEmitterType.js",
|
||||
"bin-debug/game/MainScene.js",
|
||||
"bin-debug/game/PlayerController.js",
|
||||
"bin-debug/game/SimplePooled.js",
|
||||
"bin-debug/game/SpawnerComponent.js",
|
||||
"bin-debug/game/SpawnerSystem.js"
|
||||
]
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 456 B |
|
Before Width: | Height: | Size: 1019 B After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 535 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 538 B |
|
Before Width: | Height: | Size: 1002 B After Width: | Height: | Size: 337 B |
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 340 B |
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 976 B After Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 970 B After Width: | Height: | Size: 349 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 458 B |
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 313 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 502 B |
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 313 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 535 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 536 B |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,6 +44,9 @@ export class WxgamePlugin implements plugins.Command {
|
||||
if (filename == 'main.js') {
|
||||
content += "\n;window.Main = Main;"
|
||||
}
|
||||
if (filename == 'libs/long/long.js' || filename == 'libs/long/long.min.js'){
|
||||
content += "window.Long = long;"
|
||||
}
|
||||
this.md5Obj[path.basename(filename)] = this.md5(content)
|
||||
file.contents = new Buffer(content);
|
||||
}
|
||||
|
||||
@@ -27,77 +27,29 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class Main extends eui.UILayer {
|
||||
public static emitter: Emitter<CoreEmitterType>;
|
||||
|
||||
protected createChildren(): void {
|
||||
super.createChildren();
|
||||
|
||||
egret.lifecycle.addLifecycleListener((context) => {
|
||||
// custom lifecycle plugin
|
||||
})
|
||||
|
||||
egret.lifecycle.onPause = () => {
|
||||
egret.ticker.pause();
|
||||
}
|
||||
|
||||
egret.lifecycle.onResume = () => {
|
||||
egret.ticker.resume();
|
||||
}
|
||||
|
||||
//inject the custom material parser
|
||||
//注入自定义的素材解析器
|
||||
let assetAdapter = new AssetAdapter();
|
||||
egret.registerImplementation("eui.IAssetAdapter", assetAdapter);
|
||||
egret.registerImplementation("eui.IThemeAdapter", new ThemeAdapter());
|
||||
|
||||
Main.emitter = new Emitter<CoreEmitterType>();
|
||||
this.addEventListener(egret.Event.ENTER_FRAME, this.updateFrame, this);
|
||||
class Main extends es.Core {
|
||||
protected initialize() {
|
||||
this.runGame();
|
||||
}
|
||||
|
||||
private updateFrame(evt: egret.Event){
|
||||
Main.emitter.emit(CoreEmitterType.Update, evt);
|
||||
let activeScene = SceneManager.getActiveScene();
|
||||
if (activeScene){
|
||||
let player = activeScene.findEntity("player");
|
||||
if (player){
|
||||
let mover = player.getComponent<Mover>(Mover);
|
||||
mover.move(new Vector2(0, 0));
|
||||
}
|
||||
}
|
||||
private runGame() {
|
||||
this.loadResource();
|
||||
}
|
||||
|
||||
private async runGame() {
|
||||
await this.loadResource();
|
||||
this.createGameScene();
|
||||
}
|
||||
|
||||
private async loadResource() {
|
||||
try {
|
||||
private loadResource() {
|
||||
const loadingView = new LoadingUI();
|
||||
this.stage.addChild(loadingView);
|
||||
await RES.loadConfig("resource/default.res.json", "resource/");
|
||||
await this.loadTheme();
|
||||
await RES.loadGroup("preload", 0, loadingView);
|
||||
RES.loadConfig("resource/default.res.json", "resource/").then(()=>{
|
||||
RES.loadGroup("preload", 0, loadingView).then(()=>{
|
||||
this.stage.removeChild(loadingView);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private loadTheme() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// load skin theme configuration file, you can manually modify the file. And replace the default skin.
|
||||
//加载皮肤主题配置文件,可以手动修改这个文件。替换默认皮肤。
|
||||
let theme = new eui.Theme("resource/default.thm.json", this.stage);
|
||||
theme.addEventListener(eui.UIEvent.COMPLETE, () => {
|
||||
resolve();
|
||||
}, this);
|
||||
|
||||
})
|
||||
this.createGameScene();
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}).catch(err =>{
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,22 +57,6 @@ class Main extends eui.UILayer {
|
||||
* Create scene interface
|
||||
*/
|
||||
protected createGameScene(): void {
|
||||
let scene = SceneManager.createScene("main", new MainScene(this)).setActive();
|
||||
let player = scene.createEntity("player");
|
||||
player.addComponent(new PolygonMesh([new Vector2(0, 0),
|
||||
new Vector2(10, 0),
|
||||
new Vector2(10, 10),
|
||||
new Vector2(0, 10),
|
||||
new Vector2(0, 0)]));
|
||||
player.addComponent(new SpawnComponent(EnemyType.worm));
|
||||
player.addComponent(new BoxCollider()).setSize(100, 100).isTrigger = true;
|
||||
player.addComponent(new Mover());
|
||||
|
||||
let player2 = scene.createEntity("player2");
|
||||
player2.addComponent(new BoxCollider()).setSize(99, 99);
|
||||
|
||||
// Main.emitter.addObserver(CoreEmitterType.Update, ()=>{
|
||||
// console.log("update emitter");
|
||||
// });
|
||||
es.Core.scene = new scene.MainScene();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
enum CoreEmitterType {
|
||||
Update,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
class MainScene extends Scene {
|
||||
constructor(displayContent: egret.DisplayObject){
|
||||
super(displayContent);
|
||||
module scene {
|
||||
export class MainScene extends es.Scene {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// this.addEntityProcessor(new SpawnerSystem(new Matcher()));
|
||||
this.astarTest();
|
||||
@@ -8,8 +9,51 @@ class MainScene extends Scene {
|
||||
this.breadthfirstTest();
|
||||
}
|
||||
|
||||
public breadthfirstTest(){
|
||||
let graph = new UnweightedGraph<string>();
|
||||
public async onStart() {
|
||||
let sprite = new es.Sprite(RES.getRes("checkbox_select_disabled_png"));
|
||||
this.createEntityAsync("bg").then(bg => {
|
||||
bg.addComponent(new component.PlayerController());
|
||||
bg.addComponent(new es.Mover());
|
||||
let spriteRenderer = bg.addComponent(new es.ScrollingSpriteRenderer(sprite));
|
||||
spriteRenderer.setRenderLayer(4);
|
||||
spriteRenderer.scrollX = -30;
|
||||
// bg.addComponent(new es.BoxCollider());
|
||||
|
||||
this.camera.follow(bg, es.CameraStyle.lockOn);
|
||||
});
|
||||
// // bg.addComponent(new es.SpriteRenderer()).setSprite(sprite).setColor(0xff0000);
|
||||
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
let sprite = new es.Sprite(RES.getRes("checkbox_select_disabled_png"));
|
||||
let player2 = this.createEntity("bg");
|
||||
player2.addComponent(new es.SpriteRenderer()).setSprite(sprite).setRenderLayer(i);
|
||||
player2.position = new es.Vector2(30 * i, 30 * i);
|
||||
// player2.addComponent(new es.BoxCollider());
|
||||
}
|
||||
|
||||
|
||||
let pool = new es.ComponentPool<component.SimplePooled>(component.SimplePooled);
|
||||
let c1 = pool.obtain();
|
||||
let c2 = pool.obtain();
|
||||
pool.free(c1);
|
||||
let c1b = pool.obtain();
|
||||
|
||||
console.log(c1 != c2);
|
||||
console.log(c1 == c1b);
|
||||
|
||||
let button = new eui.Button();
|
||||
button.label = "切换场景";
|
||||
this.addChild(button);
|
||||
button.addEventListener(egret.TouchEvent.TOUCH_TAP, () => {
|
||||
es.Core.startSceneTransition(new es.FadeTransition(() => {
|
||||
return new MainScene();
|
||||
}));
|
||||
}, this);
|
||||
}
|
||||
|
||||
public breadthfirstTest() {
|
||||
let graph = new es.UnweightedGraph<string>();
|
||||
|
||||
graph.addEdgesForNode("a", ["b"]); // a->b
|
||||
graph.addEdgesForNode("b", ["a", "c", "d"]); // b->a b->c b->d
|
||||
@@ -18,31 +62,33 @@ class MainScene extends Scene {
|
||||
graph.addEdgesForNode("e", ["b"]); // e->b
|
||||
|
||||
// 计算从c到e的路径
|
||||
let path = BreadthFirstPathfinder.search(graph, "c", "e");
|
||||
let path = es.BreadthFirstPathfinder.search(graph, "c", "e");
|
||||
console.log(path);
|
||||
}
|
||||
|
||||
public dijkstraTest(){
|
||||
let graph = new WeightedGridGraph(20, 20);
|
||||
public dijkstraTest() {
|
||||
let graph = new es.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 es.Vector2(3, 3));
|
||||
graph.weightedNodes.push(new es.Vector2(3, 4));
|
||||
graph.weightedNodes.push(new es.Vector2(4, 3));
|
||||
graph.weightedNodes.push(new es.Vector2(4, 4));
|
||||
|
||||
let path = graph.search(new Point(3, 4), new Point(15, 17));
|
||||
let path = graph.search(new es.Vector2(3, 4), new es.Vector2(15, 17));
|
||||
console.log(path);
|
||||
}
|
||||
|
||||
public astarTest(){
|
||||
let graph = new AstarGridGraph(20, 20);
|
||||
public astarTest() {
|
||||
let graph = new es.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 es.Vector2(1, 1), new es.Vector2(29, 29));
|
||||
console.log(egret.getTimer() - startTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
module component {
|
||||
import Component = es.Component;
|
||||
import Vector2 = es.Vector2;
|
||||
import Mover = es.Mover;
|
||||
import SpriteRenderer = es.SpriteRenderer;
|
||||
import Time = es.Time;
|
||||
import Input = es.Input;
|
||||
import CollisionResult = es.CollisionResult;
|
||||
|
||||
export class PlayerController extends Component {
|
||||
private down: boolean = false;
|
||||
private touchPoint: Vector2 = Vector2.zero;
|
||||
private mover: Mover;
|
||||
private spriteRenderer: SpriteRenderer;
|
||||
|
||||
public onAddedToEntity(){
|
||||
this.entity.scene.stage.addEventListener(egret.TouchEvent.TOUCH_BEGIN, this.touchBegin, this);
|
||||
this.entity.scene.stage.addEventListener(egret.TouchEvent.TOUCH_MOVE, this.touchBegin, this);
|
||||
this.entity.scene.stage.addEventListener(egret.TouchEvent.TOUCH_END, this.touchEnd, this);
|
||||
}
|
||||
|
||||
private touchBegin(evt: egret.TouchEvent){
|
||||
this.down = true;
|
||||
this.touchPoint = new Vector2(evt.stageX, evt.stageY);
|
||||
}
|
||||
|
||||
private touchEnd(evt: egret.TouchEvent){
|
||||
this.down = false;
|
||||
this.touchPoint = new Vector2(evt.stageX, evt.stageY);
|
||||
}
|
||||
|
||||
public update(){
|
||||
if (!this.mover)
|
||||
this.mover = this.entity.getComponent<Mover>(Mover);
|
||||
|
||||
if (!this.spriteRenderer)
|
||||
this.spriteRenderer = this.entity.getComponent<SpriteRenderer>(SpriteRenderer);
|
||||
|
||||
if (!this.mover)
|
||||
return;
|
||||
|
||||
if (!SpriteRenderer)
|
||||
return;
|
||||
|
||||
if (this.down){
|
||||
let moveLeft: number = 0;
|
||||
let moveRight: number = 0;
|
||||
let speed = 100;
|
||||
let worldPos = this.entity.scene.camera.mouseToWorldPoint();
|
||||
if (worldPos.x < this.spriteRenderer.transform.position.x){
|
||||
moveLeft = -1;
|
||||
} else if(worldPos.x > this.spriteRenderer.transform.position.x){
|
||||
moveLeft = 1;
|
||||
}
|
||||
|
||||
if (worldPos.y < this.spriteRenderer.transform.position.y){
|
||||
moveRight = -1;
|
||||
} else if(worldPos.y > this.spriteRenderer.transform.position.y){
|
||||
moveRight = 1;
|
||||
}
|
||||
let collisionResult = new CollisionResult();
|
||||
this.mover.move(new Vector2(moveLeft * speed * Time.deltaTime, moveRight * speed * Time.deltaTime), collisionResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module component {
|
||||
import PooledComponent = es.PooledComponent;
|
||||
|
||||
export class SimplePooled extends PooledComponent {
|
||||
public reset(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
class SpawnComponent extends Component implements ITriggerListener {
|
||||
module component {
|
||||
export class SpawnComponent extends es.Component implements es.ITriggerListener {
|
||||
public cooldown = -1;
|
||||
public minInterval = 2;
|
||||
public maxInterval = 60;
|
||||
@@ -19,17 +20,18 @@ class SpawnComponent extends Component implements ITriggerListener {
|
||||
// console.log("update");
|
||||
}
|
||||
|
||||
public onTriggerEnter(other: Collider, local: Collider){
|
||||
public onTriggerEnter(other: es.Collider, local: es.Collider){
|
||||
if (other == local)
|
||||
console.log("repeat collider")
|
||||
console.log("repeat collider");
|
||||
console.log("enter collider");
|
||||
}
|
||||
|
||||
public onTriggerExit(other: Collider, local: Collider){
|
||||
public onTriggerExit(other: es.Collider, local: es.Collider){
|
||||
console.log("exit collider");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum EnemyType {
|
||||
export enum EnemyType {
|
||||
worm
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
class SpawnerSystem extends EntityProcessingSystem {
|
||||
constructor(matcher: Matcher){
|
||||
module system {
|
||||
export class SpawnerSystem extends es.EntityProcessingSystem {
|
||||
constructor(matcher: es.Matcher){
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
public processEntity(entity: Entity){
|
||||
let spawner = entity.getComponent<SpawnComponent>(SpawnComponent);
|
||||
public processEntity(entity: es.Entity){
|
||||
let spawner = entity.getComponent<component.SpawnComponent>(component.SpawnComponent);
|
||||
if (!spawner)
|
||||
return;
|
||||
|
||||
@@ -20,7 +21,7 @@ class SpawnerSystem extends EntityProcessingSystem {
|
||||
spawner.cooldown /= 4;
|
||||
}
|
||||
|
||||
spawner.cooldown -= Time.deltaTime;
|
||||
spawner.cooldown -= es.Time.deltaTime;
|
||||
if (spawner.cooldown <= 0){
|
||||
spawner.cooldown = Math.random() * 60;
|
||||
// CreateEnemy
|
||||
@@ -31,4 +32,5 @@ class SpawnerSystem extends EntityProcessingSystem {
|
||||
spawner.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"outDir": "bin-debug",
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"lib": [
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
}
|
||||
@@ -8,10 +8,10 @@ const tsProject = ts.createProject('tsconfig.json');
|
||||
gulp.task('buildJs', () => {
|
||||
return tsProject.src()
|
||||
.pipe(tsProject())
|
||||
.js.pipe(inject.replace('var framework;', ''))
|
||||
.pipe(inject.prepend('window.framework = {};\n'))
|
||||
.js.pipe(inject.replace('var es;', ''))
|
||||
.pipe(inject.prepend('window.es = {};\n'))
|
||||
.pipe(inject.replace('var __extends =', 'window.__extends ='))
|
||||
.pipe(minify({ ext: { min: ".min.js" } }))
|
||||
.pipe(minify({ext: {min: ".min.js"}}))
|
||||
.pipe(gulp.dest('./bin'));
|
||||
});
|
||||
|
||||
|
||||
@@ -10967,13 +10967,13 @@ declare namespace egret {
|
||||
const RUNTIME2 = "runtime2";
|
||||
/**
|
||||
* Running on Alipay
|
||||
* @version Egret 5.2.23
|
||||
* @version Egret 5.2.33
|
||||
* @platform All
|
||||
* @language en_US
|
||||
*/
|
||||
/**
|
||||
* 运行在支付宝小游戏上
|
||||
* @version Egret 5.2.26
|
||||
* @version Egret 5.2.33
|
||||
* @platform All
|
||||
* @language zh_CN
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
///<reference path="./PriorityQueueNode.ts" />
|
||||
/**
|
||||
module es {
|
||||
/**
|
||||
* 计算路径给定的IAstarGraph和开始/目标位置
|
||||
*/
|
||||
class AStarPathfinder {
|
||||
public static search<T>(graph: IAstarGraph<T>, start: T, goal: T){
|
||||
export 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>();
|
||||
cameFrom.set(start, start);
|
||||
@@ -14,17 +21,17 @@ class AStarPathfinder {
|
||||
|
||||
costSoFar.set(start, 0);
|
||||
|
||||
while (frontier.count > 0){
|
||||
while (frontier.count > 0) {
|
||||
let current = frontier.dequeue();
|
||||
|
||||
if (JSON.stringify(current.data) == JSON.stringify(goal)){
|
||||
if (JSON.stringify(current.data) == JSON.stringify(goal)) {
|
||||
foundPath = true;
|
||||
break;
|
||||
}
|
||||
|
||||
graph.getNeighbors(current.data).forEach(next => {
|
||||
let newCost = costSoFar.get(current.data) + graph.cost(current.data, next);
|
||||
if (!this.hasKey(costSoFar, next) || newCost < costSoFar.get(next)){
|
||||
if (!this.hasKey(costSoFar, next) || newCost < costSoFar.get(next)) {
|
||||
costSoFar.set(next, newCost);
|
||||
let priority = newCost + graph.heuristic(next, goal);
|
||||
frontier.enqueue(new AStarNode<T>(next), priority);
|
||||
@@ -36,7 +43,28 @@ class AStarPathfinder {
|
||||
return foundPath ? this.recontructPath(cameFrom, start, goal) : null;
|
||||
}
|
||||
|
||||
private static hasKey<T>(map: Map<T, number>, compareKey: T){
|
||||
/**
|
||||
* 从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;
|
||||
path.push(goal);
|
||||
|
||||
while (current != start) {
|
||||
current = this.getKey(cameFrom, current);
|
||||
path.push(current);
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static hasKey<T>(map: Map<T, number>, compareKey: T) {
|
||||
let iterator = map.keys();
|
||||
let r: IteratorResult<T>;
|
||||
while (r = iterator.next() , !r.done) {
|
||||
@@ -47,7 +75,7 @@ class AStarPathfinder {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static getKey<T>(map: Map<T, T>, compareKey: T){
|
||||
private static getKey<T>(map: Map<T, T>, compareKey: T) {
|
||||
let iterator = map.keys();
|
||||
let valueIterator = map.values();
|
||||
let r: IteratorResult<T>;
|
||||
@@ -59,31 +87,17 @@ class AStarPathfinder {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
|
||||
let path = [];
|
||||
let current = goal;
|
||||
path.push(goal);
|
||||
|
||||
while (current != start){
|
||||
current = this.getKey(cameFrom, current);
|
||||
path.push(current);
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 使用PriorityQueue需要的额外字段将原始数据封装在一个小类中
|
||||
*/
|
||||
class AStarNode<T> extends PriorityQueueNode {
|
||||
export class AStarNode<T> extends PriorityQueueNode {
|
||||
public data: T;
|
||||
|
||||
constructor(data: T){
|
||||
constructor(data: T) {
|
||||
super();
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,26 @@
|
||||
/**
|
||||
module es {
|
||||
/**
|
||||
* 基本静态网格图与A*一起使用
|
||||
* 将walls添加到walls HashSet,并将加权节点添加到weightedNodes
|
||||
*/
|
||||
class AstarGridGraph implements IAstarGraph<Point> {
|
||||
public dirs: Point[] = [
|
||||
new Point(1, 0),
|
||||
new Point(0, -1),
|
||||
new Point(-1, 0),
|
||||
new Point(0, 1)
|
||||
export 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){
|
||||
constructor(width: number, height: number) {
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
}
|
||||
@@ -28,27 +29,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,12 +62,13 @@ class AstarGridGraph implements IAstarGraph<Point> {
|
||||
return this._neighbors;
|
||||
}
|
||||
|
||||
public cost(from: Point, to: Point): number {
|
||||
return this.weightedNodes.find((p)=> JSON.stringify(p) == JSON.stringify(to)) ? this.weightedNodeWeight : this.defaultWeight;
|
||||
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,26 @@
|
||||
interface IAstarGraph<T> {
|
||||
module es {
|
||||
/**
|
||||
* graph的接口,可以提供给AstarPathfinder.search方法
|
||||
*/
|
||||
export 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,75 @@
|
||||
class PriorityQueue<T extends PriorityQueueNode> {
|
||||
module es {
|
||||
/**
|
||||
* 使用堆实现最小优先级队列 O(1)复杂度
|
||||
* 这种查找速度比使用字典快5-10倍
|
||||
* 但是,由于IPriorityQueue.contains()是许多寻路算法中调用最多的方法,因此尽可能快地实现它对于我们的应用程序非常重要。
|
||||
*/
|
||||
export 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(1)复杂度
|
||||
*/
|
||||
public get count() {
|
||||
return this._numNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回可同时进入此队列的最大项数。一旦你达到这个数字(即。一旦Count == MaxSize),尝试加入另一个项目将导致undefined的行为
|
||||
* O(1)复杂度
|
||||
*/
|
||||
public get maxSize() {
|
||||
return this._nodes.length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从队列中删除每个节点。
|
||||
* O(n)复杂度 所有尽可能少调用该方法
|
||||
*/
|
||||
public clear() {
|
||||
this._nodes.splice(1, this._numNodes);
|
||||
this._numNodes = 0;
|
||||
}
|
||||
|
||||
public get count() {
|
||||
return this._numNodes;
|
||||
/**
|
||||
* 返回(在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;
|
||||
}
|
||||
|
||||
public contains(node: T): boolean {
|
||||
return (this._nodes[node.queueIndex] == node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点放入优先队列 较低的值放在前面 先入先出
|
||||
* 如果队列已满,则结果undefined。如果节点已经加入队列,则结果undefined。
|
||||
* O(log n)
|
||||
* @param node
|
||||
* @param priority
|
||||
*/
|
||||
public enqueue(node: T, priority: number) {
|
||||
node.priority = priority;
|
||||
this._numNodes++;
|
||||
@@ -31,12 +79,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 +109,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 +131,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 +164,7 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
newParent = childLeft;
|
||||
}
|
||||
|
||||
// 检查右子节点的优先级是否高于当前节点或左子节点
|
||||
let childRightIndex = childLeftIndex + 1;
|
||||
if (childRightIndex <= this._numNodes) {
|
||||
let childRight = this._nodes[childRightIndex];
|
||||
@@ -107,13 +173,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 +191,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,16 +212,25 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
||||
}
|
||||
|
||||
private swap(node1: T, node2: T) {
|
||||
// 交换节点
|
||||
this._nodes[node1.queueIndex] = node2;
|
||||
this._nodes[node2.queueIndex] = node1;
|
||||
|
||||
// 交换他们的indicies
|
||||
let temp = node1.queueIndex;
|
||||
node1.queueIndex = node2.queueIndex;
|
||||
node2.queueIndex = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果higher的优先级高于lower,则返回true,否则返回false。
|
||||
* 注意,调用HasHigherPriority(节点,节点)(即。两个参数为同一个节点)将返回false
|
||||
* @param higher
|
||||
* @param lower
|
||||
*/
|
||||
private hasHigherPriority(higher: T, lower: T) {
|
||||
return (higher.priority < lower.priority ||
|
||||
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
class PriorityQueueNode {
|
||||
module es {
|
||||
export class PriorityQueueNode {
|
||||
/**
|
||||
* 插入此节点的优先级。在将节点添加到队列之前必须设置
|
||||
*/
|
||||
@@ -11,4 +12,5 @@ class PriorityQueueNode {
|
||||
* 由优先级队列使用-不要编辑此值。表示队列中的当前位置
|
||||
*/
|
||||
public queueIndex: number = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
module es {
|
||||
/**
|
||||
* 计算路径给定的IUnweightedGraph和开始/目标位置
|
||||
*/
|
||||
class BreadthFirstPathfinder {
|
||||
public static search<T>(graph: IUnweightedGraph<T>, start: T, goal: T): T[]{
|
||||
export class BreadthFirstPathfinder {
|
||||
public static search<T>(graph: IUnweightedGraph<T>, start: T, goal: T): T[] {
|
||||
let foundPath = false;
|
||||
let frontier = [];
|
||||
frontier.unshift(start);
|
||||
@@ -10,15 +11,15 @@ class BreadthFirstPathfinder {
|
||||
let cameFrom = new Map<T, T>();
|
||||
cameFrom.set(start, start);
|
||||
|
||||
while (frontier.length > 0){
|
||||
while (frontier.length > 0) {
|
||||
let current = frontier.shift();
|
||||
if (JSON.stringify(current) == JSON.stringify(goal)){
|
||||
if (JSON.stringify(current) == JSON.stringify(goal)) {
|
||||
foundPath = true;
|
||||
break;
|
||||
}
|
||||
|
||||
graph.getNeighbors(current).forEach(next => {
|
||||
if (!this.hasKey(cameFrom, next)){
|
||||
if (!this.hasKey(cameFrom, next)) {
|
||||
frontier.unshift(next);
|
||||
cameFrom.set(next, current);
|
||||
}
|
||||
@@ -28,7 +29,7 @@ class BreadthFirstPathfinder {
|
||||
return foundPath ? AStarPathfinder.recontructPath(cameFrom, start, goal) : null;
|
||||
}
|
||||
|
||||
private static hasKey<T>(map: Map<T, T>, compareKey: T){
|
||||
private static hasKey<T>(map: Map<T, T>, compareKey: T) {
|
||||
let iterator = map.keys();
|
||||
let r: IteratorResult<T>;
|
||||
while (r = iterator.next() , !r.done) {
|
||||
@@ -38,4 +39,5 @@ class BreadthFirstPathfinder {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
interface IUnweightedGraph<T>{
|
||||
module es {
|
||||
export interface IUnweightedGraph<T> {
|
||||
/**
|
||||
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点。
|
||||
* @param node
|
||||
*/
|
||||
getNeighbors(node: T): T[];
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
/**
|
||||
module es {
|
||||
/**
|
||||
* 一个未加权图的基本实现。所有的边都被缓存。这种类型的图最适合于非基于网格的图。
|
||||
* 作为边添加的任何节点都必须在边字典中有一个条目作为键。
|
||||
*/
|
||||
class UnweightedGraph<T> implements IUnweightedGraph<T> {
|
||||
export class UnweightedGraph<T> implements IUnweightedGraph<T> {
|
||||
public edges: Map<T, T[]> = new Map<T, T[]>();
|
||||
|
||||
public addEdgesForNode(node: T, edges: T[]){
|
||||
public addEdgesForNode(node: T, edges: T[]) {
|
||||
this.edges.set(node, edges);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getNeighbors(node: T){
|
||||
public getNeighbors(node: T) {
|
||||
return this.edges.get(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,34 @@
|
||||
///<reference path="../../../Math/Point.ts" />
|
||||
/**
|
||||
///<reference path="../../../Math/Vector2.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 基本的未加权网格图形用于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)
|
||||
export 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 +36,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 +56,8 @@ 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,4 +1,5 @@
|
||||
interface IWeightedGraph<T>{
|
||||
module es {
|
||||
export interface IWeightedGraph<T> {
|
||||
/**
|
||||
*
|
||||
* @param node
|
||||
@@ -11,4 +12,5 @@ interface IWeightedGraph<T>{
|
||||
* @param to
|
||||
*/
|
||||
cost(from: T, to: T): number;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,60 @@
|
||||
///<reference path="../../../Math/Point.ts" />
|
||||
/**
|
||||
///<reference path="../../../Math/Vector2.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 支持一种加权节点的基本网格图
|
||||
*/
|
||||
class WeightedGridGraph implements IWeightedGraph<Point> {
|
||||
export 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){
|
||||
constructor(width: number, height: number, allowDiagonalSearch: boolean = false) {
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
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 +62,8 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
class WeightedNode<T> extends PriorityQueueNode {
|
||||
module es {
|
||||
export class WeightedNode<T> extends PriorityQueueNode {
|
||||
public data: T;
|
||||
|
||||
constructor(data: T){
|
||||
constructor(data: T) {
|
||||
super();
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WeightedPathfinder {
|
||||
public static search<T>(graph: IWeightedGraph<T>, start: T, goal: T){
|
||||
export class WeightedPathfinder {
|
||||
public static search<T>(graph: IWeightedGraph<T>, start: T, goal: T) {
|
||||
let foundPath = false;
|
||||
|
||||
let cameFrom = new Map<T, T>();
|
||||
@@ -20,17 +21,17 @@ class WeightedPathfinder {
|
||||
|
||||
costSoFar.set(start, 0);
|
||||
|
||||
while (frontier.count > 0){
|
||||
while (frontier.count > 0) {
|
||||
let current = frontier.dequeue();
|
||||
|
||||
if (JSON.stringify(current.data) == JSON.stringify(goal)){
|
||||
if (JSON.stringify(current.data) == JSON.stringify(goal)) {
|
||||
foundPath = true;
|
||||
break;
|
||||
}
|
||||
|
||||
graph.getNeighbors(current.data).forEach(next => {
|
||||
let newCost = costSoFar.get(current.data) + graph.cost(current.data, next);
|
||||
if (!this.hasKey(costSoFar, next) || newCost < costSoFar.get(next)){
|
||||
if (!this.hasKey(costSoFar, next) || newCost < costSoFar.get(next)) {
|
||||
costSoFar.set(next, newCost);
|
||||
let priprity = newCost;
|
||||
frontier.enqueue(new WeightedNode<T>(next), priprity);
|
||||
@@ -42,7 +43,22 @@ class WeightedPathfinder {
|
||||
return foundPath ? this.recontructPath(cameFrom, start, goal) : null;
|
||||
}
|
||||
|
||||
private static hasKey<T>(map: Map<T, number>, compareKey: T){
|
||||
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[] {
|
||||
let path = [];
|
||||
let current = goal;
|
||||
path.push(goal);
|
||||
|
||||
while (current != start) {
|
||||
current = this.getKey(cameFrom, current);
|
||||
path.push(current);
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static hasKey<T>(map: Map<T, number>, compareKey: T) {
|
||||
let iterator = map.keys();
|
||||
let r: IteratorResult<T>;
|
||||
while (r = iterator.next() , !r.done) {
|
||||
@@ -53,7 +69,7 @@ class WeightedPathfinder {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static getKey<T>(map: Map<T, T>, compareKey: T){
|
||||
private static getKey<T>(map: Map<T, T>, compareKey: T) {
|
||||
let iterator = map.keys();
|
||||
let valueIterator = map.values();
|
||||
let r: IteratorResult<T>;
|
||||
@@ -65,19 +81,5 @@ class WeightedPathfinder {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
|
||||
let path = [];
|
||||
let current = goal;
|
||||
path.push(goal);
|
||||
|
||||
while (current != start){
|
||||
current = this.getKey(cameFrom, current);
|
||||
path.push(current);
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
module es {
|
||||
export 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 (Core.scene) {
|
||||
Core.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
class DebugDefaults {
|
||||
module es {
|
||||
export class DebugDefaults {
|
||||
public static verletParticle = 0xDC345E;
|
||||
public static verletConstraintEdge = 0x433E36;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
module es {
|
||||
export enum DebugDrawType {
|
||||
line,
|
||||
hollowRectangle,
|
||||
pixel,
|
||||
text
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,114 @@
|
||||
abstract class Component {
|
||||
module es {
|
||||
/**
|
||||
* 执行顺序
|
||||
* - onAddedToEntity
|
||||
* - OnEnabled
|
||||
*
|
||||
* 删除执行顺序
|
||||
* - onRemovedFromEntity
|
||||
*/
|
||||
export abstract class Component extends egret.HashObject {
|
||||
/**
|
||||
* 此组件附加的实体
|
||||
*/
|
||||
public entity: Entity;
|
||||
private _enabled: boolean = true;
|
||||
/**
|
||||
* 更新该组件的时间间隔。这与实体的更新间隔无关。
|
||||
*/
|
||||
public updateInterval: number = 1;
|
||||
|
||||
public get transform(){
|
||||
/**
|
||||
* 快速访问 this.entity.transform
|
||||
*/
|
||||
public get transform(): Transform {
|
||||
return this.entity.transform;
|
||||
}
|
||||
|
||||
public get enabled(){
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
|
||||
*/
|
||||
public get enabled() {
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean){
|
||||
/**
|
||||
* 如果组件和实体都已启用,则为。当启用该组件时,将调用该组件的生命周期方法。状态的改变会导致调用onEnabled/onDisable。
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
public setEnabled(isEnabled: boolean){
|
||||
if (this._enabled != isEnabled){
|
||||
private _updateOrder = 0;
|
||||
|
||||
/** 更新此实体上组件的顺序 */
|
||||
public get updateOrder() {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/** 更新此实体上组件的顺序 */
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当此组件已分配其实体,但尚未添加到实体的活动组件列表时调用。有用的东西,如物理组件,需要访问转换来修改碰撞体的属性。
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在提交所有挂起的组件更改后,将该组件添加到场景时调用。此时,设置了实体字段和实体。场景也设定好了。
|
||||
*/
|
||||
public onAddedToEntity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当此组件从其实体中移除时调用。在这里做所有的清理工作。
|
||||
*/
|
||||
public onRemovedFromEntity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体的位置改变时调用。这允许组件知道它们由于父实体的移动而移动了。
|
||||
* @param comp
|
||||
*/
|
||||
public onEntityTransformChanged(comp: transform.Component) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public debugRender() {
|
||||
}
|
||||
|
||||
/**
|
||||
*当父实体或此组件启用时调用
|
||||
*/
|
||||
public onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用父实体或此组件时调用
|
||||
*/
|
||||
public onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当该组件启用时每帧进行调用
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
|
||||
if (this._enabled){
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
}else{
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
@@ -29,46 +116,22 @@ abstract class Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
public initialize(){
|
||||
|
||||
public setUpdateOrder(updateOrder: number) {
|
||||
if (this._updateOrder != updateOrder) {
|
||||
this._updateOrder = updateOrder;
|
||||
}
|
||||
|
||||
public onAddedToEntity(){
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public onRemovedFromEntity(){
|
||||
/**
|
||||
* 创建此组件的克隆
|
||||
*/
|
||||
public clone(): Component {
|
||||
let component = ObjectUtils.clone<Component>(this);
|
||||
component.entity = null;
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
public onEnabled(){
|
||||
|
||||
}
|
||||
|
||||
public onDisabled(){
|
||||
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: ComponentTransform){
|
||||
|
||||
}
|
||||
|
||||
public update(){
|
||||
|
||||
}
|
||||
|
||||
public debugRender(){
|
||||
|
||||
}
|
||||
|
||||
/** 内部使用 运行时不应该调用 */
|
||||
public registerComponent(){
|
||||
this.entity.componentBits.set(ComponentTypeManager.getIndexFor(this), false);
|
||||
this.entity.scene.entityProcessors.onComponentAdded(this.entity);
|
||||
}
|
||||
|
||||
public deregisterComponent(){
|
||||
this.entity.componentBits.set(ComponentTypeManager.getIndexFor(this));
|
||||
this.entity.scene.entityProcessors.onComponentRemoved(this.entity);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,177 @@
|
||||
///<reference path="../Component.ts"/>
|
||||
class Camera extends Component {
|
||||
private _zoom;
|
||||
private _origin: Vector2;
|
||||
private _transformMatrix: Matrix2D = Matrix2D.identity;
|
||||
private _inverseTransformMatrix = Matrix2D.identity;
|
||||
module es {
|
||||
export enum CameraStyle {
|
||||
lockOn,
|
||||
cameraWindow,
|
||||
}
|
||||
|
||||
private _minimumZoom = 0.3;
|
||||
private _maximumZoom = 3;
|
||||
private _areMatrixesDirty = true;
|
||||
private _inset: CameraInset;
|
||||
private _bounds: Rectangle;
|
||||
private _areBoundsDirty = true;
|
||||
export class CameraInset {
|
||||
public left: number = 0;
|
||||
public right: number = 0;
|
||||
public top: number = 0;
|
||||
public bottom: number = 0;
|
||||
}
|
||||
|
||||
public get bounds(){
|
||||
if (this._areMatrixesDirty)
|
||||
export class Camera extends Component {
|
||||
public _inset: CameraInset = new CameraInset();
|
||||
public _areMatrixedDirty: boolean = true;
|
||||
public _areBoundsDirty: boolean = true;
|
||||
public _isProjectionMatrixDirty = true;
|
||||
/**
|
||||
* 如果相机模式为cameraWindow 则会进行缓动移动
|
||||
* 该值为移动速度
|
||||
*/
|
||||
public followLerp = 0.1;
|
||||
/**
|
||||
* 在cameraWindow模式下,宽度/高度被用做边界框,允许在不移动相机的情况下移动
|
||||
* 在lockOn模式下,只使用deadZone的x/y值 你可以通过直接setCenteredDeadzone重写它来自定义deadZone
|
||||
*/
|
||||
public deadzone: Rectangle = new Rectangle();
|
||||
/**
|
||||
* 相机聚焦于屏幕中心的偏移
|
||||
*/
|
||||
public focusOffset: Vector2 = Vector2.zero;
|
||||
/**
|
||||
* 如果为true 相机位置则不会超出地图矩形(0, 0, mapwidth, mapheight)
|
||||
*/
|
||||
public mapLockEnabled: boolean = false;
|
||||
/**
|
||||
* 當前地圖映射的寬度和高度
|
||||
*/
|
||||
public mapSize: Rectangle = new Rectangle();
|
||||
public _targetEntity: Entity;
|
||||
public _targetCollider: Collider;
|
||||
public _desiredPositionDelta: Vector2 = new Vector2();
|
||||
public _cameraStyle: CameraStyle;
|
||||
public _worldSpaceDeadZone: Rectangle = new Rectangle();
|
||||
|
||||
constructor(targetEntity: Entity = null, cameraStyle: CameraStyle = CameraStyle.lockOn) {
|
||||
super();
|
||||
|
||||
this._targetEntity = targetEntity;
|
||||
this._cameraStyle = cameraStyle;
|
||||
this.setZoom(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对entity.transform.position的快速访问
|
||||
*/
|
||||
public get position() {
|
||||
return this.entity.transform.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对entity.transform.position的快速访问
|
||||
* @param value
|
||||
*/
|
||||
public set position(value: Vector2) {
|
||||
this.entity.transform.position = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对entity.transform.rotation的快速访问
|
||||
*/
|
||||
public get rotation(): number {
|
||||
return this.entity.transform.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对entity.transform.rotation的快速访问
|
||||
* @param value
|
||||
*/
|
||||
public set rotation(value: number) {
|
||||
this.entity.transform.rotation = value;
|
||||
}
|
||||
|
||||
public _zoom;
|
||||
|
||||
/**
|
||||
* 缩放值应该在-1和1之间、然后将该值从minimumZoom转换为maximumZoom。
|
||||
* 允许你设置适当的最小/最大值,然后使用更直观的-1到1的映射来更改缩放
|
||||
*/
|
||||
public get zoom() {
|
||||
if (this._zoom == 0)
|
||||
return 1;
|
||||
|
||||
if (this._zoom < 1)
|
||||
return MathHelper.map(this._zoom, this._minimumZoom, 1, -1, 0);
|
||||
|
||||
return MathHelper.map(this._zoom, 1, this._maximumZoom, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放值应该在-1和1之间、然后将该值从minimumZoom转换为maximumZoom。
|
||||
* 允许你设置适当的最小/最大值,然后使用更直观的-1到1的映射来更改缩放
|
||||
* @param value
|
||||
*/
|
||||
public set zoom(value: number) {
|
||||
this.setZoom(value);
|
||||
}
|
||||
|
||||
public _minimumZoom = 0.3;
|
||||
|
||||
/**
|
||||
* 相机变焦可以达到的最小非缩放值(0-number.max)。默认为0.3
|
||||
*/
|
||||
public get minimumZoom() {
|
||||
return this._minimumZoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相机变焦可以达到的最小非缩放值(0-number.max)。默认为0.3
|
||||
* @param value
|
||||
*/
|
||||
public set minimumZoom(value: number) {
|
||||
this.setMinimumZoom(value);
|
||||
}
|
||||
|
||||
public _maximumZoom = 3;
|
||||
|
||||
/**
|
||||
* 相机变焦可以达到的最大非缩放值(0-number.max)。默认为3
|
||||
*/
|
||||
public get maximumZoom() {
|
||||
return this._maximumZoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相机变焦可以达到的最大非缩放值(0-number.max)。默认为3
|
||||
* @param value
|
||||
*/
|
||||
public set maximumZoom(value: number) {
|
||||
this.setMaximumZoom(value);
|
||||
}
|
||||
|
||||
public _bounds: Rectangle = new Rectangle();
|
||||
|
||||
/**
|
||||
* 相机的世界-空间边界
|
||||
*/
|
||||
public get bounds() {
|
||||
if (this._areMatrixedDirty)
|
||||
this.updateMatrixes();
|
||||
|
||||
if (this._areBoundsDirty){
|
||||
let stage = this.entity.scene.stage;
|
||||
if (this._areBoundsDirty) {
|
||||
// 旋转或非旋转的边界都需要左上角和右下角
|
||||
let topLeft = this.screenToWorldPoint(new Vector2(this._inset.left, this._inset.top));
|
||||
let bottomRight = this.screenToWorldPoint(new Vector2(stage.stageWidth - this._inset.right, stage.stageHeight - this._inset.bottom));
|
||||
let bottomRight = this.screenToWorldPoint(new Vector2(Core.graphicsDevice.viewport.width - this._inset.right,
|
||||
Core.graphicsDevice.viewport.height - this._inset.bottom));
|
||||
|
||||
if (this.entity.transform.rotation != 0){
|
||||
let topRight = this.screenToWorldPoint(new Vector2(stage.stageWidth - this._inset.right, this._inset.top));
|
||||
let bottomLeft = this.screenToWorldPoint(new Vector2(this._inset.left, stage.stageHeight - this._inset.bottom));
|
||||
if (this.entity.transform.rotation != 0) {
|
||||
// 特别注意旋转的边界。我们需要找到绝对的最小/最大值并从中创建边界
|
||||
let topRight = this.screenToWorldPoint(new Vector2(Core.graphicsDevice.viewport.width - this._inset.right,
|
||||
this._inset.top));
|
||||
let bottomLeft = this.screenToWorldPoint(new Vector2(this._inset.left,
|
||||
Core.graphicsDevice.viewport.height - this._inset.bottom));
|
||||
|
||||
let minX = MathHelper.minOf(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
|
||||
let maxX = MathHelper.maxOf(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
|
||||
let minY = MathHelper.minOf(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
|
||||
let maxY = MathHelper.maxOf(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
|
||||
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._bounds.location = new Vector2(minX, minY);
|
||||
this._bounds.width = maxX - minX;
|
||||
this._bounds.height = maxY - minY;
|
||||
}else{
|
||||
} else {
|
||||
this._bounds.location = topLeft;
|
||||
this._bounds.width = bottomRight.x - topLeft.x;
|
||||
this._bounds.height = bottomRight.y - topLeft.y;
|
||||
@@ -45,66 +183,117 @@ class Camera extends Component {
|
||||
return this._bounds;
|
||||
}
|
||||
|
||||
public get zoom(){
|
||||
if (this._zoom == 0)
|
||||
return 1;
|
||||
public _transformMatrix: Matrix2D = new Matrix2D().identity();
|
||||
|
||||
if (this._zoom < 1)
|
||||
return MathHelper.map(this._zoom, this._minimumZoom, 1, -1, 0);
|
||||
|
||||
return MathHelper.map(this._zoom, 1, this._maximumZoom, 0, 1);
|
||||
}
|
||||
|
||||
public set zoom(value: number){
|
||||
this.setZoom(value);
|
||||
}
|
||||
|
||||
public get minimumZoom(){
|
||||
return this._minimumZoom;
|
||||
}
|
||||
|
||||
public set minimumZoom(value: number){
|
||||
this.setMinimumZoom(value);
|
||||
}
|
||||
|
||||
public get maximumZoom(){
|
||||
return this._maximumZoom;
|
||||
}
|
||||
|
||||
public set maximumZoom(value: number){
|
||||
this.setMaximumZoom(value);
|
||||
}
|
||||
|
||||
public get origin(){
|
||||
return this._origin;
|
||||
}
|
||||
|
||||
public set origin(value: Vector2){
|
||||
if (this._origin != value){
|
||||
this._origin = value;
|
||||
this._areMatrixesDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public get transformMatrix(){
|
||||
if (this._areBoundsDirty)
|
||||
/**
|
||||
* 用于从世界坐标转换到屏幕
|
||||
*/
|
||||
public get transformMatrix(): Matrix2D {
|
||||
if (this._areMatrixedDirty)
|
||||
this.updateMatrixes();
|
||||
return this._transformMatrix;
|
||||
}
|
||||
|
||||
public get inverseTransformMatrix(){
|
||||
if (this._areBoundsDirty)
|
||||
public _inverseTransformMatrix: Matrix2D = new Matrix2D().identity();
|
||||
|
||||
/**
|
||||
* 用于从屏幕坐标到世界的转换
|
||||
*/
|
||||
public get inverseTransformMatrix(): Matrix2D {
|
||||
if (this._areMatrixedDirty)
|
||||
this.updateMatrixes();
|
||||
return this._inverseTransformMatrix;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
public _origin: Vector2 = Vector2.zero;
|
||||
|
||||
this.setZoom(0);
|
||||
public get origin() {
|
||||
return this._origin;
|
||||
}
|
||||
|
||||
public set origin(value: Vector2) {
|
||||
if (this._origin != value) {
|
||||
this._origin = value;
|
||||
this._areMatrixedDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当场景渲染目标的大小发生变化时,我们会更新相机的原点并调整它的位置以保持它原来的位置
|
||||
* @param newWidth
|
||||
* @param newHeight
|
||||
*/
|
||||
public onSceneSizeChanged(newWidth: number, newHeight: number) {
|
||||
let oldOrigin = this._origin;
|
||||
this.origin = new Vector2(newWidth / 2, newHeight / 2);
|
||||
|
||||
this.entity.transform.position = Vector2.add(this.entity.transform.position, Vector2.subtract(this._origin, oldOrigin));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用于从视口边缘插入摄像机边界的量
|
||||
* @param left
|
||||
* @param right
|
||||
* @param top
|
||||
* @param bottom
|
||||
*/
|
||||
public setInset(left: number, right: number, top: number, bottom: number): Camera {
|
||||
this._inset = new CameraInset();
|
||||
this._inset.left = left;
|
||||
this._inset.right = right;
|
||||
this._inset.top = top;
|
||||
this._inset.bottom = bottom;
|
||||
this._areBoundsDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对entity.transform.setPosition快速访问
|
||||
* @param position
|
||||
*/
|
||||
public setPosition(position: Vector2) {
|
||||
this.entity.transform.setPosition(position.x, position.y);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对entity.transform.setRotation的快速访问
|
||||
* @param rotation
|
||||
*/
|
||||
public setRotation(rotation: number): Camera {
|
||||
this.entity.transform.setRotation(rotation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缩放值,缩放值应该在-1到1之间。然后将该值从minimumZoom转换为maximumZoom
|
||||
* 允许您设置适当的最小/最大值。使用更直观的-1到1的映射来更改缩放
|
||||
* @param zoom
|
||||
*/
|
||||
public setZoom(zoom: number): Camera {
|
||||
let newZoom = MathHelper.clamp(zoom, -1, 1);
|
||||
if (newZoom == 0) {
|
||||
this._zoom = 1;
|
||||
} else if (newZoom < 0) {
|
||||
this._zoom = MathHelper.map(newZoom, -1, 0, this._minimumZoom, 1);
|
||||
} else {
|
||||
this._zoom = MathHelper.map(newZoom, 0, 1, 1, this._maximumZoom);
|
||||
}
|
||||
this._areMatrixedDirty = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相机变焦可以达到的最小非缩放值(0-number.max) 默认为0.3
|
||||
* @param minZoom
|
||||
*/
|
||||
public setMinimumZoom(minZoom: number): Camera {
|
||||
if (minZoom <= 0) {
|
||||
console.error("minimumZoom must be greater than zero");
|
||||
return;
|
||||
}
|
||||
|
||||
public setMinimumZoom(minZoom: number): Camera{
|
||||
if (this._zoom < minZoom)
|
||||
this._zoom = this.minimumZoom;
|
||||
|
||||
@@ -112,7 +301,16 @@ class Camera extends Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相机变焦可以达到的最大非缩放值(0-number.max) 默认为3
|
||||
* @param maxZoom
|
||||
*/
|
||||
public setMaximumZoom(maxZoom: number): Camera {
|
||||
if (maxZoom <= 0) {
|
||||
console.error("maximumZoom must be greater than zero");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._zoom > maxZoom)
|
||||
this._zoom = maxZoom;
|
||||
|
||||
@@ -120,73 +318,167 @@ class Camera extends Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setZoom(zoom: number){
|
||||
let newZoom = MathHelper.clamp(zoom, -1, 1);
|
||||
if (newZoom == 0){
|
||||
this._zoom = 1;
|
||||
} else if(newZoom < 0){
|
||||
this._zoom = MathHelper.map(newZoom, -1, 0, this._minimumZoom, 1);
|
||||
public onEntityTransformChanged(comp: transform.Component) {
|
||||
this._areMatrixedDirty = true;
|
||||
}
|
||||
|
||||
public zoomIn(deltaZoom: number) {
|
||||
this.zoom += deltaZoom;
|
||||
}
|
||||
|
||||
public zoomOut(deltaZoom: number) {
|
||||
this.zoom -= deltaZoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个点从世界坐标转换到屏幕
|
||||
* @param worldPosition
|
||||
*/
|
||||
public worldToScreenPoint(worldPosition: Vector2): Vector2 {
|
||||
this.updateMatrixes();
|
||||
worldPosition = Vector2.transform(worldPosition, this._transformMatrix);
|
||||
return worldPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将点从屏幕坐标转换为世界坐标
|
||||
* @param screenPosition
|
||||
*/
|
||||
public screenToWorldPoint(screenPosition: Vector2): Vector2 {
|
||||
this.updateMatrixes();
|
||||
screenPosition = Vector2.transform(screenPosition, this._inverseTransformMatrix);
|
||||
return screenPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回鼠标在世界空间中的位置
|
||||
*/
|
||||
public mouseToWorldPoint(): Vector2 {
|
||||
return this.screenToWorldPoint(Input.touchPosition);
|
||||
}
|
||||
|
||||
public onAddedToEntity() {
|
||||
this.follow(this._targetEntity, this._cameraStyle);
|
||||
}
|
||||
|
||||
public update() {
|
||||
let halfScreen = Vector2.multiply(new Vector2(this.bounds.width, this.bounds.height), new Vector2(0.5));
|
||||
this._worldSpaceDeadZone.x = this.position.x - halfScreen.x * Core.scene.scaleX + this.deadzone.x + this.focusOffset.x;
|
||||
this._worldSpaceDeadZone.y = this.position.y - halfScreen.y * Core.scene.scaleY + this.deadzone.y + this.focusOffset.y;
|
||||
this._worldSpaceDeadZone.width = this.deadzone.width;
|
||||
this._worldSpaceDeadZone.height = this.deadzone.height;
|
||||
|
||||
if (this._targetEntity)
|
||||
this.updateFollow();
|
||||
|
||||
this.position = Vector2.lerp(this.position, Vector2.add(this.position, this._desiredPositionDelta), this.followLerp);
|
||||
this.entity.transform.roundPosition();
|
||||
|
||||
if (this.mapLockEnabled) {
|
||||
this.position = this.clampToMapSize(this.position);
|
||||
this.entity.transform.roundPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 固定相机 永远不会离开地图的可见区域
|
||||
* @param position
|
||||
*/
|
||||
public clampToMapSize(position: Vector2) {
|
||||
let halfScreen = Vector2.multiply(new Vector2(this.bounds.width, this.bounds.height), new Vector2(0.5)).add(new Vector2(this.mapSize.x, this.mapSize.y));
|
||||
let cameraMax = new Vector2(this.mapSize.width - halfScreen.x, this.mapSize.height - halfScreen.y);
|
||||
|
||||
return Vector2.clamp(position, halfScreen, cameraMax);
|
||||
}
|
||||
|
||||
public updateFollow() {
|
||||
this._desiredPositionDelta.x = this._desiredPositionDelta.y = 0;
|
||||
|
||||
if (this._cameraStyle == CameraStyle.lockOn) {
|
||||
let targetX = this._targetEntity.transform.position.x;
|
||||
let targetY = this._targetEntity.transform.position.y;
|
||||
|
||||
if (this._worldSpaceDeadZone.x > targetX)
|
||||
this._desiredPositionDelta.x = targetX - this._worldSpaceDeadZone.x;
|
||||
else if (this._worldSpaceDeadZone.x < targetX)
|
||||
this._desiredPositionDelta.x = targetX - this._worldSpaceDeadZone.x;
|
||||
|
||||
if (this._worldSpaceDeadZone.y < targetY)
|
||||
this._desiredPositionDelta.y = targetY - this._worldSpaceDeadZone.y;
|
||||
else if (this._worldSpaceDeadZone.y > targetY)
|
||||
this._desiredPositionDelta.y = targetY - this._worldSpaceDeadZone.y;
|
||||
} else {
|
||||
this._zoom = MathHelper.map(newZoom, 0, 1, 1, this._maximumZoom);
|
||||
if (!this._targetCollider) {
|
||||
this._targetCollider = this._targetEntity.getComponent<Collider>(Collider);
|
||||
if (!this._targetCollider)
|
||||
return;
|
||||
}
|
||||
|
||||
this._areMatrixesDirty = true;
|
||||
let targetBounds = this._targetEntity.getComponent<Collider>(Collider).bounds;
|
||||
if (!this._worldSpaceDeadZone.containsRect(targetBounds)) {
|
||||
if (this._worldSpaceDeadZone.left > targetBounds.left)
|
||||
this._desiredPositionDelta.x = targetBounds.left - this._worldSpaceDeadZone.left;
|
||||
else if (this._worldSpaceDeadZone.right < targetBounds.right)
|
||||
this._desiredPositionDelta.x = targetBounds.right - this._worldSpaceDeadZone.right;
|
||||
|
||||
return this;
|
||||
if (this._worldSpaceDeadZone.bottom < targetBounds.bottom)
|
||||
this._desiredPositionDelta.y = targetBounds.bottom - this._worldSpaceDeadZone.bottom;
|
||||
else if (this._worldSpaceDeadZone.top > targetBounds.top)
|
||||
this._desiredPositionDelta.y = targetBounds.top - this._worldSpaceDeadZone.top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
public follow(targetEntity: Entity, cameraStyle: CameraStyle = CameraStyle.cameraWindow) {
|
||||
this._targetEntity = targetEntity;
|
||||
this._cameraStyle = cameraStyle;
|
||||
|
||||
switch (this._cameraStyle) {
|
||||
case CameraStyle.cameraWindow:
|
||||
let w = this.bounds.width / 6;
|
||||
let h = this.bounds.height / 3;
|
||||
this.deadzone = new Rectangle((this.bounds.width - w) / 2, (this.bounds.height - h) / 2, w, h);
|
||||
break;
|
||||
case CameraStyle.lockOn:
|
||||
this.deadzone = new Rectangle(this.bounds.width / 2, this.bounds.height / 2, 10, 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public update(){
|
||||
|
||||
/**
|
||||
* 以给定的尺寸设置当前相机边界中心的死区
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public setCenteredDeadzone(width: number, height: number) {
|
||||
this.deadzone = new Rectangle((this.bounds.width - width) / 2, (this.bounds.height - height) / 2, width, height);
|
||||
}
|
||||
|
||||
public setPosition(position: Vector2){
|
||||
this.entity.transform.setPosition(position);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public updateMatrixes(){
|
||||
if (!this._areMatrixesDirty)
|
||||
protected updateMatrixes() {
|
||||
if (!this._areMatrixedDirty)
|
||||
return;
|
||||
|
||||
let tempMat: Matrix2D;
|
||||
this._transformMatrix = Matrix2D.createTranslation(-this.entity.transform.position.x, -this.entity.transform.position.y);
|
||||
if (this._zoom != 1){
|
||||
tempMat = Matrix2D.createScale(this._zoom, this._zoom);
|
||||
this._transformMatrix = Matrix2D.multiply(this._transformMatrix, tempMat);
|
||||
this._transformMatrix = Matrix2D.create().translate(-this.entity.transform.position.x, -this.entity.transform.position.y);
|
||||
|
||||
if (this._zoom != 1) {
|
||||
tempMat = Matrix2D.create().scale(this._zoom, this._zoom);
|
||||
this._transformMatrix = this._transformMatrix.multiply(tempMat);
|
||||
}
|
||||
|
||||
tempMat = Matrix2D.createTranslation(this._origin.x, this._origin.y, tempMat);
|
||||
this._transformMatrix = Matrix2D.multiply(this._transformMatrix, tempMat);
|
||||
if (this.entity.transform.rotation != 0) {
|
||||
tempMat = Matrix2D.create().rotate(this.entity.transform.rotation);
|
||||
this._transformMatrix = this._transformMatrix.multiply(tempMat);
|
||||
}
|
||||
|
||||
this._inverseTransformMatrix = Matrix2D.invert(this._transformMatrix);
|
||||
tempMat = Matrix2D.create().translate(this._origin.x, this._origin.y);
|
||||
this._transformMatrix = this._transformMatrix.multiply(tempMat);
|
||||
|
||||
this._inverseTransformMatrix = this._transformMatrix.invert();
|
||||
|
||||
// 无论何时矩阵改变边界都是无效的
|
||||
this._areBoundsDirty = true;
|
||||
this._areMatrixesDirty = false;
|
||||
this._areMatrixedDirty = false;
|
||||
}
|
||||
|
||||
public screenToWorldPoint(screenPosition: Vector2){
|
||||
this.updateMatrixes();
|
||||
return Vector2.transform(screenPosition, this._inverseTransformMatrix);
|
||||
}
|
||||
|
||||
public worldToScreenPoint(worldPosition: Vector2){
|
||||
this.updateMatrixes();
|
||||
return Vector2.transform(worldPosition, this._transformMatrix);
|
||||
}
|
||||
|
||||
public destory() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class CameraInset {
|
||||
public left;
|
||||
public right;
|
||||
public top;
|
||||
public bottom;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
module es {
|
||||
export class ComponentPool<T extends PooledComponent> {
|
||||
private _cache: T[];
|
||||
private _type: any;
|
||||
|
||||
constructor(typeClass: any) {
|
||||
this._type = typeClass;
|
||||
this._cache = [];
|
||||
}
|
||||
|
||||
public obtain(): T {
|
||||
try {
|
||||
return this._cache.length > 0 ? this._cache.shift() : new this._type();
|
||||
} catch (err) {
|
||||
throw new Error(this._type + err);
|
||||
}
|
||||
}
|
||||
|
||||
public free(component: T) {
|
||||
component.reset();
|
||||
this._cache.push(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module es {
|
||||
/**
|
||||
* 用于比较组件更新排序
|
||||
*/
|
||||
export class IUpdatableComparer {
|
||||
public compare(a: Component, b: Component) {
|
||||
return a.updateOrder - b.updateOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,25 @@
|
||||
class Mesh extends Component {
|
||||
private _verts: VertexPosition[];
|
||||
private _primitiveCount: number;
|
||||
private _triangles: number[];
|
||||
private _topLeftVertPosition: Vector2;
|
||||
private _width;
|
||||
private _height;
|
||||
///<reference path="./RenderableComponent.ts" />
|
||||
module es {
|
||||
export class Mesh extends RenderableComponent {
|
||||
private _mesh: egret.Mesh;
|
||||
|
||||
public initialize() {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._mesh = new egret.Mesh();
|
||||
}
|
||||
|
||||
public setVertPosition(positions: Vector2[]){
|
||||
let createVerts = !this._verts || this._verts.length != positions.length;
|
||||
if (createVerts)
|
||||
this._verts = new Array(positions.length);
|
||||
|
||||
for (let i = 0; i < this._verts.length; i ++){
|
||||
this._verts[i] = new VertexPosition(positions[i]);
|
||||
}
|
||||
public setTexture(texture: egret.Texture): Mesh {
|
||||
this._mesh.texture = texture;
|
||||
this._mesh.$renderNode = new egret.sys.RenderNode();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setTriangles(triangles: number[]){
|
||||
this._primitiveCount = triangles.length / 3;
|
||||
this._triangles = triangles;
|
||||
return this;
|
||||
public reset() {
|
||||
}
|
||||
|
||||
public recalculateBounds(){
|
||||
this._topLeftVertPosition = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
|
||||
let max = new Vector2(Number.MIN_VALUE, Number.MIN_VALUE);
|
||||
|
||||
for (let i = 0; i < this._verts.length; i ++){
|
||||
this._topLeftVertPosition.x = Math.min(this._topLeftVertPosition.x, this._verts[i].position.x);
|
||||
this._topLeftVertPosition.y = Math.min(this._topLeftVertPosition.y, this._verts[i].position.y);
|
||||
max.x = Math.max(max.x, this._verts[i].position.x);
|
||||
max.y = Math.max(max.y, this._verts[i].position.y);
|
||||
render(camera: es.Camera) {
|
||||
}
|
||||
|
||||
this._width = max.x - this._topLeftVertPosition.x;
|
||||
this._height = max.y - this._topLeftVertPosition.y;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public render(){
|
||||
}
|
||||
}
|
||||
|
||||
class VertexPosition{
|
||||
public position: Vector2;
|
||||
|
||||
constructor(position: Vector2){
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,85 @@
|
||||
///<reference path="./Collider.ts" />
|
||||
class BoxCollider extends Collider {
|
||||
public get width(){
|
||||
return (this.shape as Box).width;
|
||||
}
|
||||
|
||||
public set width(value: number){
|
||||
this.setWidth(value);
|
||||
}
|
||||
|
||||
public setWidth(width: number): BoxCollider{
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width){
|
||||
box.updateBox(width, box.height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public get height(){
|
||||
return (this.shape as Box).height;
|
||||
}
|
||||
|
||||
public set height(value: number){
|
||||
this.setHeight(value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(){
|
||||
module es {
|
||||
export class BoxCollider extends Collider {
|
||||
/**
|
||||
* 零参数构造函数要求RenderableComponent在实体上,这样碰撞器可以在实体被添加到场景时调整自身的大小。
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// 我们在这里插入一个1x1框作为占位符,直到碰撞器在下一阵被添加到实体并可以获得更精确的自动调整大小数据
|
||||
this.shape = new Box(1, 1);
|
||||
this._colliderRequiresAutoSizing = true;
|
||||
}
|
||||
|
||||
public setSize(width: number, height: number){
|
||||
public get width() {
|
||||
return (this.shape as Box).width;
|
||||
}
|
||||
|
||||
public set width(value: number) {
|
||||
this.setWidth(value);
|
||||
}
|
||||
|
||||
public get height() {
|
||||
return (this.shape as Box).height;
|
||||
}
|
||||
|
||||
public set height(value: number) {
|
||||
this.setHeight(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的大小
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public setSize(width: number, height: number) {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width || height != box.height){
|
||||
if (width != box.width || height != box.height) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的宽度
|
||||
* @param width
|
||||
*/
|
||||
public setWidth(width: number): BoxCollider {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(width, box.height);
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BoxCollider的高度
|
||||
* @param height
|
||||
*/
|
||||
public setHeight(height: number) {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (height != box.height) {
|
||||
// 更新框,改变边界,如果我们需要更新物理系统中的边界
|
||||
box.updateBox(box.width, height);
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[BoxCollider: bounds: ${this.bounds}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
module es {
|
||||
export class CircleCollider extends Collider {
|
||||
/**
|
||||
* 创建一个有半径的圆
|
||||
*
|
||||
* @param radius
|
||||
*/
|
||||
constructor(radius?: number) {
|
||||
super();
|
||||
|
||||
if (radius)
|
||||
this._colliderRequiresAutoSizing = true;
|
||||
// 我们在这里插入一个1px的圆圈作为占位符
|
||||
// 直到碰撞器被添加到实体并可以获得更精确的自动调整大小数据的下一帧
|
||||
this.shape = new Circle(radius ? radius : 1);
|
||||
}
|
||||
|
||||
public get radius(): number {
|
||||
return (this.shape as Circle).radius;
|
||||
}
|
||||
|
||||
public set radius(value: number) {
|
||||
this.setRadius(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置圆的半径
|
||||
* @param radius
|
||||
*/
|
||||
public setRadius(radius: number): CircleCollider {
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let circle = this.shape as Circle;
|
||||
if (radius != circle.radius) {
|
||||
circle.radius = radius;
|
||||
circle._originalRadius = radius;
|
||||
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[CircleCollider: bounds: ${this.bounds}, radius: ${(this.shape as Circle).radius}]`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,63 @@
|
||||
abstract class Collider extends Component{
|
||||
module es {
|
||||
export abstract class Collider extends Component {
|
||||
/**
|
||||
* 对撞机的基本形状
|
||||
*/
|
||||
public shape: Shape;
|
||||
public physicsLayer = 1 << 0;
|
||||
/**
|
||||
* 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件
|
||||
*/
|
||||
public isTrigger: boolean;
|
||||
public registeredPhysicsBounds: Rectangle;
|
||||
public shouldColliderScaleAndRotationWithTransform = true;
|
||||
/**
|
||||
* 在处理冲突时,physicsLayer可以用作过滤器。Flags类有帮助位掩码的方法
|
||||
*/
|
||||
public physicsLayer = 1 << 0;
|
||||
/**
|
||||
* 碰撞器在使用移动器移动时应该碰撞的层
|
||||
* 默认为所有层
|
||||
*/
|
||||
public collidesWithLayers = Physics.allLayers;
|
||||
|
||||
/**
|
||||
* 如果为true,碰撞器将根据附加的变换缩放和旋转
|
||||
*/
|
||||
public shouldColliderScaleAndRotateWithTransform = true;
|
||||
/**
|
||||
* 这个对撞机在物理系统注册时的边界。
|
||||
* 存储这个允许我们始终能够安全地从物理系统中移除对撞机,即使它在试图移除它之前已经被移动了。
|
||||
*/
|
||||
public registeredPhysicsBounds: Rectangle = new Rectangle();
|
||||
public _localOffsetLength: number;
|
||||
public _isPositionDirty = true;
|
||||
public _isRotationDirty = true;
|
||||
protected _isParentEntityAddedToScene;
|
||||
public _isPositionDirty: boolean = true;
|
||||
public _isRotationDirty: boolean = true;
|
||||
protected _colliderRequiresAutoSizing;
|
||||
protected _localOffset: Vector2 = new Vector2(0, 0);
|
||||
/**
|
||||
* 标记来跟踪我们的实体是否被添加到场景中
|
||||
*/
|
||||
protected _isParentEntityAddedToScene;
|
||||
/**
|
||||
* 标记来记录我们是否注册了物理系统
|
||||
*/
|
||||
protected _isColliderRegistered;
|
||||
|
||||
/**
|
||||
* 镖师碰撞器的绝对位置
|
||||
*/
|
||||
public get absolutePosition(): Vector2 {
|
||||
return Vector2.add(this.entity.transform.position, this._localOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装变换。如果碰撞器没和实体一起旋转 则返回transform.rotation
|
||||
*/
|
||||
public get rotation(): number {
|
||||
if (this.shouldColliderScaleAndRotateWithTransform && this.entity)
|
||||
return this.entity.transform.rotation;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public get bounds(): Rectangle {
|
||||
if (this._isPositionDirty || this._isRotationDirty){
|
||||
if (this._isPositionDirty || this._isRotationDirty) {
|
||||
this.shape.recalculateBounds(this);
|
||||
this._isPositionDirty = this._isRotationDirty = false;
|
||||
}
|
||||
@@ -23,75 +65,78 @@ abstract class Collider extends Component{
|
||||
return this.shape.bounds;
|
||||
}
|
||||
|
||||
public get localOffset(){
|
||||
protected _localOffset: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器几何图形的最终位置。
|
||||
* 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转
|
||||
*/
|
||||
public get localOffset(): Vector2 {
|
||||
return this._localOffset;
|
||||
}
|
||||
|
||||
public set localOffset(value: Vector2){
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器几何图形的最终位置。
|
||||
* 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转
|
||||
* @param value
|
||||
*/
|
||||
public set localOffset(value: Vector2) {
|
||||
this.setLocalOffset(value);
|
||||
}
|
||||
|
||||
public setLocalOffset(offset: Vector2){
|
||||
if (this._localOffset != offset){
|
||||
/**
|
||||
* 将localOffset添加到实体。获取碰撞器的最终位置。
|
||||
* 这允许您向一个实体添加多个碰撞器并分别定位它们。
|
||||
* @param offset
|
||||
*/
|
||||
public setLocalOffset(offset: Vector2): Collider {
|
||||
if (this._localOffset != offset) {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
this._localOffset = offset;
|
||||
this._localOffsetLength = this._localOffset.length();
|
||||
this._isPositionDirty = true;
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public registerColliderWithPhysicsSystem(){
|
||||
if (this._isParentEntityAddedToScene && !this._isColliderRegistered){
|
||||
Physics.addCollider(this);
|
||||
this._isColliderRegistered = true;
|
||||
}
|
||||
/**
|
||||
* 如果为true,碰撞器将根据附加的变换缩放和旋转
|
||||
* @param shouldColliderScaleAndRotationWithTransform
|
||||
*/
|
||||
public setShouldColliderScaleAndRotateWithTransform(shouldColliderScaleAndRotationWithTransform: boolean): Collider {
|
||||
this.shouldColliderScaleAndRotateWithTransform = shouldColliderScaleAndRotationWithTransform;
|
||||
this._isPositionDirty = this._isRotationDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unregisterColliderWithPhysicsSystem(){
|
||||
if (this._isParentEntityAddedToScene && this._isColliderRegistered){
|
||||
Physics.removeCollider(this);
|
||||
}
|
||||
this._isColliderRegistered = false;
|
||||
}
|
||||
|
||||
public overlaps(other: Collider){
|
||||
return this.shape.overlaps(other.shape);
|
||||
}
|
||||
|
||||
public collidesWith(collider: Collider, motion: Vector2){
|
||||
let oldPosition = this.shape.position;
|
||||
this.shape.position = Vector2.add(this.shape.position, motion);
|
||||
|
||||
let result = this.shape.collidesWithShape(collider.shape);
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
let renderable = this.entity.getComponent<RenderableComponent>(RenderableComponent);
|
||||
if (renderable){
|
||||
let renderbaleBounds = renderable.bounds;
|
||||
if (renderable) {
|
||||
let renderableBounds = renderable.bounds;
|
||||
|
||||
let width = renderbaleBounds.width / this.entity.transform.scale.x;
|
||||
let height = renderbaleBounds.height / this.entity.transform.scale.y;
|
||||
|
||||
if (this instanceof BoxCollider){
|
||||
let boxCollider = this as BoxCollider;
|
||||
boxCollider.width = width;
|
||||
boxCollider.height = height;
|
||||
|
||||
this.localOffset = Vector2.subtract(renderbaleBounds.center, this.entity.transform.position);
|
||||
// 这里我们需要大小*反尺度,因为当我们自动调整碰撞器的大小时,它需要没有缩放的渲染
|
||||
let width = renderableBounds.width / this.entity.scale.x;
|
||||
let height = renderableBounds.height / this.entity.scale.y;
|
||||
// 圆碰撞器需要特别注意原点
|
||||
if (this instanceof CircleCollider) {
|
||||
this.radius = Math.max(width, height) * 0.5;
|
||||
} else {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
// 获取渲染的中心,将其转移到本地坐标,并使用它作为碰撞器的localOffset
|
||||
this.localOffset = Vector2.subtract(renderableBounds.center, this.entity.transform.position);
|
||||
} else {
|
||||
console.warn("Collider has no shape and no RenderableComponent. Can't figure out how to size it.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,20 +144,20 @@ abstract class Collider extends Component{
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
public onRemovedFromEntity(){
|
||||
public onRemovedFromEntity() {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
this._isParentEntityAddedToScene = false;
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: ComponentTransform){
|
||||
switch (comp){
|
||||
case ComponentTransform.position:
|
||||
public onEntityTransformChanged(comp: transform.Component) {
|
||||
switch (comp) {
|
||||
case transform.Component.position:
|
||||
this._isPositionDirty = true;
|
||||
break;
|
||||
case ComponentTransform.scale:
|
||||
case transform.Component.scale:
|
||||
this._isPositionDirty = true;
|
||||
break;
|
||||
case ComponentTransform.rotation:
|
||||
case transform.Component.rotation:
|
||||
this._isRotationDirty = true;
|
||||
break;
|
||||
}
|
||||
@@ -121,12 +166,73 @@ abstract class Collider extends Component{
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
public onEnabled(){
|
||||
public onEnabled() {
|
||||
this.registerColliderWithPhysicsSystem();
|
||||
this._isPositionDirty = this._isRotationDirty = true;
|
||||
}
|
||||
|
||||
public onDisabled(){
|
||||
public onDisabled() {
|
||||
this.unregisterColliderWithPhysicsSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 父实体会在不同的时间调用它(当添加到场景,启用,等等)
|
||||
*/
|
||||
public registerColliderWithPhysicsSystem() {
|
||||
// 如果在将我们添加到实体之前更改了origin等属性,则实体可以为null
|
||||
if (this._isParentEntityAddedToScene && !this._isColliderRegistered) {
|
||||
Physics.addCollider(this);
|
||||
this._isColliderRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 父实体会在不同的时候调用它(从场景中移除,禁用,等等)
|
||||
*/
|
||||
public unregisterColliderWithPhysicsSystem() {
|
||||
if (this._isParentEntityAddedToScene && this._isColliderRegistered) {
|
||||
Physics.removeCollider(this);
|
||||
}
|
||||
this._isColliderRegistered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查这个形状是否与物理系统中的其他对撞机重叠
|
||||
* @param other
|
||||
*/
|
||||
public overlaps(other: Collider): boolean {
|
||||
return this.shape.overlaps(other.shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查这个与运动应用的碰撞器(移动向量)是否与碰撞器碰撞。如果是这样,将返回true,并且结果将填充碰撞数据。
|
||||
* @param collider
|
||||
* @param motion
|
||||
* @param result
|
||||
*/
|
||||
public collidesWith(collider: Collider, motion: Vector2, result: CollisionResult): boolean {
|
||||
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
|
||||
let oldPosition = this.entity.position;
|
||||
this.entity.position = this.entity.position.add(motion);
|
||||
|
||||
let didCollide = this.shape.collidesWithShape(collider.shape, result);
|
||||
if (didCollide)
|
||||
result.collider = collider;
|
||||
|
||||
// 将图形位置返回到检查前的位置
|
||||
this.entity.position = oldPosition;
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
public clone(): Component {
|
||||
let collider = ObjectUtils.clone<Collider>(this);
|
||||
collider.entity = null;
|
||||
|
||||
if (this.shape)
|
||||
collider.shape = this.shape.clone();
|
||||
|
||||
return collider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
module es {
|
||||
/**
|
||||
* 多边形应该以顺时针方式定义
|
||||
*/
|
||||
export class PolygonCollider extends Collider {
|
||||
/**
|
||||
* 如果这些点没有居中,它们将以localOffset的差异为居中。
|
||||
* @param points
|
||||
*/
|
||||
constructor(points: Vector2[]) {
|
||||
super();
|
||||
|
||||
// 第一点和最后一点决不能相同。我们想要一个开放的多边形
|
||||
let isPolygonClosed = points[0] == points[points.length - 1];
|
||||
|
||||
// 最后一个移除
|
||||
if (isPolygonClosed)
|
||||
points.splice(points.length - 1, 1);
|
||||
|
||||
let center = Polygon.findPolygonCenter(points);
|
||||
this.setLocalOffset(center);
|
||||
Polygon.recenterPolygonVerts(points);
|
||||
this.shape = new Polygon(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,23 @@
|
||||
interface ITriggerListener {
|
||||
module es {
|
||||
/**
|
||||
* 当添加到组件时,每当实体上的冲突器与另一个组件重叠/退出时,将调用这些方法。
|
||||
* ITriggerListener方法将在实现接口的触发器实体上的任何组件上调用。
|
||||
* 注意,这个接口只与Mover类一起工作
|
||||
*/
|
||||
export interface ITriggerListener {
|
||||
/**
|
||||
* 当碰撞器与触发碰撞器相交时调用。这是在触发碰撞器和触发碰撞器上调用的。
|
||||
* 移动必须由Mover/ProjectileMover方法处理,以使其自动工作。
|
||||
* @param other
|
||||
* @param local
|
||||
*/
|
||||
onTriggerEnter(other: Collider, local: Collider);
|
||||
|
||||
/**
|
||||
* 当另一个碰撞器离开触发碰撞器时调用
|
||||
* @param other
|
||||
* @param local
|
||||
*/
|
||||
onTriggerExit(other: Collider, local: Collider);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,56 @@
|
||||
class Mover extends Component {
|
||||
module es {
|
||||
/**
|
||||
* 辅助类说明了一种处理移动的方法,它考虑了包括触发器在内的所有冲突。
|
||||
* ITriggerListener接口用于管理对移动过程中违反的任何触发器的回调。
|
||||
* 一个物体只能通过移动器移动。要正确报告触发器的move方法。
|
||||
*
|
||||
* 请注意,多个移动者相互交互将多次调用ITriggerListener。
|
||||
*/
|
||||
export class Mover extends Component {
|
||||
private _triggerHelper: ColliderTriggerHelper;
|
||||
|
||||
public onAddedToEntity(){
|
||||
public onAddedToEntity() {
|
||||
this._triggerHelper = new ColliderTriggerHelper(this.entity);
|
||||
}
|
||||
|
||||
public calculateMovement(motion: Vector2){
|
||||
let collisionResult = new CollisionResult();
|
||||
|
||||
if (!this.entity.getComponent(Collider) || !this._triggerHelper){
|
||||
return null;
|
||||
/**
|
||||
* 计算修改运动矢量的运动,以考虑移动时可能发生的碰撞
|
||||
* @param motion
|
||||
* @param collisionResult
|
||||
*/
|
||||
public calculateMovement(motion: Vector2, collisionResult: CollisionResult): boolean {
|
||||
if (!this.entity.getComponent(Collider) || !this._triggerHelper) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移动所有的非触发碰撞器并获得最近的碰撞
|
||||
let colliders: Collider[] = this.entity.getComponents(Collider);
|
||||
for (let i = 0; i < colliders.length; i ++){
|
||||
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);
|
||||
|
||||
for (let j = 0; j < neighbors.length; j ++){
|
||||
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);
|
||||
let _internalcollisionResult: CollisionResult = new CollisionResult();
|
||||
if (collider.collidesWith(neighbor, motion, _internalcollisionResult)) {
|
||||
// 如果碰撞 则退回之前的移动量
|
||||
motion = motion.subtract(_internalcollisionResult.minimumTranslationVector);
|
||||
|
||||
if (_internalcollisionResult.collider){
|
||||
// 如果我们碰到多个对象,为了简单起见,只取第一个。
|
||||
if (_internalcollisionResult.collider != null) {
|
||||
collisionResult = _internalcollisionResult;
|
||||
}
|
||||
}
|
||||
@@ -42,21 +59,31 @@ class Mover extends Component {
|
||||
|
||||
ListPool.free(colliders);
|
||||
|
||||
return collisionResult;
|
||||
return collisionResult.collider != null;
|
||||
}
|
||||
|
||||
public applyMovement(motion: Vector2){
|
||||
this.entity.transform.position = Vector2.add(this.entity.transform.position, motion);
|
||||
/**
|
||||
* 将calculatemomovement应用到实体并更新triggerHelper
|
||||
* @param motion
|
||||
*/
|
||||
public applyMovement(motion: Vector2) {
|
||||
// 移动实体到它的新位置,如果我们有一个碰撞,否则移动全部数量。当碰撞发生时,运动被更新
|
||||
this.entity.position = Vector2.add(this.entity.position, motion);
|
||||
|
||||
// 对所有是触发器的碰撞器与所有宽相位碰撞器进行重叠检查。任何重叠都会导致触发事件。
|
||||
if (this._triggerHelper)
|
||||
this._triggerHelper.update();
|
||||
}
|
||||
|
||||
public move(motion: Vector2){
|
||||
let collisionResult = this.calculateMovement(motion);
|
||||
|
||||
/**
|
||||
* 通过调用calculateMovement和applyMovement来移动考虑碰撞的实体;
|
||||
* @param motion
|
||||
* @param collisionResult
|
||||
*/
|
||||
public move(motion: Vector2, collisionResult: CollisionResult) {
|
||||
this.calculateMovement(motion, collisionResult);
|
||||
this.applyMovement(motion);
|
||||
|
||||
return collisionResult;
|
||||
return collisionResult.collider != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
module es {
|
||||
/**
|
||||
* 只向itriggerlistener报告冲突的移动器
|
||||
* 该对象将始终移动完整的距离
|
||||
*/
|
||||
export 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.length; i++) {
|
||||
let neighbor = neighbors[i];
|
||||
if (this._collider.overlaps(neighbor) && neighbor.enabled) {
|
||||
didCollide = true;
|
||||
this.notifyTriggerListeners(this._collider, neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
return didCollide;
|
||||
}
|
||||
|
||||
private notifyTriggerListeners(self: Collider, other: Collider) {
|
||||
// 通知我们重叠的碰撞器实体上的任何侦听器
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
class PolygonMesh extends Mesh {
|
||||
constructor(points: Vector2[], arePointsCCW: boolean = true){
|
||||
super();
|
||||
|
||||
let triangulator = new Triangulator();
|
||||
triangulator.triangulate(points, arePointsCCW);
|
||||
|
||||
this.setVertPosition(points);
|
||||
this.setTriangles(triangulator.triangleIndices);
|
||||
this.recalculateBounds();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module es {
|
||||
/** 回收实例的组件类型。 */
|
||||
export abstract class PooledComponent extends Component {
|
||||
public abstract reset();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,73 @@
|
||||
/**
|
||||
///<reference path="./PooledComponent.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 所有可渲染组件的基类
|
||||
*/
|
||||
abstract class RenderableComponent extends Component {
|
||||
private _isVisible: boolean;
|
||||
private _areBoundsDirty = true;
|
||||
private _bounds: Rectangle;
|
||||
private _localOffset: Vector2;
|
||||
export abstract class RenderableComponent extends Component implements IRenderable {
|
||||
/**
|
||||
* 用于装载egret显示对象
|
||||
*/
|
||||
public displayObject: egret.DisplayObject = new egret.DisplayObject();
|
||||
/**
|
||||
* 用于着色器处理精灵
|
||||
*/
|
||||
public color: number = 0x000000;
|
||||
protected _areBoundsDirty = true;
|
||||
|
||||
public get width(){
|
||||
return this.getWidth();
|
||||
}
|
||||
|
||||
public get height(){
|
||||
return this.getHeight();
|
||||
}
|
||||
|
||||
public get isVisible(){
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
public set isVisible(value: boolean){
|
||||
this._isVisible = value;
|
||||
|
||||
if (this._isVisible)
|
||||
this.onBecameVisible();
|
||||
else
|
||||
this.onBecameInvisible();
|
||||
}
|
||||
|
||||
public get bounds(): Rectangle{
|
||||
return this.getBounds();
|
||||
}
|
||||
|
||||
protected getWidth(){
|
||||
/**
|
||||
* renderableComponent的宽度
|
||||
* 如果你不重写bounds属性则需要实现这个
|
||||
*/
|
||||
public get width() {
|
||||
return this.bounds.width;
|
||||
}
|
||||
|
||||
protected getHeight(){
|
||||
/**
|
||||
* renderableComponent的高度
|
||||
* 如果你不重写bounds属性则需要实现这个
|
||||
*/
|
||||
public get height() {
|
||||
return this.bounds.height;
|
||||
}
|
||||
|
||||
protected getBounds(){
|
||||
if (this._areBoundsDirty){
|
||||
this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, new Vector2(0, 0),
|
||||
protected _localOffset: Vector2 = Vector2.zero;
|
||||
|
||||
/**
|
||||
* 从父实体的偏移量。用于向需要特定定位的实体
|
||||
*/
|
||||
public get localOffset(): Vector2 {
|
||||
return this._localOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从父实体的偏移量。用于向需要特定定位的实体
|
||||
* @param value
|
||||
*/
|
||||
public set localOffset(value: Vector2) {
|
||||
this.setLocalOffset(value);
|
||||
}
|
||||
|
||||
protected _renderLayer: number = 0;
|
||||
|
||||
/**
|
||||
* 较低的渲染层在前面,较高的在后面
|
||||
*/
|
||||
public get renderLayer(): number {
|
||||
return this._renderLayer;
|
||||
}
|
||||
|
||||
public set renderLayer(value: number) {
|
||||
this.setRenderLayer(value);
|
||||
}
|
||||
|
||||
protected _bounds: Rectangle = new Rectangle();
|
||||
|
||||
/**
|
||||
* 这个物体的AABB, 用于相机剔除
|
||||
*/
|
||||
public get bounds(): Rectangle {
|
||||
if (this._areBoundsDirty) {
|
||||
this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, Vector2.zero,
|
||||
this.entity.transform.scale, this.entity.transform.rotation, this.width, this.height);
|
||||
this._areBoundsDirty = false;
|
||||
}
|
||||
@@ -50,12 +75,118 @@ abstract class RenderableComponent extends Component {
|
||||
return this._bounds;
|
||||
}
|
||||
|
||||
protected onBecameVisible(){}
|
||||
private _isVisible: boolean;
|
||||
|
||||
protected onBecameInvisible(){}
|
||||
/**
|
||||
* 可渲染的可见性。状态的改变会调用onBecameVisible/onBecameInvisible方法
|
||||
*/
|
||||
public get isVisible() {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
public isVisibleFromCamera(camera: Camera): boolean{
|
||||
/**
|
||||
* 可渲染的可见性。状态的改变会调用onBecameVisible/onBecameInvisible方法
|
||||
* @param value
|
||||
*/
|
||||
public set isVisible(value: boolean) {
|
||||
if (this._isVisible != value) {
|
||||
this._isVisible = value;
|
||||
|
||||
if (this._isVisible)
|
||||
this.onBecameVisible();
|
||||
else
|
||||
this.onBecameInvisible();
|
||||
}
|
||||
}
|
||||
|
||||
public onEntityTransformChanged(comp: transform.Component) {
|
||||
this._areBoundsDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由渲染器调用。可以使用摄像机进行剔除
|
||||
* @param camera
|
||||
*/
|
||||
public abstract render(camera: Camera);
|
||||
|
||||
/**
|
||||
* 如果renderableComponent的边界与camera.bounds相交 返回true
|
||||
* 用于处理isVisible标志的状态开关
|
||||
* 在渲染方法中使用这个方法来决定是否渲染
|
||||
* @param camera
|
||||
*/
|
||||
public isVisibleFromCamera(camera: Camera): boolean {
|
||||
this.isVisible = camera.bounds.intersects(this.bounds);
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 较低的渲染层在前面,较高的在后面
|
||||
* @param renderLayer
|
||||
*/
|
||||
public setRenderLayer(renderLayer: number): RenderableComponent {
|
||||
if (renderLayer != this._renderLayer) {
|
||||
let oldRenderLayer = this._renderLayer;
|
||||
this._renderLayer = renderLayer;
|
||||
|
||||
// 如果该组件拥有一个实体,那么是由ComponentList管理,需要通知它改变了渲染层
|
||||
if (this.entity && this.entity.scene)
|
||||
this.entity.scene.renderableComponents.updateRenderableRenderLayer(this, oldRenderLayer, this._renderLayer);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于着色器处理精灵
|
||||
* @param color
|
||||
*/
|
||||
public setColor(color: number): RenderableComponent {
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从父实体的偏移量。用于向需要特定定位的实体
|
||||
* @param offset
|
||||
*/
|
||||
public setLocalOffset(offset: Vector2): RenderableComponent {
|
||||
if (this._localOffset != offset) {
|
||||
this._localOffset = offset;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行状态同步
|
||||
*/
|
||||
public sync(camera: Camera) {
|
||||
this.displayObject.x = this.entity.position.x + this.localOffset.x - camera.position.x + camera.origin.x;
|
||||
this.displayObject.y = this.entity.position.y + this.localOffset.y - camera.position.y + camera.origin.y;
|
||||
this.displayObject.scaleX = this.entity.scale.x;
|
||||
this.displayObject.scaleY = this.entity.scale.y;
|
||||
this.displayObject.rotation = this.entity.rotation;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[RenderableComponent] renderLayer: ${this.renderLayer}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当renderableComponent进入相机框架时调用
|
||||
* 如果渲染器不适用isVisibleFromCamera进行剔除检查 这些方法不会被调用
|
||||
*/
|
||||
protected onBecameVisible() {
|
||||
this.displayObject.visible = this.isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当renderableComponent离开相机框架时调用
|
||||
* 如果渲染器不适用isVisibleFromCamera进行剔除检查 这些方法不会被调用
|
||||
*/
|
||||
protected onBecameInvisible() {
|
||||
this.displayObject.visible = this.isVisible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
///<reference path="./TiledSpriteRenderer.ts"/>
|
||||
module es {
|
||||
import Bitmap = egret.Bitmap;
|
||||
|
||||
export class ScrollingSpriteRenderer extends TiledSpriteRenderer {
|
||||
/**
|
||||
* x自动滚动速度(以像素/s为单位)
|
||||
*/
|
||||
public scrollSpeedX = 15;
|
||||
/**
|
||||
* 自动滚动的y速度(以像素/s为单位)
|
||||
*/
|
||||
public scroolSpeedY = 0;
|
||||
|
||||
public get textureScale(): Vector2 {
|
||||
return this._textureScale;
|
||||
}
|
||||
|
||||
public set textureScale(value: Vector2){
|
||||
this._textureScale = value;
|
||||
|
||||
// 重新计算我们的inverseTextureScale和源矩形大小
|
||||
this._inverseTexScale = new Vector2(1 / this._textureScale.x, 1 / this._textureScale.y);
|
||||
}
|
||||
|
||||
public set scrollWidth(value: number){
|
||||
this._scrollWidth = value;
|
||||
}
|
||||
|
||||
public get scrollWidth(){
|
||||
return this._scrollWidth;
|
||||
}
|
||||
|
||||
public set scrollHeight(value: number){
|
||||
this._scrollHeight = value;
|
||||
}
|
||||
|
||||
public get scrollHeight(){
|
||||
return this._scrollHeight;
|
||||
}
|
||||
|
||||
private _scrollX = 0;
|
||||
private _scrollY = 0;
|
||||
private _scrollWidth = 0;
|
||||
private _scrollHeight = 0;
|
||||
|
||||
constructor(sprite: Sprite) {
|
||||
super(sprite);
|
||||
|
||||
this._scrollWidth = this.width;
|
||||
this._scrollHeight = this.height;
|
||||
}
|
||||
|
||||
public update() {
|
||||
if (!this.sprite)
|
||||
return;
|
||||
|
||||
this._scrollX += this.scrollSpeedX * Time.deltaTime;
|
||||
this._scrollY += this.scroolSpeedY * Time.deltaTime;
|
||||
|
||||
this._sourceRect.x = this._scrollX;
|
||||
this._sourceRect.y = this._scrollY;
|
||||
this._sourceRect.width = this._scrollWidth + Math.abs(this._scrollX);
|
||||
this._sourceRect.height = this._scrollHeight + Math.abs(this._scrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
module es {
|
||||
export class Sprite {
|
||||
public texture2D: egret.Texture;
|
||||
public readonly sourceRect: Rectangle;
|
||||
public readonly center: Vector2;
|
||||
public origin: Vector2;
|
||||
public readonly uvs: Rectangle = new Rectangle();
|
||||
|
||||
constructor(texture: egret.Texture,
|
||||
sourceRect: Rectangle = new Rectangle(0, 0, texture.textureWidth, texture.textureHeight),
|
||||
origin: Vector2 = sourceRect.getHalfSize()) {
|
||||
this.texture2D = texture;
|
||||
this.sourceRect = sourceRect;
|
||||
this.center = new Vector2(sourceRect.width * 0.5, sourceRect.height * 0.5);
|
||||
this.origin = origin;
|
||||
|
||||
let inverseTexW = 1 / texture.textureWidth;
|
||||
let inverseTexH = 1 / texture.textureHeight;
|
||||
|
||||
this.uvs.x = sourceRect.x * inverseTexW;
|
||||
this.uvs.y = sourceRect.y * inverseTexH;
|
||||
this.uvs.width = sourceRect.width * inverseTexW;
|
||||
this.uvs.height = sourceRect.height * inverseTexH;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module es {
|
||||
export class SpriteAnimation {
|
||||
public readonly sprites: Sprite[];
|
||||
public readonly frameRate: number;
|
||||
|
||||
constructor(sprites: Sprite[], frameRate: number) {
|
||||
this.sprites = sprites;
|
||||
this.frameRate = frameRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
///<reference path="./SpriteRenderer.ts" />
|
||||
module es {
|
||||
export 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,
|
||||
}
|
||||
|
||||
export enum State {
|
||||
none,
|
||||
running,
|
||||
paused,
|
||||
completed,
|
||||
}
|
||||
|
||||
export class SpriteAnimator extends SpriteRenderer {
|
||||
/**
|
||||
* 在动画完成时触发,包括动画名称
|
||||
*/
|
||||
public onAnimationCompletedEvent: (string) => {};
|
||||
/**
|
||||
* 动画播放速度
|
||||
*/
|
||||
public speed = 1;
|
||||
/**
|
||||
* 动画的当前状态
|
||||
*/
|
||||
public animationState = State.none;
|
||||
/**
|
||||
* 当前动画
|
||||
*/
|
||||
public currentAnimation: SpriteAnimation;
|
||||
/**
|
||||
* 当前动画的名称
|
||||
*/
|
||||
public currentAnimationName: string;
|
||||
/**
|
||||
* 当前动画的精灵数组中当前帧的索引
|
||||
*/
|
||||
public currentFrame: number;
|
||||
public _elapsedTime: number = 0;
|
||||
public _loopMode: LoopMode;
|
||||
|
||||
constructor(sprite?: Sprite) {
|
||||
super(sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前动画是否正在运行
|
||||
*/
|
||||
public get isRunning(): boolean {
|
||||
return this.animationState == State.running;
|
||||
}
|
||||
|
||||
private _animations: Map<string, SpriteAnimation> = new Map<string, SpriteAnimation>();
|
||||
|
||||
/** 提供对可用动画列表的访问 */
|
||||
public get animations() {
|
||||
return this._animations;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Once和PingPongOnce完成后重置为Time = 0
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,131 @@
|
||||
class SpriteRenderer extends RenderableComponent {
|
||||
private _sprite: egret.DisplayObject;
|
||||
private _origin: Vector2;
|
||||
module es {
|
||||
import Bitmap = egret.Bitmap;
|
||||
|
||||
public get sprite(){
|
||||
export class SpriteRenderer extends RenderableComponent {
|
||||
constructor(sprite: Sprite | egret.Texture = null) {
|
||||
super();
|
||||
if (sprite instanceof Sprite)
|
||||
this.setSprite(sprite);
|
||||
else if (sprite instanceof egret.Texture)
|
||||
this.setSprite(new Sprite(sprite));
|
||||
}
|
||||
|
||||
public get bounds() {
|
||||
if (this._areBoundsDirty) {
|
||||
if (this._sprite) {
|
||||
this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, this._origin,
|
||||
this.entity.transform.scale, this.entity.transform.rotation, this._sprite.sourceRect.width,
|
||||
this._sprite.sourceRect.height);
|
||||
this._areBoundsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this._bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用归一化方法设置原点
|
||||
* x/y 均为 0-1
|
||||
*/
|
||||
public get originNormalized(): Vector2 {
|
||||
return new Vector2(this._origin.x / this.width * this.entity.transform.scale.x,
|
||||
this._origin.y / this.height * this.entity.transform.scale.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用归一化方法设置原点
|
||||
* x/y 均为 0-1
|
||||
* @param value
|
||||
*/
|
||||
public set originNormalized(value: Vector2) {
|
||||
this.setOrigin(new Vector2(value.x * this.width / this.entity.transform.scale.x,
|
||||
value.y * this.height / this.entity.transform.scale.y));
|
||||
}
|
||||
|
||||
protected _origin: Vector2;
|
||||
|
||||
/**
|
||||
* 精灵的原点。这是在设置精灵时自动设置的
|
||||
*/
|
||||
public get origin(): Vector2 {
|
||||
return this._origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 精灵的原点。这是在设置精灵时自动设置的
|
||||
* @param value
|
||||
*/
|
||||
public set origin(value: Vector2) {
|
||||
this.setOrigin(value);
|
||||
}
|
||||
|
||||
protected _sprite: Sprite;
|
||||
|
||||
/**
|
||||
* 应该由这个精灵显示的精灵
|
||||
* 当设置时,精灵的原点也被设置为精灵的origin
|
||||
*/
|
||||
public get sprite(): Sprite {
|
||||
return this._sprite;
|
||||
}
|
||||
|
||||
public set sprite(value: egret.DisplayObject){
|
||||
/**
|
||||
* 应该由这个精灵显示的精灵
|
||||
* 当设置时,精灵的原点也被设置为精灵的origin
|
||||
* @param value
|
||||
*/
|
||||
public set sprite(value: Sprite) {
|
||||
this.setSprite(value);
|
||||
}
|
||||
|
||||
public setSprite(sprite: egret.DisplayObject): SpriteRenderer{
|
||||
/**
|
||||
* 设置精灵并更新精灵的原点以匹配sprite.origin
|
||||
* @param sprite
|
||||
*/
|
||||
public setSprite(sprite: Sprite): SpriteRenderer {
|
||||
this._sprite = sprite;
|
||||
if (this._sprite)
|
||||
this._origin = new Vector2(this._sprite.anchorOffsetX, this._sprite.anchorOffsetY);
|
||||
if (this._sprite) {
|
||||
this._origin = this._sprite.origin;
|
||||
this.displayObject.anchorOffsetX = this._origin.x;
|
||||
this.displayObject.anchorOffsetY = this._origin.y;
|
||||
}
|
||||
this.displayObject = new Bitmap(sprite.texture2D);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
/**
|
||||
* 设置可渲染的原点
|
||||
* @param origin
|
||||
*/
|
||||
public setOrigin(origin: Vector2): SpriteRenderer {
|
||||
if (this._origin != origin) {
|
||||
this._origin = origin;
|
||||
this.displayObject.anchorOffsetX = this._origin.x;
|
||||
this.displayObject.anchorOffsetY = this._origin.y;
|
||||
this._areBoundsDirty = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用归一化方法设置原点
|
||||
* x/y 均为 0-1
|
||||
* @param value
|
||||
*/
|
||||
public setOriginNormalized(value: Vector2): SpriteRenderer {
|
||||
this.setOrigin(new Vector2(value.x * this.width / this.entity.transform.scale.x,
|
||||
value.y * this.height / this.entity.transform.scale.y));
|
||||
return this;
|
||||
}
|
||||
|
||||
public render(camera: Camera) {
|
||||
this.sync(camera);
|
||||
|
||||
this.displayObject.x = this.entity.position.x + this.localOffset.x - camera.position.x + camera.origin.x;
|
||||
this.displayObject.y = this.entity.position.y + this.localOffset.y - camera.position.y + camera.origin.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
///<reference path="./SpriteRenderer.ts" />
|
||||
module es {
|
||||
import Bitmap = egret.Bitmap;
|
||||
import RenderTexture = egret.RenderTexture;
|
||||
|
||||
/**
|
||||
* 滚动由两张图片组合而成
|
||||
*/
|
||||
export class TiledSpriteRenderer extends SpriteRenderer {
|
||||
public get bounds(): Rectangle {
|
||||
if (this._areBoundsDirty){
|
||||
if (this._sprite){
|
||||
this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, this._origin,
|
||||
this.entity.transform.scale, this.entity.transform.rotation, this.width, this.height);
|
||||
this._areBoundsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this._bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理滚动的x值
|
||||
*/
|
||||
public get scrollX() {
|
||||
return this._sourceRect.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理滚动的x值
|
||||
* @param value
|
||||
*/
|
||||
public set scrollX(value: number) {
|
||||
this._sourceRect.x = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理滚动的y值
|
||||
*/
|
||||
public get scrollY() {
|
||||
return this._sourceRect.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理滚动的y值
|
||||
* @param value
|
||||
*/
|
||||
public set scrollY(value: number) {
|
||||
this._sourceRect.y = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理比例尺
|
||||
*/
|
||||
public get textureScale(): Vector2 {
|
||||
return this._textureScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理比例尺
|
||||
* @param value
|
||||
*/
|
||||
public set textureScale(value: Vector2) {
|
||||
this._textureScale = value;
|
||||
|
||||
// 重新计算我们的inverseTextureScale和源矩形大小
|
||||
this._inverseTexScale = new Vector2(1 / this._textureScale.x, 1 / this._textureScale.y);
|
||||
this._sourceRect.width = this._sprite.sourceRect.width * this._inverseTexScale.x;
|
||||
this._sourceRect.height = this._sprite.sourceRect.height * this._inverseTexScale.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖宽度值,这样TiledSprite可以有一个独立于其纹理的宽度
|
||||
*/
|
||||
public get width(): number{
|
||||
return this._sourceRect.width;
|
||||
}
|
||||
|
||||
public set width(value: number) {
|
||||
this._areBoundsDirty = true;
|
||||
this._sourceRect.width = value;
|
||||
}
|
||||
|
||||
public get height(): number {
|
||||
return this._sourceRect.height;
|
||||
}
|
||||
|
||||
public set height(value: number) {
|
||||
this._areBoundsDirty = true;
|
||||
this._sourceRect.height = value;
|
||||
}
|
||||
|
||||
public get gapXY(): Vector2{
|
||||
return new Vector2(this._gapX, this._gapY);
|
||||
}
|
||||
|
||||
public set gapXY(value: Vector2){
|
||||
this._gapX = value.x;
|
||||
this._gapY = value.y;
|
||||
|
||||
let renderTexture = new RenderTexture();
|
||||
let newRectangle = this.sprite.sourceRect;
|
||||
newRectangle.x = 0;
|
||||
newRectangle.y = 0;
|
||||
newRectangle.width += this._gapX;
|
||||
newRectangle.height += this._gapY;
|
||||
renderTexture.drawToTexture(this.displayObject, newRectangle);
|
||||
|
||||
if (!this.displayObject){
|
||||
this.displayObject = new Bitmap(renderTexture);
|
||||
}else{
|
||||
(this.displayObject as Bitmap).texture = renderTexture;
|
||||
}
|
||||
}
|
||||
|
||||
protected _sourceRect: Rectangle;
|
||||
protected _textureScale = Vector2.one;
|
||||
protected _inverseTexScale = Vector2.one;
|
||||
private _gapX = 0;
|
||||
private _gapY = 0;
|
||||
|
||||
constructor(sprite: Sprite) {
|
||||
super(sprite);
|
||||
|
||||
this._sourceRect = sprite.sourceRect;
|
||||
let bitmap = this.displayObject as Bitmap;
|
||||
bitmap.$fillMode = egret.BitmapFillMode.REPEAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置间隔
|
||||
* @param value
|
||||
*/
|
||||
public setGapXY(value: Vector2): TiledSpriteRenderer {
|
||||
this.gapXY = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public render(camera: es.Camera) {
|
||||
super.render(camera);
|
||||
|
||||
let bitmap = this.displayObject as Bitmap;
|
||||
bitmap.width = this.width;
|
||||
bitmap.height = this.height;
|
||||
bitmap.scrollRect = this._sourceRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
module es {
|
||||
/**
|
||||
* 全局核心类
|
||||
*/
|
||||
export class Core extends egret.DisplayObjectContainer {
|
||||
/**
|
||||
* 核心发射器。只发出核心级别的事件
|
||||
*/
|
||||
public static emitter: Emitter<CoreEvents>;
|
||||
/**
|
||||
* 全局访问图形设备
|
||||
*/
|
||||
public static graphicsDevice: GraphicsDevice;
|
||||
/**
|
||||
* 全局内容管理器加载任何应该停留在场景之间的资产
|
||||
*/
|
||||
public static content: ContentManager;
|
||||
/**
|
||||
* 简化对内部类的全局内容实例的访问
|
||||
*/
|
||||
public static _instance: Core;
|
||||
public _nextScene: Scene;
|
||||
public _sceneTransition: SceneTransition;
|
||||
/**
|
||||
* 全局访问系统
|
||||
*/
|
||||
public _globalManagers: GlobalManager[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
Core._instance = this;
|
||||
Core.emitter = new Emitter<CoreEvents>();
|
||||
Core.content = new ContentManager();
|
||||
|
||||
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供对单例/游戏实例的访问
|
||||
* @constructor
|
||||
*/
|
||||
public static get Instance() {
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
public _scene: Scene;
|
||||
|
||||
/**
|
||||
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
|
||||
*/
|
||||
public static get scene() {
|
||||
if (!this._instance)
|
||||
return null;
|
||||
return this._instance._scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前活动的场景。注意,如果设置了该设置,在更新结束之前场景实际上不会改变
|
||||
* @param value
|
||||
*/
|
||||
public static set scene(value: Scene) {
|
||||
if (!value) {
|
||||
console.error("场景不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._instance._scene == null) {
|
||||
this._instance._scene = value;
|
||||
this._instance.addChild(value);
|
||||
this._instance._scene.begin();
|
||||
Core.Instance.onSceneChanged();
|
||||
} else {
|
||||
this._instance._nextScene = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时运行SceneTransition,允许一个场景过渡到另一个平滑的自定义效果。
|
||||
* @param sceneTransition
|
||||
*/
|
||||
public static startSceneTransition<T extends SceneTransition>(sceneTransition: T): T {
|
||||
if (this._instance._sceneTransition) {
|
||||
console.warn("在前一个场景完成之前,不能开始一个新的场景转换。");
|
||||
return;
|
||||
}
|
||||
|
||||
this._instance._sceneTransition = sceneTransition;
|
||||
return sceneTransition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个全局管理器对象,它的更新方法将调用场景前的每一帧。
|
||||
* @param manager
|
||||
*/
|
||||
public static registerGlobalManager(manager: es.GlobalManager) {
|
||||
this._instance._globalManagers.push(manager);
|
||||
manager.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除全局管理器对象
|
||||
* @param manager
|
||||
*/
|
||||
public static unregisterGlobalManager(manager: es.GlobalManager) {
|
||||
this._instance._globalManagers.remove(manager);
|
||||
manager.enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为T的全局管理器
|
||||
* @param type
|
||||
*/
|
||||
public static getGlobalManager<T extends es.GlobalManager>(type): T {
|
||||
for (let i = 0; i < this._instance._globalManagers.length; i++) {
|
||||
if (this._instance._globalManagers[i] instanceof type)
|
||||
return this._instance._globalManagers[i] as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public onOrientationChanged() {
|
||||
Core.emitter.emit(CoreEvents.OrientationChanged);
|
||||
}
|
||||
|
||||
public async draw() {
|
||||
if (this._sceneTransition) {
|
||||
this._sceneTransition.preRender();
|
||||
|
||||
// 如果我们有场景转换的特殊处理。我们要么渲染场景过渡,要么渲染场景
|
||||
if (this._scene && !this._sceneTransition.hasPreviousSceneRender) {
|
||||
this._scene.render();
|
||||
this._scene.postRender();
|
||||
await this._sceneTransition.onBeginTransition();
|
||||
} else if (this._sceneTransition) {
|
||||
if (this._scene && this._sceneTransition.isNewSceneLoaded) {
|
||||
this._scene.render();
|
||||
this._scene.postRender();
|
||||
}
|
||||
|
||||
this._sceneTransition.render();
|
||||
}
|
||||
} else if (this._scene) {
|
||||
this._scene.render();
|
||||
|
||||
Debug.render();
|
||||
|
||||
// 如果我们没有一个活跃的场景转换,就像平常一样渲染
|
||||
this._scene.postRender();
|
||||
}
|
||||
}
|
||||
|
||||
public startDebugUpdate() {
|
||||
TimeRuler.Instance.startFrame();
|
||||
TimeRuler.Instance.beginMark("update", 0x00FF00);
|
||||
}
|
||||
|
||||
public endDebugUpdate() {
|
||||
TimeRuler.Instance.endMark("update");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一个场景结束后,下一个场景开始之前调用
|
||||
*/
|
||||
public onSceneChanged() {
|
||||
Core.emitter.emit(CoreEvents.SceneChanged);
|
||||
Time.sceneChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当屏幕大小发生改变时调用
|
||||
*/
|
||||
protected onGraphicsDeviceReset() {
|
||||
Core.emitter.emit(CoreEvents.GraphicsDeviceReset);
|
||||
}
|
||||
|
||||
protected initialize() {
|
||||
}
|
||||
|
||||
protected async update() {
|
||||
// this.startDebugUpdate();
|
||||
|
||||
// 更新我们所有的系统管理器
|
||||
Time.update(egret.getTimer());
|
||||
|
||||
if (this._scene) {
|
||||
for (let i = this._globalManagers.length - 1; i >= 0; i--) {
|
||||
if (this._globalManagers[i].enabled)
|
||||
this._globalManagers[i].update();
|
||||
}
|
||||
|
||||
// 仔细阅读:
|
||||
// 当场景转换发生时,我们不会更新场景
|
||||
// -除非是不改变场景的场景转换(没有理由不更新)
|
||||
// -或者它是一个已经切换到新场景的场景转换(新场景需要做它自己的事情)
|
||||
if (!this._sceneTransition ||
|
||||
(this._sceneTransition && (!this._sceneTransition.loadsNewScene || this._sceneTransition.isNewSceneLoaded))) {
|
||||
this._scene.update();
|
||||
}
|
||||
|
||||
if (this._nextScene) {
|
||||
this.removeChild(this._scene);
|
||||
this._scene.end();
|
||||
|
||||
this._scene = this._nextScene;
|
||||
this._nextScene = null;
|
||||
this.onSceneChanged();
|
||||
|
||||
this.addChild(this._scene);
|
||||
await this._scene.begin();
|
||||
}
|
||||
}
|
||||
|
||||
// this.endDebugUpdate();
|
||||
|
||||
await this.draw();
|
||||
}
|
||||
|
||||
private onAddToStage() {
|
||||
Core.graphicsDevice = new GraphicsDevice();
|
||||
|
||||
this.addEventListener(egret.Event.RESIZE, this.onGraphicsDeviceReset, this);
|
||||
this.addEventListener(egret.StageOrientationEvent.ORIENTATION_CHANGE, this.onOrientationChanged, this);
|
||||
this.addEventListener(egret.Event.ENTER_FRAME, this.update, this);
|
||||
|
||||
Input.initialize();
|
||||
this.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
module es {
|
||||
export enum CoreEvents {
|
||||
/**
|
||||
* 在图形设备重置时触发。当这种情况发生时,任何渲染目标或其他内容的VRAM将被擦除,需要重新生成
|
||||
*/
|
||||
GraphicsDeviceReset,
|
||||
/**
|
||||
* 当场景发生变化时触发
|
||||
*/
|
||||
SceneChanged,
|
||||
/**
|
||||
* 当设备方向改变时触发
|
||||
*/
|
||||
OrientationChanged,
|
||||
}
|
||||
}
|
||||
@@ -1,224 +1,385 @@
|
||||
class Entity {
|
||||
private static _idGenerator: number;
|
||||
module es {
|
||||
export class Entity {
|
||||
public static _idGenerator: number = 0;
|
||||
|
||||
public name: string;
|
||||
public readonly id: number;
|
||||
/** 当前实体所属的场景 */
|
||||
/**
|
||||
* 当前实体所属的场景
|
||||
*/
|
||||
public scene: Scene;
|
||||
/** 封装实体的位置/旋转/缩放,并允许设置一个高层结构 */
|
||||
/**
|
||||
* 实体名称。用于在场景范围内搜索实体
|
||||
*/
|
||||
public name: string;
|
||||
/**
|
||||
* 此实体的唯一标识
|
||||
*/
|
||||
public readonly id: number;
|
||||
/**
|
||||
* 封装实体的位置/旋转/缩放,并允许设置一个高层结构
|
||||
*/
|
||||
public readonly transform: Transform;
|
||||
/** 当前附加到此实体的所有组件的列表 */
|
||||
/**
|
||||
* 当前附加到此实体的所有组件的列表
|
||||
*/
|
||||
public readonly components: ComponentList;
|
||||
private _updateOrder: number = 0;
|
||||
private _enabled: boolean = true;
|
||||
private _isDestoryed: boolean;
|
||||
private _tag: number = 0;
|
||||
|
||||
/**
|
||||
* 指定应该调用这个entity update方法的频率。1表示每一帧,2表示每一帧,以此类推
|
||||
*/
|
||||
public updateInterval: number = 1;
|
||||
public componentBits: BitSet;
|
||||
|
||||
public get parent(){
|
||||
return this.transform.parent;
|
||||
}
|
||||
|
||||
public set parent(value: Transform){
|
||||
this.transform.setParent(value);
|
||||
}
|
||||
|
||||
public get position(){
|
||||
return this.transform.position;
|
||||
}
|
||||
|
||||
public set position(value: Vector2){
|
||||
this.transform.setPosition(value);
|
||||
}
|
||||
|
||||
public get localPosition(){
|
||||
return this.transform.localPosition;
|
||||
}
|
||||
|
||||
public set localPosition(value: Vector2){
|
||||
this.transform.setLocalPosition(value);
|
||||
}
|
||||
|
||||
public get rotation(){
|
||||
return this.transform.rotation;
|
||||
}
|
||||
|
||||
public set rotation(value: number){
|
||||
this.transform.setRotation(value);
|
||||
}
|
||||
|
||||
public get rotationDegrees(){
|
||||
return this.transform.rotationDegrees;
|
||||
}
|
||||
|
||||
public set rotationDegrees(value: number){
|
||||
this.transform.setRotationDegrees(value);
|
||||
}
|
||||
|
||||
public get localRotation(){
|
||||
return this.transform.localRotation;
|
||||
}
|
||||
|
||||
public set localRotation(value: number){
|
||||
this.transform.setLocalRotation(value);
|
||||
}
|
||||
|
||||
public get localRotationDegrees(){
|
||||
return this.transform.localRotationDegrees;
|
||||
}
|
||||
|
||||
public set localRotationDegrees(value: number){
|
||||
this.transform.setLocalRotationDegrees(value);
|
||||
}
|
||||
|
||||
public get scale(){
|
||||
return this.transform.scale;
|
||||
}
|
||||
|
||||
public set scale(value: Vector2){
|
||||
this.transform.setScale(value);
|
||||
}
|
||||
|
||||
public get localScale(){
|
||||
return this.transform.scale;
|
||||
}
|
||||
|
||||
public set localScale(value: Vector2){
|
||||
this.transform.setScale(value);
|
||||
}
|
||||
|
||||
public get worldInverseTransform(){
|
||||
return this.transform.worldInverseTransform;
|
||||
}
|
||||
|
||||
public get localToWorldTransform(){
|
||||
return this.transform.localToWorldTransform;
|
||||
}
|
||||
|
||||
public get worldToLocalTransform(){
|
||||
return this.transform.worldToLocalTransform;
|
||||
}
|
||||
|
||||
public get isDestoryed(){
|
||||
return this._isDestoryed;
|
||||
}
|
||||
|
||||
public get enabled(){
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean){
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
public setEnabled(isEnabled: boolean){
|
||||
if (this._enabled != isEnabled){
|
||||
this._enabled = isEnabled;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public get tag(){
|
||||
return this._tag;
|
||||
}
|
||||
|
||||
public set tag(value: number){
|
||||
this.setTag(value);
|
||||
}
|
||||
|
||||
constructor(name: string){
|
||||
this.name = name;
|
||||
this.transform = new Transform(this);
|
||||
constructor(name: string) {
|
||||
this.components = new ComponentList(this);
|
||||
this.id = Entity._idGenerator ++;
|
||||
this.transform = new Transform(this);
|
||||
this.name = name;
|
||||
this.id = Entity._idGenerator++;
|
||||
|
||||
this.componentBits = new BitSet();
|
||||
}
|
||||
|
||||
public get updateOrder(){
|
||||
public _isDestroyed: boolean;
|
||||
|
||||
/**
|
||||
* 如果调用了destroy,那么在下一次处理实体之前这将一直为true
|
||||
*/
|
||||
public get isDestroyed() {
|
||||
return this._isDestroyed;
|
||||
}
|
||||
|
||||
private _tag: number = 0;
|
||||
|
||||
/**
|
||||
* 你可以随意使用。稍后可以使用它来查询场景中具有特定标记的所有实体
|
||||
*/
|
||||
public get tag(): number {
|
||||
return this._tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 你可以随意使用。稍后可以使用它来查询场景中具有特定标记的所有实体
|
||||
* @param value
|
||||
*/
|
||||
public set tag(value: number) {
|
||||
this.setTag(value);
|
||||
}
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 启用/禁用实体。当禁用碰撞器从物理系统和组件中移除时,方法将不会被调用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用实体。当禁用碰撞器从物理系统和组件中移除时,方法将不会被调用
|
||||
* @param value
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
private _updateOrder: number = 0;
|
||||
|
||||
/**
|
||||
* 更新此实体的顺序。updateOrder还用于对scene.entities上的标签列表进行排序
|
||||
*/
|
||||
public get updateOrder() {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
public set updateOrder(value: number){
|
||||
/**
|
||||
* 更新此实体的顺序。updateOrder还用于对scene.entities上的标签列表进行排序
|
||||
* @param value
|
||||
*/
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
public setUpdateOrder(updateOrder: number){
|
||||
if (this._updateOrder != updateOrder){
|
||||
this._updateOrder = updateOrder;
|
||||
if (this.scene){
|
||||
|
||||
public get parent(): Transform {
|
||||
return this.transform.parent;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
public set parent(value: Transform) {
|
||||
this.transform.setParent(value);
|
||||
}
|
||||
|
||||
public setTag(tag: number): Entity{
|
||||
if (this._tag != tag){
|
||||
if (this.scene){
|
||||
public get childCount() {
|
||||
return this.transform.childCount;
|
||||
}
|
||||
|
||||
public get position(): Vector2 {
|
||||
return this.transform.position;
|
||||
}
|
||||
|
||||
public set position(value: Vector2) {
|
||||
this.transform.setPosition(value.x, value.y);
|
||||
}
|
||||
|
||||
public get localPosition(): Vector2 {
|
||||
return this.transform.localPosition;
|
||||
}
|
||||
|
||||
public set localPosition(value: Vector2) {
|
||||
this.transform.setLocalPosition(value);
|
||||
}
|
||||
|
||||
public get rotation(): number {
|
||||
return this.transform.rotation;
|
||||
}
|
||||
|
||||
public set rotation(value: number) {
|
||||
this.transform.setRotation(value);
|
||||
}
|
||||
|
||||
public get rotationDegrees(): number {
|
||||
return this.transform.rotationDegrees;
|
||||
}
|
||||
|
||||
public set rotationDegrees(value: number) {
|
||||
this.transform.setRotationDegrees(value);
|
||||
}
|
||||
|
||||
public get localRotation(): number {
|
||||
return this.transform.localRotation;
|
||||
}
|
||||
|
||||
public set localRotation(value: number) {
|
||||
this.transform.setLocalRotation(value);
|
||||
}
|
||||
|
||||
public get localRotationDegrees(): number {
|
||||
return this.transform.localRotationDegrees;
|
||||
}
|
||||
|
||||
public set localRotationDegrees(value: number) {
|
||||
this.transform.setLocalRotationDegrees(value);
|
||||
}
|
||||
|
||||
public get scale(): Vector2 {
|
||||
return this.transform.scale;
|
||||
}
|
||||
|
||||
public set scale(value: Vector2) {
|
||||
this.transform.setScale(value);
|
||||
}
|
||||
|
||||
public get localScale(): Vector2 {
|
||||
return this.transform.localScale;
|
||||
}
|
||||
|
||||
public set localScale(value: Vector2) {
|
||||
this.transform.setLocalScale(value);
|
||||
}
|
||||
|
||||
public get worldInverseTransform(): Matrix2D {
|
||||
return this.transform.worldInverseTransform;
|
||||
}
|
||||
|
||||
public get localToWorldTransform(): Matrix2D {
|
||||
return this.transform.localToWorldTransform;
|
||||
}
|
||||
|
||||
public get worldToLocalTransform(): Matrix2D {
|
||||
return this.transform.worldToLocalTransform;
|
||||
}
|
||||
|
||||
public onTransformChanged(comp: transform.Component) {
|
||||
// 通知我们的子项改变了位置
|
||||
this.components.onEntityTransformChanged(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体的标记
|
||||
* @param tag
|
||||
*/
|
||||
public setTag(tag: number): Entity {
|
||||
if (this._tag != tag) {
|
||||
// 我们只有在已经有场景的情况下才会调用entityTagList。如果我们还没有场景,我们会被添加到entityTagList
|
||||
if (this.scene)
|
||||
this.scene.entities.removeFromTagList(this);
|
||||
}
|
||||
this._tag = tag;
|
||||
if (this.scene){
|
||||
if (this.scene)
|
||||
this.scene.entities.addToTagList(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体的启用状态。当禁用碰撞器从物理系统和组件中移除时,方法将不会被调用
|
||||
* @param isEnabled
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
|
||||
if (this._enabled)
|
||||
this.components.onEntityEnabled();
|
||||
else
|
||||
this.components.onEntityDisabled();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public attachToScene(newScene: Scene){
|
||||
/**
|
||||
* 设置此实体的更新顺序。updateOrder还用于对scene.entities上的标签列表进行排序
|
||||
* @param updateOrder
|
||||
*/
|
||||
public setUpdateOrder(updateOrder: number) {
|
||||
if (this._updateOrder != updateOrder) {
|
||||
this._updateOrder = updateOrder;
|
||||
if (this.scene) {
|
||||
this.scene.entities.markEntityListUnsorted();
|
||||
this.scene.entities.markTagUnsorted(this.tag);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除实体并销毁所有子元素
|
||||
*/
|
||||
public destroy() {
|
||||
this._isDestroyed = true;
|
||||
this.scene.entities.remove(this);
|
||||
this.transform.parent = null;
|
||||
|
||||
// 销毁所有子项
|
||||
for (let i = this.transform.childCount - 1; i >= 0; i--) {
|
||||
let child = this.transform.getChild(i);
|
||||
child.entity.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体从场景中分离。下面的生命周期方法将被调用在组件上:OnRemovedFromEntity
|
||||
*/
|
||||
public detachFromScene() {
|
||||
this.scene.entities.remove(this);
|
||||
this.components.deregisterAllComponents();
|
||||
|
||||
for (let i = 0; i < this.transform.childCount; i++)
|
||||
this.transform.getChild(i).entity.detachFromScene();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个先前分离的实体附加到一个新的场景
|
||||
* @param newScene
|
||||
*/
|
||||
public attachToScene(newScene: Scene) {
|
||||
this.scene = newScene;
|
||||
newScene.entities.add(this);
|
||||
this.components.registerAllComponents();
|
||||
|
||||
for (let i = 0; i < this.transform.childCount; i ++){
|
||||
for (let i = 0; i < this.transform.childCount; i++) {
|
||||
this.transform.getChild(i).entity.attachToScene(newScene);
|
||||
}
|
||||
}
|
||||
|
||||
public detachFromScene(){
|
||||
this.scene.entities.remove(this);
|
||||
this.components.deregisterAllComponents();
|
||||
/**
|
||||
* 创建此实体的深层克隆。子类可以重写此方法来复制任何自定义字段。
|
||||
* 当重写时,应该调用CopyFrom方法,它将为您克隆所有组件、碰撞器和转换子组件。
|
||||
* 注意克隆的实体不会被添加到任何场景中!你必须自己添加它们!
|
||||
* @param position
|
||||
*/
|
||||
public clone(position: Vector2 = new Vector2()): Entity {
|
||||
let entity = new Entity(this.name + "(clone)");
|
||||
entity.copyFrom(this);
|
||||
entity.transform.position = position;
|
||||
|
||||
for (let i = 0; i < this.transform.childCount; i ++)
|
||||
this.transform.getChild(i).entity.detachFromScene();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public addComponent<T extends Component>(component: T): T{
|
||||
/**
|
||||
* 在提交了所有挂起的实体更改后,将此实体添加到场景时调用
|
||||
*/
|
||||
public onAddedToScene() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当此实体从场景中删除时调用
|
||||
*/
|
||||
public onRemovedFromScene() {
|
||||
// 如果已经被销毁了,移走我们的组件。如果我们只是分离,我们需要保持我们的组件在实体上。
|
||||
if (this._isDestroyed)
|
||||
this.components.removeAllComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧进行调用进行更新组件
|
||||
*/
|
||||
public update() {
|
||||
this.components.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将组件添加到组件列表中。返回组件。
|
||||
* @param component
|
||||
*/
|
||||
public addComponent<T extends Component>(component: T): T {
|
||||
component.entity = this;
|
||||
this.components.add(component);
|
||||
component.initialize();
|
||||
return component;
|
||||
}
|
||||
|
||||
public hasComponent<T extends Component>(type){
|
||||
/**
|
||||
* 获取类型T的第一个组件并返回它。如果没有找到组件,则返回null。
|
||||
* @param type
|
||||
*/
|
||||
public getComponent<T extends Component>(type): T {
|
||||
return this.components.getComponent(type, false) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否具有该组件
|
||||
* @param type
|
||||
*/
|
||||
public hasComponent<T extends Component>(type) {
|
||||
return this.components.getComponent<T>(type, false) != null;
|
||||
}
|
||||
|
||||
public getOrCreateComponent<T extends Component>(type: T){
|
||||
/**
|
||||
* 获取类型T的第一个组件并返回它。如果没有找到组件,将创建组件。
|
||||
* @param type
|
||||
*/
|
||||
public getOrCreateComponent<T extends Component>(type: T) {
|
||||
let comp = this.components.getComponent<T>(type, true);
|
||||
if (!comp){
|
||||
if (!comp) {
|
||||
comp = this.addComponent<T>(type);
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
public getComponent<T extends Component>(type): T{
|
||||
return this.components.getComponent(type, false) as T;
|
||||
}
|
||||
|
||||
public getComponents(typeName: string | any, componentList?){
|
||||
/**
|
||||
* 获取typeName类型的所有组件,但不使用列表分配
|
||||
* @param typeName
|
||||
* @param componentList
|
||||
*/
|
||||
public getComponents(typeName: string | any, componentList?) {
|
||||
return this.components.getComponents(typeName, componentList);
|
||||
}
|
||||
|
||||
public removeComponentForType<T extends Component>(type){
|
||||
/**
|
||||
* 从组件列表中删除组件
|
||||
* @param component
|
||||
*/
|
||||
public removeComponent(component: Component) {
|
||||
this.components.remove(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组件列表中删除类型为T的第一个组件
|
||||
* @param type
|
||||
*/
|
||||
public removeComponentForType<T extends Component>(type) {
|
||||
let comp = this.getComponent<T>(type);
|
||||
if (comp){
|
||||
if (comp) {
|
||||
this.removeComponent(comp);
|
||||
return true;
|
||||
}
|
||||
@@ -226,42 +387,50 @@ class Entity {
|
||||
return false;
|
||||
}
|
||||
|
||||
public removeComponent(component: Component){
|
||||
this.components.remove(component);
|
||||
}
|
||||
|
||||
public removeAllComponents(){
|
||||
for (let i = 0; i < this.components.count; i ++){
|
||||
/**
|
||||
* 从实体中删除所有组件
|
||||
*/
|
||||
public removeAllComponents() {
|
||||
for (let i = 0; i < this.components.count; i++) {
|
||||
this.removeComponent(this.components.buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public update(){
|
||||
this.components.update();
|
||||
this.transform.updateTransform();
|
||||
public compareTo(other: Entity): number {
|
||||
let compare = this._updateOrder - other._updateOrder;
|
||||
if (compare == 0)
|
||||
compare = this.id - other.id;
|
||||
return compare;
|
||||
}
|
||||
|
||||
public onAddedToScene(){
|
||||
|
||||
public toString(): string {
|
||||
return `[Entity: name: ${this.name}, tag: ${this.tag}, enabled: ${this.enabled}, depth: ${this.updateOrder}]`;
|
||||
}
|
||||
|
||||
public onRemovedFromScene(){
|
||||
if (this._isDestoryed)
|
||||
this.components.remove
|
||||
/**
|
||||
* 将实体的属性、组件和碰撞器复制到此实例
|
||||
* @param entity
|
||||
*/
|
||||
protected copyFrom(entity: Entity) {
|
||||
this.tag = entity.tag;
|
||||
this.updateInterval = entity.updateInterval;
|
||||
this.updateOrder = entity.updateOrder;
|
||||
this.enabled = entity.enabled;
|
||||
|
||||
this.transform.scale = entity.transform.scale;
|
||||
this.transform.rotation = entity.transform.rotation;
|
||||
|
||||
for (let i = 0; i < entity.components.count; i++)
|
||||
this.addComponent(entity.components.buffer[i].clone());
|
||||
for (let i = 0; i < entity.components._componentsToAdd.length; i++)
|
||||
this.addComponent(entity.components._componentsToAdd[i].clone());
|
||||
|
||||
for (let i = 0; i < entity.transform.childCount; i++) {
|
||||
let child = entity.transform.getChild(i).entity;
|
||||
let childClone = child.clone();
|
||||
childClone.transform.copyFrom(child.transform);
|
||||
childClone.transform.parent = this.transform;
|
||||
}
|
||||
|
||||
public onTransformChanged(comp: ComponentTransform){
|
||||
this.components.onEntityTransformChanged(comp);
|
||||
}
|
||||
|
||||
public destory(){
|
||||
this._isDestoryed = true;
|
||||
this.scene.entities.remove(this);
|
||||
this.transform.parent = null;
|
||||
|
||||
for (let i = this.transform.childCount - 1; i >= 0; i --){
|
||||
let child = this.transform.getChild(i);
|
||||
child.entity.destory();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +1,365 @@
|
||||
/** 场景 */
|
||||
class Scene extends egret.DisplayObjectContainer {
|
||||
module es {
|
||||
/** 场景 */
|
||||
export class Scene extends egret.DisplayObjectContainer {
|
||||
/**
|
||||
* 默认场景摄像机
|
||||
*/
|
||||
public camera: Camera;
|
||||
/**
|
||||
* 场景特定内容管理器。使用它来加载仅由这个场景需要的任何资源。如果你有全局/多场景资源,你可以使用SceneManager.content。
|
||||
* contentManager来加载它们,因为Nez不会卸载它们。
|
||||
*/
|
||||
public readonly content: ContentManager;
|
||||
/**
|
||||
* 全局切换后处理器
|
||||
*/
|
||||
public enablePostProcessing = true;
|
||||
/**
|
||||
* 这个场景中的实体列表
|
||||
*/
|
||||
public readonly entities: EntityList;
|
||||
|
||||
private _projectionMatrix: Matrix2D;
|
||||
private _transformMatrix: Matrix2D;
|
||||
private _matrixTransformMatrix: Matrix2D;
|
||||
|
||||
/**
|
||||
* 管理当前在场景实体上的所有可呈现组件的列表
|
||||
*/
|
||||
public readonly renderableComponents: RenderableComponentList;
|
||||
/**
|
||||
* 管理所有实体处理器
|
||||
*/
|
||||
public readonly entityProcessors: EntityProcessorList;
|
||||
|
||||
constructor(displayObject: egret.DisplayObject){
|
||||
public _renderers: Renderer[] = [];
|
||||
public readonly _postProcessors: PostProcessor[] = [];
|
||||
public _didSceneBegin;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
displayObject.stage.addChild(this);
|
||||
this._projectionMatrix = new Matrix2D(0, 0, 0, 0, 0, 0);
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
this.entities = new EntityList(this);
|
||||
this.renderableComponents = new RenderableComponentList();
|
||||
this.content = new ContentManager();
|
||||
|
||||
this.addEventListener(egret.Event.ACTIVATE, this.onActive, this);
|
||||
this.addEventListener(egret.Event.DEACTIVATE, this.onDeactive, this);
|
||||
this.addEventListener(egret.Event.ENTER_FRAME, this.update, this);
|
||||
}
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
|
||||
public createEntity(name: string){
|
||||
let entity = new Entity(name);
|
||||
entity.transform.position = new Vector2(0, 0);
|
||||
return this.addEntity(entity);
|
||||
}
|
||||
|
||||
public addEntity(entity: Entity){
|
||||
this.entities.add(entity);
|
||||
entity.scene = this;
|
||||
|
||||
for (let i = 0; i < entity.transform.childCount; i ++)
|
||||
this.addEntity(entity.transform.getChild(i).entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public destroyAllEntities(){
|
||||
for (let i = 0; i < this.entities.count; i ++){
|
||||
this.entities.buffer[i].destory();
|
||||
}
|
||||
}
|
||||
|
||||
public findEntity(name: string): Entity{
|
||||
return this.entities.findEntity(name);
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
* 辅助器,创建一个场景与DefaultRenderer附加并准备使用
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem){
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
return processor;
|
||||
public static createWithDefaultRenderer() {
|
||||
let scene = new Scene();
|
||||
scene.addRenderer(new DefaultRenderer());
|
||||
return scene;
|
||||
}
|
||||
|
||||
public removeEntityProcessor(processor: EntitySystem){
|
||||
this.entityProcessors.remove(processor);
|
||||
/**
|
||||
* 在场景子类中重写这个并在这里进行加载。在场景设置好之后,在调用begin之前,从构造器中调用。
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
|
||||
public getEntityProcessor<T extends EntitySystem>(): T {
|
||||
return this.entityProcessors.getProcessor<T>();
|
||||
/**
|
||||
* 在场景子类中重写这个。当SceneManager将此场景设置为活动场景时,将调用此操作。
|
||||
*/
|
||||
public async onStart() {
|
||||
}
|
||||
|
||||
public setActive(): Scene{
|
||||
SceneManager.setActiveScene(this);
|
||||
|
||||
return this;
|
||||
/**
|
||||
* 在场景子类中重写这个,并在这里做任何必要的卸载。当SceneManager从活动槽中删除此场景时调用。
|
||||
*/
|
||||
public unload() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在场景子类中重写这个,当该场景当获得焦点时调用
|
||||
*/
|
||||
public onActive() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景子类中重写这个,当该场景当失去焦点时调用
|
||||
*/
|
||||
public onDeactive() {
|
||||
}
|
||||
|
||||
public async begin() {
|
||||
if (this._renderers.length == 0) {
|
||||
this.addRenderer(new DefaultRenderer());
|
||||
console.warn("场景开始时没有渲染器 自动添加DefaultRenderer以保证能够正常渲染");
|
||||
}
|
||||
|
||||
/** 初始化场景 */
|
||||
public initialize(){
|
||||
/** 初始化默认相机 */
|
||||
this.camera = this.createEntity("camera").getOrCreateComponent(new Camera());
|
||||
|
||||
Physics.reset();
|
||||
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.begin();
|
||||
|
||||
this.addEventListener(egret.Event.ACTIVATE, this.onActive, this);
|
||||
this.addEventListener(egret.Event.DEACTIVATE, this.onDeactive, this);
|
||||
this.camera.onSceneSizeChanged(this.stage.stageWidth, this.stage.stageHeight);
|
||||
|
||||
this._didSceneBegin = true;
|
||||
this.onStart();
|
||||
}
|
||||
|
||||
/** 场景激活 */
|
||||
public onActive(){
|
||||
public end() {
|
||||
this._didSceneBegin = false;
|
||||
|
||||
this.removeEventListener(egret.Event.DEACTIVATE, this.onDeactive, this);
|
||||
this.removeEventListener(egret.Event.ACTIVATE, this.onActive, this);
|
||||
|
||||
for (let i = 0; i < this._renderers.length; i++) {
|
||||
this._renderers[i].unload();
|
||||
}
|
||||
|
||||
/** 场景失去焦点 */
|
||||
public onDeactive(){
|
||||
|
||||
for (let i = 0; i < this._postProcessors.length; i++) {
|
||||
this._postProcessors[i].unload();
|
||||
}
|
||||
|
||||
public update(){
|
||||
Time.update(egret.getTimer());
|
||||
this.entities.removeAllEntities();
|
||||
this.removeChildren();
|
||||
|
||||
this.entities.updateLists();
|
||||
this.camera = null;
|
||||
this.content.dispose();
|
||||
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.update()
|
||||
this.entityProcessors.end();
|
||||
|
||||
if (this.parent)
|
||||
this.parent.removeChild(this);
|
||||
|
||||
this.unload();
|
||||
}
|
||||
|
||||
public update() {
|
||||
// 更新我们的列表,以防它们有任何变化
|
||||
this.entities.updateLists();
|
||||
|
||||
// 更新我们的实体解析器
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.update();
|
||||
|
||||
// 更新我们的实体组
|
||||
this.entities.update();
|
||||
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.lateUpdate();
|
||||
|
||||
// 我们在实体之后更新我们的呈现。如果添加了任何新的渲染,请进行更新
|
||||
this.renderableComponents.updateList();
|
||||
}
|
||||
|
||||
public prepRenderState(){
|
||||
this._projectionMatrix.m11 = 2 / this.stage.width;
|
||||
this._projectionMatrix.m22 = -2 / this.stage.height;
|
||||
|
||||
this._transformMatrix = this.camera.transformMatrix;
|
||||
this._matrixTransformMatrix = Matrix2D.multiply(this._transformMatrix, this._projectionMatrix);
|
||||
public render() {
|
||||
if (this._renderers.length == 0) {
|
||||
console.error("场景中没有渲染器!");
|
||||
return;
|
||||
}
|
||||
|
||||
public destory(){
|
||||
this.removeEventListener(egret.Event.DEACTIVATE, this.onDeactive, this);
|
||||
this.removeEventListener(egret.Event.ACTIVATE, this.onActive, this);
|
||||
for (let i = 0; i < this._renderers.length; i++) {
|
||||
this._renderers[i].render(this);
|
||||
}
|
||||
}
|
||||
|
||||
this.camera.destory();
|
||||
this.camera = null;
|
||||
/**
|
||||
* 现在的任何后处理器都要完成它的处理
|
||||
* 只有在SceneTransition请求渲染时,它才会有一个值。
|
||||
*/
|
||||
public postRender() {
|
||||
if (this.enablePostProcessing) {
|
||||
for (let i = 0; i < this._postProcessors.length; i++) {
|
||||
if (this._postProcessors[i].enabled) {
|
||||
this._postProcessors[i].process();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.entities.removeAllEntities();
|
||||
/**
|
||||
* 为场景添加一个渲染器
|
||||
* @param renderer
|
||||
*/
|
||||
public addRenderer<T extends Renderer>(renderer: T) {
|
||||
this._renderers.push(renderer);
|
||||
this._renderers.sort();
|
||||
|
||||
renderer.onAddedToScene(this);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为T的第一个渲染器
|
||||
* @param type
|
||||
*/
|
||||
public getRenderer<T extends Renderer>(type): T {
|
||||
for (let i = 0; i < this._renderers.length; i++) {
|
||||
if (this._renderers[i] instanceof type)
|
||||
return this._renderers[i] as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中移除渲染器
|
||||
* @param renderer
|
||||
*/
|
||||
public removeRenderer(renderer: Renderer) {
|
||||
if (!this._renderers.contains(renderer))
|
||||
return;
|
||||
this._renderers.remove(renderer);
|
||||
renderer.unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个后处理器到场景。设置场景字段并调用后处理器。onAddedToScene使后处理器可以使用场景ContentManager加载资源。
|
||||
* @param postProcessor
|
||||
*/
|
||||
public addPostProcessor<T extends PostProcessor>(postProcessor: T): T {
|
||||
this._postProcessors.push(postProcessor);
|
||||
this._postProcessors.sort();
|
||||
postProcessor.onAddedToScene(this);
|
||||
|
||||
if (this._didSceneBegin) {
|
||||
postProcessor.onSceneBackBufferSizeChanged(this.stage.stageWidth, this.stage.stageHeight);
|
||||
}
|
||||
|
||||
return postProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为T的第一个后处理器
|
||||
* @param type
|
||||
*/
|
||||
public getPostProcessor<T extends PostProcessor>(type): T {
|
||||
for (let i = 0; i < this._postProcessors.length; i++) {
|
||||
if (this._postProcessors[i] instanceof type)
|
||||
return this._postProcessors[i] as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个后处理程序。注意,在删除时不会调用unload,因此如果不再需要PostProcessor,请确保调用unload来释放资源。
|
||||
* @param postProcessor
|
||||
*/
|
||||
public removePostProcessor(postProcessor: PostProcessor) {
|
||||
if (!this._postProcessors.contains(postProcessor))
|
||||
return;
|
||||
|
||||
this._postProcessors.remove(postProcessor);
|
||||
postProcessor.unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体添加到此场景,并返回它
|
||||
* @param name
|
||||
*/
|
||||
public createEntity(name: string) {
|
||||
let entity = new Entity(name);
|
||||
return this.addEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体添加到此场景,并返回它
|
||||
* @param name
|
||||
*/
|
||||
public createEntityAsync(name: string): Promise<Entity> {
|
||||
return new Promise<Entity>(resolve => {
|
||||
resolve(this.createEntity(name));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景的实体列表中添加一个实体
|
||||
* @param entity
|
||||
*/
|
||||
public addEntity(entity: Entity) {
|
||||
if (this.entities.buffer.contains(entity))
|
||||
console.warn(`您试图将同一实体添加到场景两次: ${entity}`);
|
||||
this.entities.add(entity);
|
||||
entity.scene = this;
|
||||
|
||||
for (let i = 0; i < entity.transform.childCount; i++)
|
||||
this.addEntity(entity.transform.getChild(i).entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除所有实体
|
||||
*/
|
||||
public destroyAllEntities() {
|
||||
for (let i = 0; i < this.entities.count; i++) {
|
||||
this.entities.buffer[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索并返回第一个具有名称的实体
|
||||
* @param name
|
||||
*/
|
||||
public findEntity(name: string): Entity {
|
||||
return this.entities.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回具有给定标记的所有实体
|
||||
* @param tag
|
||||
*/
|
||||
public findEntitiesWithTag(tag: number): Entity[] {
|
||||
return this.entities.entitiesWithTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回类型为T的所有实体
|
||||
* @param type
|
||||
*/
|
||||
public entitiesOfType<T extends Entity>(type): T[] {
|
||||
return this.entities.entitiesOfType<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个启用加载的类型为T的组件
|
||||
* @param type
|
||||
*/
|
||||
public findComponentOfType<T extends Component>(type): T {
|
||||
return this.entities.findComponentOfType<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回类型为T的所有已启用已加载组件的列表
|
||||
* @param type
|
||||
*/
|
||||
public findComponentsOfType<T extends Component>(type): T[] {
|
||||
return this.entities.findComponentsOfType<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem) {
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除EntitySystem处理器
|
||||
* @param processor
|
||||
*/
|
||||
public removeEntityProcessor(processor: EntitySystem) {
|
||||
this.entityProcessors.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取EntitySystem处理器
|
||||
*/
|
||||
public getEntityProcessor<T extends EntitySystem>(): T {
|
||||
return this.entityProcessors.getProcessor<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||