Sfoglia il codice sorgente

复制倒水源码

woso_javan 2 mesi fa
parent
commit
d89c0af219
100 ha cambiato i file con 8311 aggiunte e 0 eliminazioni
  1. 2 0
      .creator/asset-template/typescript/Custom Script Template Help Documentation.url
  2. 15 0
      .creator/default-meta.json
  3. 24 0
      .gitignore
  4. 9 0
      assets/core_tgx.meta
  5. 9 0
      assets/core_tgx/base.meta
  6. 157 0
      assets/core_tgx/base/AudioMgr.ts
  7. 9 0
      assets/core_tgx/base/AudioMgr.ts.meta
  8. 65 0
      assets/core_tgx/base/GtagMgr.ts
  9. 9 0
      assets/core_tgx/base/GtagMgr.ts.meta
  10. 41 0
      assets/core_tgx/base/InputMgr.ts
  11. 13 0
      assets/core_tgx/base/InputMgr.ts.meta
  12. 37 0
      assets/core_tgx/base/ModuleClass.ts
  13. 9 0
      assets/core_tgx/base/ModuleClass.ts.meta
  14. 37 0
      assets/core_tgx/base/ModuleContext.ts
  15. 9 0
      assets/core_tgx/base/ModuleContext.ts.meta
  16. 505 0
      assets/core_tgx/base/ResLoader.ts
  17. 9 0
      assets/core_tgx/base/ResLoader.ts.meta
  18. 43 0
      assets/core_tgx/base/ResolutionAutoFit.ts
  19. 9 0
      assets/core_tgx/base/ResolutionAutoFit.ts.meta
  20. 49 0
      assets/core_tgx/base/ResourceMgr.ts
  21. 13 0
      assets/core_tgx/base/ResourceMgr.ts.meta
  22. 22 0
      assets/core_tgx/base/SafeJSON.ts
  23. 13 0
      assets/core_tgx/base/SafeJSON.ts.meta
  24. 9 0
      assets/core_tgx/base/ad.meta
  25. 11 0
      assets/core_tgx/base/ad/ADEvent.ts
  26. 9 0
      assets/core_tgx/base/ad/ADEvent.ts.meta
  27. 81 0
      assets/core_tgx/base/ad/AdvertMgr.ts
  28. 9 0
      assets/core_tgx/base/ad/AdvertMgr.ts.meta
  29. 9 0
      assets/core_tgx/base/utils.meta
  30. 109 0
      assets/core_tgx/base/utils/JsonUtil.ts
  31. 9 0
      assets/core_tgx/base/utils/JsonUtil.ts.meta
  32. 9 0
      assets/core_tgx/easy_adapter.meta
  33. 253 0
      assets/core_tgx/easy_adapter/Adapter.ts
  34. 9 0
      assets/core_tgx/easy_adapter/Adapter.ts.meta
  35. 234 0
      assets/core_tgx/easy_adapter/AdapterSafeArea.ts
  36. 9 0
      assets/core_tgx/easy_adapter/AdapterSafeArea.ts.meta
  37. 167 0
      assets/core_tgx/easy_adapter/AdapterSprite.ts
  38. 9 0
      assets/core_tgx/easy_adapter/AdapterSprite.ts.meta
  39. 60 0
      assets/core_tgx/easy_adapter/AdapterView.ts
  40. 9 0
      assets/core_tgx/easy_adapter/AdapterView.ts.meta
  41. 9 0
      assets/core_tgx/easy_camera.meta
  42. 14 0
      assets/core_tgx/easy_camera/FPSCamera.ts
  43. 9 0
      assets/core_tgx/easy_camera/FPSCamera.ts.meta
  44. 27 0
      assets/core_tgx/easy_camera/FollowCamera2D.ts
  45. 9 0
      assets/core_tgx/easy_camera/FollowCamera2D.ts.meta
  46. 182 0
      assets/core_tgx/easy_camera/FreeCamera.ts
  47. 9 0
      assets/core_tgx/easy_camera/FreeCamera.ts.meta
  48. 100 0
      assets/core_tgx/easy_camera/ThirdPersonCamera.ts
  49. 9 0
      assets/core_tgx/easy_camera/ThirdPersonCamera.ts.meta
  50. 9 0
      assets/core_tgx/easy_controller.meta
  51. 207 0
      assets/core_tgx/easy_controller/CharacterMovement.ts
  52. 9 0
      assets/core_tgx/easy_controller/CharacterMovement.ts.meta
  53. 58 0
      assets/core_tgx/easy_controller/CharacterMovement2D.ts
  54. 9 0
      assets/core_tgx/easy_controller/CharacterMovement2D.ts.meta
  55. 43 0
      assets/core_tgx/easy_controller/EasyController.ts
  56. 9 0
      assets/core_tgx/easy_controller/EasyController.ts.meta
  57. 22 0
      assets/core_tgx/easy_controller/ThirdPersonCameraCtrl.ts
  58. 9 0
      assets/core_tgx/easy_controller/ThirdPersonCameraCtrl.ts.meta
  59. 348 0
      assets/core_tgx/easy_controller/UI_Joystick.ts
  60. 9 0
      assets/core_tgx/easy_controller/UI_Joystick.ts.meta
  61. 3565 0
      assets/core_tgx/easy_controller/ui_joystick_panel.prefab
  62. 13 0
      assets/core_tgx/easy_controller/ui_joystick_panel.prefab.meta
  63. 9 0
      assets/core_tgx/easy_ui_framework.meta
  64. 74 0
      assets/core_tgx/easy_ui_framework/EventDispatcher.ts
  65. 13 0
      assets/core_tgx/easy_ui_framework/EventDispatcher.ts.meta
  66. 279 0
      assets/core_tgx/easy_ui_framework/UICanvas.prefab
  67. 13 0
      assets/core_tgx/easy_ui_framework/UICanvas.prefab.meta
  68. 390 0
      assets/core_tgx/easy_ui_framework/UIController.ts
  69. 13 0
      assets/core_tgx/easy_ui_framework/UIController.ts.meta
  70. 32 0
      assets/core_tgx/easy_ui_framework/UILayers.ts
  71. 9 0
      assets/core_tgx/easy_ui_framework/UILayers.ts.meta
  72. 191 0
      assets/core_tgx/easy_ui_framework/UIMgr.ts
  73. 13 0
      assets/core_tgx/easy_ui_framework/UIMgr.ts.meta
  74. 9 0
      assets/core_tgx/easy_ui_framework/alert.meta
  75. 28 0
      assets/core_tgx/easy_ui_framework/alert/Layout_UIAlert.ts
  76. 9 0
      assets/core_tgx/easy_ui_framework/alert/Layout_UIAlert.ts.meta
  77. 100 0
      assets/core_tgx/easy_ui_framework/alert/UIAlert.ts
  78. 9 0
      assets/core_tgx/easy_ui_framework/alert/UIAlert.ts.meta
  79. 9 0
      assets/core_tgx/easy_ui_framework/tips.meta
  80. 10 0
      assets/core_tgx/easy_ui_framework/tips/Layout_UITips.ts
  81. 9 0
      assets/core_tgx/easy_ui_framework/tips/Layout_UITips.ts.meta
  82. 96 0
      assets/core_tgx/easy_ui_framework/tips/UITips.ts
  83. 9 0
      assets/core_tgx/easy_ui_framework/tips/UITips.ts.meta
  84. 9 0
      assets/core_tgx/easy_ui_framework/waiting.meta
  85. 12 0
      assets/core_tgx/easy_ui_framework/waiting/Layout_UIWaiting.ts
  86. 9 0
      assets/core_tgx/easy_ui_framework/waiting/Layout_UIWaiting.ts.meta
  87. 48 0
      assets/core_tgx/easy_ui_framework/waiting/UIWaiting.ts
  88. 9 0
      assets/core_tgx/easy_ui_framework/waiting/UIWaiting.ts.meta
  89. 32 0
      assets/core_tgx/tgx.ts
  90. 9 0
      assets/core_tgx/tgx.ts.meta
  91. 12 0
      assets/module_basic.meta
  92. 9 0
      assets/module_basic/config.meta
  93. 1 0
      assets/module_basic/config/color_config.json
  94. 11 0
      assets/module_basic/config/color_config.json.meta
  95. 0 0
      assets/module_basic/config/levels_config.json
  96. 11 0
      assets/module_basic/config/levels_config.json.meta
  97. 1 0
      assets/module_basic/config/main_config.json
  98. 11 0
      assets/module_basic/config/main_config.json.meta
  99. 1 0
      assets/module_basic/config/music_config.json
  100. 11 0
      assets/module_basic/config/music_config.json.meta

+ 2 - 0
.creator/asset-template/typescript/Custom Script Template Help Documentation.url

@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template

+ 15 - 0
.creator/default-meta.json

@@ -0,0 +1,15 @@
+{
+  "texture": {
+    "minfilter": "linear",
+    "magfilter": "linear",
+    "mipfilter": "nearest"
+  },
+  "erp-texture-cube": {
+    "minfilter": "linear",
+    "magfilter": "linear",
+    "mipfilter": "nearest"
+  },
+  "image": {
+    "type": "sprite-frame"
+  }
+}

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+
+#///////////////////////////
+# Cocos Creator 3D Project
+#///////////////////////////
+library/
+temp/
+local/
+build/
+profiles/
+native
+#//////////////////////////
+# NPM
+#//////////////////////////
+node_modules/
+
+#//////////////////////////
+# VSCode
+#//////////////////////////
+.vscode/
+
+#//////////////////////////
+# WebStorm
+#//////////////////////////
+.idea/

+ 9 - 0
assets/core_tgx.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "6d37d75f-452b-4ebf-a05a-47a483c8e612",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/base.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "7aebb67a-53c0-42ae-965a-bdbe30277e3f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 157 - 0
assets/core_tgx/base/AudioMgr.ts

@@ -0,0 +1,157 @@
+import { AudioClip, AudioSource, Node, assetManager, director } from 'cc';
+
+/**
+ * @en
+ * This is a singleton class for audio play, can be easily called from anywhere in your project.
+ * @zh
+ * 这是一个用于播放音频的单件类,可以很方便地在项目的任何地方调用。
+ */
+export class AudioMgr {
+    private static _inst: AudioMgr;
+    public static get inst(): AudioMgr {
+        if (this._inst == null) {
+            this._inst = new AudioMgr();
+        }
+        return this._inst;
+    }
+
+    private _audioSource: AudioSource;
+    private _bgMusicEnabled: boolean = true; // 是否开启背景音乐
+    private _soundEffectsEnabled: boolean = true; // 是否开启音效
+
+    constructor() {
+        // 创建一个节点作为 audioMgr
+        let audioMgr = new Node();
+        audioMgr.name = '__audioMgr__';
+
+        // 添加节点到场景
+        director.getScene().addChild(audioMgr);
+
+        // 标记为常驻节点,这样场景切换的时候就不会被销毁了
+        director.addPersistRootNode(audioMgr);
+
+        // 添加 AudioSource 组件,用于播放音频
+        this._audioSource = audioMgr.addComponent(AudioSource);
+    }
+
+    public get audioSource() {
+        return this._audioSource;
+    }
+    public get bgMusicEnabled() {
+        return this._bgMusicEnabled;
+    }
+    public get soundEffectsEnabled() {
+        return this._soundEffectsEnabled;
+    }
+
+    /**
+     * @en
+     * Play short audio, such as strikes, explosions
+     * @zh
+     * 播放短音频,比如 打击音效,爆炸音效等
+     * @param sound Clip or URL for the audio
+     * @param volume 
+     */
+    playOneShot(sound: AudioClip | string, volume: number = 1.0, bundleName: string = 'resources') {
+        if (!this._soundEffectsEnabled) {
+            return; // 如果音效开关关闭,则不播放音效
+        }
+
+        if (sound instanceof AudioClip) {
+            this._audioSource.playOneShot(sound, volume);
+        } else {
+            let bundle = assetManager.getBundle(bundleName);
+            bundle.load(sound, (err, clip: AudioClip) => {
+                if (err) {
+                    console.log(err);
+                } else {
+                    this._audioSource.playOneShot(clip, volume);
+                }
+            });
+        }
+    }
+
+    /**
+     * @en
+     * Play long audio, such as the bg music
+     * @zh
+     * 播放长音频,比如 背景音乐
+     * @param sound Clip or URL for the sound
+     * @param volume 
+     */
+    play(sound: AudioClip | string, volume: number = 1.0, bundleName: string = 'resources') {
+        if (!this._bgMusicEnabled) {
+            return; // 如果背景音乐开关关闭,则不播放音乐
+        }
+
+        if (sound instanceof AudioClip) {
+            this._audioSource.clip = sound;
+            this._audioSource.play();
+            this._audioSource.loop = true;
+            this.audioSource.volume = volume;
+        } else {
+            if (this._audioSource.clip) {
+                this._audioSource.stop();
+                this._audioSource.clip = null;
+            }
+
+            let bundle = assetManager.getBundle(bundleName);
+            bundle.load(sound, (err, clip: AudioClip) => {
+                if (err) {
+                    console.log(err);
+                } else {
+                    this._audioSource.clip = clip;
+                    this._audioSource.play();
+                    this._audioSource.loop = true;
+                    this.audioSource.volume = volume;
+                }
+            });
+        }
+    }
+
+    /**
+     * Stop the audio play
+     */
+    stop() {
+        this._audioSource.stop();
+    }
+
+    /**
+     * Pause the audio play
+     */
+    pause() {
+        this._audioSource.pause();
+    }
+
+    /**
+     * Resume the audio play
+     */
+    resume() {
+        if (this._bgMusicEnabled) {
+            this._audioSource.play();
+        }
+    }
+
+    /**
+     * Enable or disable background music
+     * 开启或关闭背景音乐
+     * @param enabled 
+     */
+    toggleBgMusic(enabled: boolean) {
+        this._bgMusicEnabled = enabled;
+        if (enabled) {
+            this.resume(); // 开启时恢复播放背景音乐
+        } else {
+            this.stop(); // 关闭时停止背景音乐
+        }
+    }
+
+    /**
+     * Enable or disable sound effects
+     * 开启或关闭音效
+     * @param enabled 
+     */
+    toggleSoundEffects(enabled: boolean) {
+        this._soundEffectsEnabled = enabled;
+    }
+}

+ 9 - 0
assets/core_tgx/base/AudioMgr.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "d506f08a-1247-4c63-baa4-759594ea27aa",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 65 - 0
assets/core_tgx/base/GtagMgr.ts

@@ -0,0 +1,65 @@
+/** 上报管理器*/
+export class GtagMgr {
+
+    private static _inst: GtagMgr = null;
+    private gtag: any = null;
+    public static get inst(): GtagMgr {
+        if (!this._inst) {
+            this._inst = new GtagMgr();
+        }
+        return this._inst;
+    }
+
+    public init() {
+        //@ts-ignore
+        if (typeof window.gtag === 'function') {
+            console.log('gtag is available.');
+            //@ts-ignore
+            this.gtag = window.gtag;
+        } else {
+            console.warn('gtag is not available.');
+        }
+    }
+
+    public doGameDot(_events, value?) {
+        if (!this.gtag) {
+            console.log('gtag获取失败 无法上报!');
+            return;
+        }
+        if (_events === GtagType.game_start) {
+            console.log('gtag上报游戏开始!');
+            this.gtag('event', _events, {
+                'game_name': document.title
+            })
+        }
+
+        if (_events === GtagType.level_start) {
+            this.gtag('event', _events, {
+                'level_name': value.level,
+                'game_name': document.title
+
+            })
+        }
+        if (_events === GtagType.level_end) {
+            this.gtag('event', _events, {
+                'level_name': value.level,
+                'success': 'level success',
+                'game_name': document.title
+            })
+        }
+
+        if (_events === GtagType.ad_error) {
+            this.gtag('event', _events, {
+                'game_name': document.title
+
+            })
+        }
+    }
+}
+
+export const GtagType = {
+    game_start: "game_start",
+    level_start: "level_start",
+    level_end: "level_end",
+    ad_error: "ad_error",
+}

+ 9 - 0
assets/core_tgx/base/GtagMgr.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "90384a41-3d8d-406d-b35e-bb5463c22803",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 41 - 0
assets/core_tgx/base/InputMgr.ts

@@ -0,0 +1,41 @@
+export class InputMgr{
+    private STATE_NORMAL = 1;
+    private STATE_KEEP = 2;
+
+    private static _inst:InputMgr = null;
+    private _flags = {};
+    private _flagsMeta = {};
+    public static get inst():InputMgr{
+        if(!this._inst){
+            this._inst = new InputMgr();
+        }
+        return this._inst;
+    }
+
+    public setFlag(flag:string,keep?:boolean,meta?:any){
+        this._flags[flag] = keep? this.STATE_KEEP:this.STATE_NORMAL;
+        if(meta != null){
+            this._flagsMeta[flag] = meta;
+        }
+    }
+
+    public removeFlag(flag:string){
+        delete this._flags[flag];
+    }
+
+    public hasFlag(flag:string):boolean{
+        return !!this._flags[flag];
+    }
+
+    public getMetaByFlag(flag:string):any{
+        return this._flagsMeta[flag];
+    }
+
+    public update(){
+        for(let k in this._flags){
+            if(this._flags[k] != this.STATE_KEEP){
+                this._flags[k] = 0;
+            }
+        }
+    }
+}

+ 13 - 0
assets/core_tgx/base/InputMgr.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "8fac4981-1bc9-4fa3-9c41-df044d19386c",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/InputMgr.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 37 - 0
assets/core_tgx/base/ModuleClass.ts

@@ -0,0 +1,37 @@
+
+const PROP_MODULE = '__module__name__';
+const PROP_IMPL_CLASS = '__impl__class__';
+
+let defaultModule = 'resources';
+
+export class ModuleClass {
+    public static setDefaultModule(moduleName) {
+        defaultModule = moduleName;
+    }
+
+    public static attachModule(cls, moduleName) {
+        cls[PROP_MODULE] = moduleName;
+    }
+
+    public static getModule(cls) {
+        return cls[PROP_MODULE] || defaultModule;
+    }
+
+    public static attachImplClass(cls, implCls) {
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static attachModuleAndImplClass(cls, moduleName, implCls) {
+        cls[PROP_MODULE] = moduleName;
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static getImplClass(cls) {
+        return cls[PROP_IMPL_CLASS] || cls;
+    }
+
+    public static createFromModule(cls) {
+        let implCls = this.getImplClass(cls) || cls;
+        return new implCls();
+    }
+}

+ 9 - 0
assets/core_tgx/base/ModuleClass.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "aa98a0fc-e2da-4a44-a681-cd3411f4de99",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 37 - 0
assets/core_tgx/base/ModuleContext.ts

@@ -0,0 +1,37 @@
+
+const PROP_MODULE = '__module__name__';
+const PROP_IMPL_CLASS = '__impl__class__';
+
+let defaultModule = 'resources';
+
+export class ModuleContext {
+    public static setDefaultModule(moduleName) {
+        defaultModule = moduleName;
+    }
+
+    public static attachModule(cls, moduleName) {
+        cls[PROP_MODULE] = moduleName;
+    }
+
+    public static getClassModule(cls) {
+        return cls[PROP_MODULE] || defaultModule;
+    }
+
+    public static attachImplClass(cls, implCls) {
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static attachModuleAndImplClass(cls, moduleName, implCls) {
+        cls[PROP_MODULE] = moduleName;
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static getImplClass(cls) {
+        return cls[PROP_IMPL_CLASS] || cls;
+    }
+
+    public static createFromModule(cls) {
+        let implCls = this.getImplClass(cls) || cls;
+        return new implCls();
+    }
+}

+ 9 - 0
assets/core_tgx/base/ModuleContext.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "5575add5-5764-43ef-8ea1-8b5394d57b23",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 505 - 0
assets/core_tgx/base/ResLoader.ts

@@ -0,0 +1,505 @@
+import { Asset, AssetManager, __private, assetManager, error, js, resources, warn } from "cc";
+
+export type AssetType<T = Asset> = __private._types_globals__Constructor<T> | null;
+export type Paths = string | string[];
+export type ProgressCallback = ((finished: number, total: number, item: AssetManager.RequestItem) => void) | null;
+export type CompleteCallback = any;
+export type IRemoteOptions = { [k: string]: any; ext?: string; } | null;
+
+interface ILoadResArgs<T extends Asset> {
+    /** 资源包名 */
+    bundle?: string;
+    /** 资源文件夹名 */
+    dir?: string;
+    /** 资源路径 */
+    paths: Paths;
+    /** 资源类型 */
+    type: AssetType<T>;
+    /** 资源加载进度 */
+    onProgress: ProgressCallback;
+    /** 资源加载完成 */
+    onComplete: CompleteCallback;
+    /** 是否为预加载 */
+    preload?: boolean;
+}
+
+/** 
+ * 游戏资源管理
+ * 1、加载默认resources文件夹中资源
+ * 2、加载默认bundle远程资源
+ * 3、主动传递bundle名时,优先加载传递bundle名资源包中的资源
+ * 
+ * @help    https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12037901&doc_id=2873565
+ */
+export class ResLoader {
+    //#region 资源配置数据
+    /** 全局默认加载的资源包名 */
+    defaultBundleName: string = "module_basic";
+    gameBundleName: string = "module_take_goblet";
+    /** 是否使用远程 CDN 资源 */
+    cdn: boolean = false;
+
+    /** 下载时的最大并发数 - 项目设置 -> 项目数据 -> 资源下载并发数,设置默认值;初始值为15 */
+    get maxConcurrency() {
+        return assetManager.downloader.maxConcurrency;
+    }
+    set maxConcurrency(value) {
+        assetManager.downloader.maxConcurrency = value;
+    }
+
+    /** 下载时每帧可以启动的最大请求数 - 默认值为15 */
+    get maxRequestsPerFrame() {
+        return assetManager.downloader.maxRequestsPerFrame;
+    }
+    set maxRequestsPerFrame(value) {
+        assetManager.downloader.maxRequestsPerFrame = value;
+    }
+
+    /** 失败重试次数 - 默认值为0 */
+    get maxRetryCount() {
+        return assetManager.downloader.maxRetryCount;
+    }
+    set maxRetryCount(value) {
+        assetManager.downloader.maxRetryCount = value;
+    }
+
+    /** 重试的间隔时间,单位为毫秒 - 默认值为2000毫秒 */
+    get retryInterval() {
+        return assetManager.downloader.retryInterval;
+    }
+    set retryInterval(value) {
+        assetManager.downloader.retryInterval = value;
+    }
+
+    /** 资源包配置 */
+    private bundles: Map<string, string> = new Map<string, string>();
+    //#endregion
+
+    init(config: any) {
+        this.cdn = config.enable;
+        for (let bundleName in config.packages) {
+            this.bundles.set(bundleName, config.packages[bundleName]);
+        }
+    }
+
+    //#region 加载远程资源
+    /**
+     * 加载远程资源
+     * @param url           资源地址
+     * @param options       资源参数,例:{ ext: ".png" }
+     * @param onComplete    加载完成回调
+     * @example
+var opt: IRemoteOptions = { ext: ".png" };
+var onComplete = (err: Error | null, data: ImageAsset) => {
+    const texture = new Texture2D();
+    texture.image = data;
+    
+    const spriteFrame = new SpriteFrame();
+    spriteFrame.texture = texture;
+    
+    var sprite = this.sprite.addComponent(Sprite);
+    sprite.spriteFrame = spriteFrame;
+}
+oops.res.loadRemote<ImageAsset>(this.url, opt, onComplete);
+     */
+    loadRemote<T extends Asset>(url: string, options: IRemoteOptions | null, onComplete?: CompleteCallback): void;
+    loadRemote<T extends Asset>(url: string, onComplete?: CompleteCallback): void;
+    loadRemote<T extends Asset>(url: string, ...args: any): void {
+        let options: IRemoteOptions | null = null;
+        let onComplete: CompleteCallback = null;
+        if (args.length == 2) {
+            options = args[0];
+            onComplete = args[1];
+        }
+        else {
+            onComplete = args[0];
+        }
+        assetManager.loadRemote<T>(url, options, onComplete);
+    }
+    //#endregion
+
+    //#region 资源包管理
+    /**
+     * 加载资源包
+     * @param url       资源地址
+     * @param v         资源MD5版本号
+     * @example
+var serverUrl = "http://192.168.1.8:8080/";         // 服务器地址
+var md5 = "8e5c0";                                  // Cocos Creator 构建后的MD5字符
+await oops.res.loadBundle(serverUrl,md5);
+     */
+    loadBundle(url: string, v?: string) {
+        return new Promise<AssetManager.Bundle>((resolve, reject) => {
+            assetManager.loadBundle(url, { version: v }, (err, bundle: AssetManager.Bundle) => {
+                if (err) {
+                    return error(err);
+                }
+                resolve(bundle);
+            });
+        });
+    }
+
+    /**
+     * 释放资源包与包中所有资源
+     * @param bundleName 资源地址
+     */
+    removeBundle(bundleName: string) {
+        let bundle = assetManager.bundles.get(bundleName);
+        if (bundle) {
+            bundle.releaseAll();
+            assetManager.removeBundle(bundle);
+        }
+    }
+    //#endregion
+
+    //#region 预加载资源
+    /**
+     * 加载一个资源
+     * @param bundleName    远程包名
+     * @param paths         资源路径
+     * @param type          资源类型
+     * @param onProgress    加载进度回调
+     * @param onComplete    加载完成回调
+     */
+    preload<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preload<T extends Asset>(bundleName: string, paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preload<T extends Asset>(bundleName: string, paths: Paths, onComplete?: CompleteCallback): void;
+    preload<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    preload<T extends Asset>(paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preload<T extends Asset>(paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preload<T extends Asset>(paths: Paths, onComplete?: CompleteCallback): void;
+    preload<T extends Asset>(paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    preload<T extends Asset>(
+        bundleName: string,
+        paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback,
+        onProgress?: ProgressCallback | CompleteCallback,
+        onComplete?: CompleteCallback,
+    ) {
+        let args: ILoadResArgs<Asset> | null = null;
+        if (typeof paths === "string" || paths instanceof Array) {
+            args = this.parseLoadResArgs(paths, type, onProgress, onComplete);
+            args.bundle = bundleName;
+        }
+        else {
+            args = this.parseLoadResArgs(bundleName, paths, type, onProgress);
+            args.bundle = this.defaultBundleName;
+        }
+        args.preload = true;
+        this.loadByArgs(args);
+    }
+
+    /**
+     * 异步加载一个资源
+     * @param bundleName    远程包名
+     * @param paths         资源路径
+     * @param type          资源类型
+     */
+    preloadAsync<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>): Promise<AssetManager.RequestItem>;
+    preloadAsync<T extends Asset>(bundleName: string, paths: Paths): Promise<AssetManager.RequestItem>;
+    preloadAsync<T extends Asset>(paths: Paths, type: AssetType<T>): Promise<AssetManager.RequestItem>;
+    preloadAsync<T extends Asset>(paths: Paths): Promise<AssetManager.RequestItem>;
+    preloadAsync<T extends Asset>(bundleName: string,
+        paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback): Promise<AssetManager.RequestItem> {
+        return new Promise((resolve, reject) => {
+            this.preload(bundleName, paths, type, (err: Error | null, data: AssetManager.RequestItem) => {
+                if (err) {
+                    warn(err.message);
+                }
+                resolve(data);
+            });
+        });
+    }
+
+    /**
+     * 预加载文件夹中的资源
+     * @param bundleName    远程包名
+     * @param dir           文件夹名
+     * @param type          资源类型
+     * @param onProgress    加载进度回调
+     * @param onComplete    加载完成回调
+     */
+    preloadDir<T extends Asset>(bundleName: string, dir: string, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preloadDir<T extends Asset>(bundleName: string, dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preloadDir<T extends Asset>(bundleName: string, dir: string, onComplete?: CompleteCallback): void;
+    preloadDir<T extends Asset>(bundleName: string, dir: string, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    preloadDir<T extends Asset>(dir: string, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preloadDir<T extends Asset>(dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    preloadDir<T extends Asset>(dir: string, onComplete?: CompleteCallback): void;
+    preloadDir<T extends Asset>(dir: string, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    preloadDir<T extends Asset>(
+        bundleName: string,
+        dir?: string | AssetType<T> | ProgressCallback | CompleteCallback,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback,
+        onProgress?: ProgressCallback | CompleteCallback,
+        onComplete?: CompleteCallback,
+    ) {
+        let args: ILoadResArgs<T> | null = null;
+        if (typeof dir === "string") {
+            args = this.parseLoadResArgs(dir, type, onProgress, onComplete);
+            args.bundle = bundleName;
+        }
+        else {
+            args = this.parseLoadResArgs(bundleName, dir, type, onProgress);
+            args.bundle = this.defaultBundleName;
+        }
+        args.dir = args.paths as string;
+        args.preload = true;
+        this.loadByArgs(args);
+    }
+    //#endregion
+
+    //#region 资源加载、获取、释放
+    /**
+     * 加载一个资源
+     * @param bundleName    远程包名
+     * @param paths         资源路径
+     * @param type          资源类型
+     * @param onProgress    加载进度回调
+     * @param onComplete    加载完成回调
+     * @example
+oops.res.load("spine_path", sp.SkeletonData, (err: Error | null, sd: sp.SkeletonData) => {
+
+});
+     */
+    load<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    load<T extends Asset>(bundleName: string, paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    load<T extends Asset>(bundleName: string, paths: Paths, onComplete?: CompleteCallback): void;
+    load<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    load<T extends Asset>(paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    load<T extends Asset>(paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    load<T extends Asset>(paths: Paths, onComplete?: CompleteCallback): void;
+    load<T extends Asset>(paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    load<T extends Asset>(
+        bundleName: string,
+        paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback,
+        onProgress?: ProgressCallback | CompleteCallback,
+        onComplete?: CompleteCallback,
+    ) {
+        let args: ILoadResArgs<T> | null = null;
+        if (typeof paths === "string" || paths instanceof Array) {
+            args = this.parseLoadResArgs(paths, type, onProgress, onComplete);
+            args.bundle = bundleName;
+        }
+        else {
+            args = this.parseLoadResArgs(bundleName, paths, type, onProgress);
+            args.bundle = this.defaultBundleName;
+        }
+        this.loadByArgs(args);
+    }
+
+    /**
+     * 异步加载一个资源
+     * @param bundleName    远程包名
+     * @param paths         资源路径
+     * @param type          资源类型
+     */
+    loadAsync<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>): Promise<T>;
+    loadAsync<T extends Asset>(bundleName: string, paths: Paths): Promise<T>;
+    loadAsync<T extends Asset>(paths: Paths, type: AssetType<T>): Promise<T>;
+    loadAsync<T extends Asset>(paths: Paths): Promise<T>;
+    loadAsync<T extends Asset>(bundleName: string,
+        paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback): Promise<T> {
+        return new Promise((resolve, reject) => {
+            this.load(bundleName, paths, type, (err: Error | null, asset: T) => {
+                if (err) {
+                    warn(err.message);
+                }
+                resolve(asset);
+            });
+        });
+    }
+
+    /**
+     * 加载文件夹中的资源
+     * @param bundleName    远程包名
+     * @param dir           文件夹名
+     * @param type          资源类型
+     * @param onProgress    加载进度回调
+     * @param onComplete    加载完成回调
+     * @example
+// 加载进度事件
+var onProgressCallback = (finished: number, total: number, item: any) => {
+    console.log("资源加载进度", finished, total);
+}
+
+// 加载完成事件
+var onCompleteCallback = () => {
+    console.log("资源加载完成");
+}
+oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
+     */
+    loadDir<T extends Asset>(bundleName: string, dir: string, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    loadDir<T extends Asset>(bundleName: string, dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    loadDir<T extends Asset>(bundleName: string, dir: string, onComplete?: CompleteCallback): void;
+    loadDir<T extends Asset>(bundleName: string, dir: string, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    loadDir<T extends Asset>(dir: string, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    loadDir<T extends Asset>(dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
+    loadDir<T extends Asset>(dir: string, onComplete?: CompleteCallback): void;
+    loadDir<T extends Asset>(dir: string, type: AssetType<T>, onComplete?: CompleteCallback): void;
+    loadDir<T extends Asset>(
+        bundleName: string,
+        dir?: string | AssetType<T> | ProgressCallback | CompleteCallback,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback,
+        onProgress?: ProgressCallback | CompleteCallback,
+        onComplete?: CompleteCallback,
+    ) {
+        let args: ILoadResArgs<T> | null = null;
+        if (typeof dir === "string") {
+            args = this.parseLoadResArgs(dir, type, onProgress, onComplete);
+            args.bundle = bundleName;
+        }
+        else {
+            args = this.parseLoadResArgs(bundleName, dir, type, onProgress);
+            args.bundle = this.defaultBundleName;
+        }
+        args.dir = args.paths as string;
+        this.loadByArgs(args);
+    }
+
+    /**
+     * 通过资源相对路径释放资源
+     * @param path          资源路径
+     * @param bundleName    远程资源包名
+     */
+    release(path: string, bundleName: string = this.defaultBundleName) {
+        const bundle = assetManager.getBundle(bundleName);
+        if (bundle) {
+            const asset = bundle.get(path);
+            if (asset) {
+                this.releasePrefabtDepsRecursively(asset);
+            }
+        }
+    }
+
+    /**
+     * 通过相对文件夹路径删除所有文件夹中资源
+     * @param path          资源文件夹路径
+     * @param bundleName    远程资源包名
+     */
+    releaseDir(path: string, bundleName: string = this.defaultBundleName) {
+        const bundle: AssetManager.Bundle | null = assetManager.getBundle(bundleName);
+        if (bundle) {
+            var infos = bundle.getDirWithPath(path);
+            if (infos) {
+                infos.map((info) => {
+                    this.releasePrefabtDepsRecursively(info.uuid);
+                });
+            }
+
+            if (path == "" && bundleName != "resources") {
+                assetManager.removeBundle(bundle);
+            }
+        }
+    }
+
+    /** 释放预制依赖资源 */
+    private releasePrefabtDepsRecursively(uuid: string | Asset) {
+        if (uuid instanceof Asset) {
+            uuid.decRef();
+            // assetManager.releaseAsset(uuid);
+        }
+        else {
+            const asset = assetManager.assets.get(uuid);
+            if (asset) {
+                asset.decRef();
+                // assetManager.releaseAsset(asset);
+            }
+        }
+    }
+
+    /**
+     * 获取资源
+     * @param path          资源路径
+     * @param type          资源类型
+     * @param bundleName    远程资源包名
+     */
+    get<T extends Asset>(path: string, type?: AssetType<T>, bundleName: string = this.defaultBundleName): T | null {
+        var bundle: AssetManager.Bundle = assetManager.getBundle(bundleName)!;
+        return bundle.get(path, type);
+    }
+    //#endregion
+
+    private parseLoadResArgs<T extends Asset>(
+        paths: Paths,
+        type?: AssetType<T> | ProgressCallback | CompleteCallback,
+        onProgress?: AssetType<T> | ProgressCallback | CompleteCallback,
+        onComplete?: ProgressCallback | CompleteCallback
+    ) {
+        let pathsOut: any = paths;
+        let typeOut: any = type;
+        let onProgressOut: any = onProgress;
+        let onCompleteOut: any = onComplete;
+        if (onComplete === undefined) {
+            const isValidType = js.isChildClassOf(type as AssetType, Asset);
+            if (onProgress) {
+                onCompleteOut = onProgress as CompleteCallback;
+                if (isValidType) {
+                    onProgressOut = null;
+                }
+            }
+            else if (onProgress === undefined && !isValidType) {
+                onCompleteOut = type as CompleteCallback;
+                onProgressOut = null;
+                typeOut = null;
+            }
+            if (onProgress !== undefined && !isValidType) {
+                onProgressOut = type as ProgressCallback;
+                typeOut = null;
+            }
+        }
+        return { paths: pathsOut, type: typeOut, onProgress: onProgressOut, onComplete: onCompleteOut };
+    }
+
+    private loadByBundleAndArgs<T extends Asset>(bundle: AssetManager.Bundle, args: ILoadResArgs<T>): void {
+        if (args.dir) {
+            if (args.preload) {
+                bundle.preloadDir(args.paths as string, args.type, args.onProgress, args.onComplete);
+            }
+            else {
+                bundle.loadDir(args.paths as string, args.type, args.onProgress, args.onComplete);
+            }
+        }
+        else {
+            if (args.preload) {
+                bundle.preload(args.paths as any, args.type, args.onProgress, args.onComplete);
+            }
+            else {
+                bundle.load(args.paths as any, args.type, args.onProgress, args.onComplete);
+            }
+        }
+    }
+
+    private async loadByArgs<T extends Asset>(args: ILoadResArgs<T>) {
+        if (args.bundle) {
+            let bundle = assetManager.bundles.get(args.bundle);
+            // 获取缓存中的资源包
+            if (bundle) {
+                this.loadByBundleAndArgs(bundle, args);
+            }
+            // 自动加载资源包
+            else {
+                const v = this.cdn ? this.bundles.get(args.bundle) : "";
+                bundle = await this.loadBundle(args.bundle, v);
+                if (bundle) this.loadByBundleAndArgs(bundle, args);
+            }
+        }
+        // 默认资源包
+        else {
+            this.loadByBundleAndArgs(resources, args);
+        }
+    }
+
+    /** 打印缓存中所有资源信息 */
+    dump() {
+        assetManager.assets.forEach((value: Asset, key: string) => {
+            console.log(assetManager.assets.get(key));
+        })
+        console.log(`当前资源总数:${assetManager.assets.count}`);
+    }
+}
+
+export const resLoader = new ResLoader();

+ 9 - 0
assets/core_tgx/base/ResLoader.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "95cd8d48-d370-4030-9542-220868955a12",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 43 - 0
assets/core_tgx/base/ResolutionAutoFit.ts

@@ -0,0 +1,43 @@
+import { _decorator, Component, view, screen, Size, size, ResolutionPolicy } from 'cc';
+const { ccclass, property } = _decorator;
+
+const CHECK_INTERVAL = 0.1;
+
+@ccclass('tgxResolutionAutoFit')
+export class ResolutionAutoFit extends Component {
+    private _oldSize:Size = size();
+    start() {
+        this.adjustResolutionPolicy();
+    }
+
+    private lastCheckTime = 0;
+    update(deltaTime: number) {
+        this.lastCheckTime+=deltaTime;
+        if(this.lastCheckTime < CHECK_INTERVAL){
+            return;
+        }
+        this.lastCheckTime = 0;
+
+        this.adjustResolutionPolicy();
+    }
+
+    adjustResolutionPolicy(){
+        let winSize = screen.windowSize;
+        if(!this._oldSize.equals(winSize)){
+            let ratio = winSize.width / winSize.height;
+            let drs = view.getDesignResolutionSize();
+            let drsRatio = drs.width / drs.height;
+
+            if(ratio > drsRatio){
+                //wider than desgin. fixed height
+                view.setResolutionPolicy(ResolutionPolicy.FIXED_HEIGHT);
+            }
+            else{
+                //
+                view.setResolutionPolicy(ResolutionPolicy.FIXED_WIDTH);
+            }
+            this._oldSize.set(winSize);
+        }
+    }
+}
+

+ 9 - 0
assets/core_tgx/base/ResolutionAutoFit.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "e5d8a223-5fd3-4ede-8c77-28c5930dbb00",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 49 - 0
assets/core_tgx/base/ResourceMgr.ts

@@ -0,0 +1,49 @@
+import { loader, Constructor, resources, Asset } from "cc";
+
+class ResItem {
+    public url: string;
+    public isLoading = false;
+    public callbackArr = [];
+}
+
+export class ResourceMgr {
+    private static _inst: ResourceMgr = null;
+    public static get inst(): ResourceMgr {
+        if (!this._inst) {
+            this._inst = new ResourceMgr();
+        }
+        return this._inst;
+    }
+
+    private loadingQueue:[] = [];
+
+    public loadRes<T>(url: string, type: any, callback: (err, assets: T) => void) {
+        let cache = resources.get(url,type) as any;
+        if(cache){
+            if(callback){
+                setTimeout(()=>{
+                    callback(null,cache);
+                },10);
+            }
+            return;
+        }
+        let loadingItem:ResItem = this.loadingQueue[url];
+        if(!loadingItem){
+            loadingItem = this.loadingQueue[url] = new ResItem();
+            loadingItem.url = url;
+        }
+        loadingItem.callbackArr.push(callback);
+        if(!loadingItem.isLoading){
+            loadingItem.isLoading = true;
+            resources.load(url, type, (err,asset:Asset)=>{
+                delete this.loadingQueue[url];
+                for(let k in loadingItem.callbackArr){
+                    let cb = loadingItem.callbackArr[k];
+                    if(cb){
+                        cb(err,asset);
+                    }
+                }
+            });
+        }
+    }
+}

+ 13 - 0
assets/core_tgx/base/ResourceMgr.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "b666c2cf-c69b-456e-adc2-73d54d34f216",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/ResourceMgr.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 22 - 0
assets/core_tgx/base/SafeJSON.ts

@@ -0,0 +1,22 @@
+/**
+ * @en call JSON mthods safely.
+ * @zh 用于安全操作JSON相关函数
+ * */
+export class SafeJSON {
+    public static parse(text: string, reciver?: (key: any, value: any) => any): any {
+        try {
+            return JSON.parse(text, reciver);
+        } catch (error) {
+            console.log(error);
+            return null;
+        }
+    }
+
+    public static stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number) {
+        try {
+            return JSON.stringify(value, replacer, space);
+        } catch (error) {
+            return null;
+        }
+    }
+}

+ 13 - 0
assets/core_tgx/base/SafeJSON.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2aad897f-5b30-4f4c-886b-d56e9207d3ed",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/SafeJSON.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 9 - 0
assets/core_tgx/base/ad.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "b3bdd776-7643-4fcd-a569-826e1e9cf4c2",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/core_tgx/base/ad/ADEvent.ts

@@ -0,0 +1,11 @@
+/** 广告事件*/
+export class ADEvent {
+    /** 激励广告显示播放*/
+    static readonly REWARD_VIDEO_PLAY = "REWARD_VIDEO_PLAY";
+    /** 激励广告玩家手动关闭*/
+    static readonly REWARD_VIDEO_DISMISSED = "REWARD_VIDEO_DISMISSED";
+    /** 激励广告播放完成关闭*/
+    static readonly REWARD_VIDEO_CLOSEED = "REWARD_VIDEO_CLOSEED";
+    /** 激励广告错误*/
+    static readonly REWARD_VIDEO_ERROR = "REWARD_VIDEO_ERROR";
+}

+ 9 - 0
assets/core_tgx/base/ad/ADEvent.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6430b798-0d28-4b2f-8778-ea8831ecb114",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 81 - 0
assets/core_tgx/base/ad/AdvertMgr.ts

@@ -0,0 +1,81 @@
+import { _decorator, Node, Prefab, instantiate, Component, Camera, UITransform, v3, game, view, screen, tween, Vec3 } from 'cc';
+import { EventDispatcher } from '../../easy_ui_framework/EventDispatcher';
+import { ADEvent } from './ADEvent';
+import { AudioMgr } from '../AudioMgr';
+import { tgxUITips } from 'db://assets/core_tgx/tgx';
+import { GtagMgr, GtagType } from 'db://assets/core_tgx/base/GtagMgr';
+const { ccclass, property } = _decorator;
+
+/** 广告管理*/
+@ccclass('AdvertMgr')
+export class AdvertMgr {
+    private static _instance: AdvertMgr | null = null;
+    public static get instance(): AdvertMgr {
+        if (!this._instance) this._instance = new AdvertMgr();
+        return this._instance;
+    }
+
+    adInstance: any = null;
+    gtag: any = null;
+
+    initilize(): void {
+        this.adInstance = (window as any)['adInstance'];
+        console.log('ad sdk初始化');
+        console.log(this.adInstance);
+    }
+
+    /** 显示插屏广告*/
+    showInterstitial(cb?: () => void): void {
+        if (!this.adInstance) {
+            cb && cb();
+            return;
+        }
+
+        this.adInstance.interstitialAd({
+            beforeAd() {
+                console.log('The ad starts playing');
+            },
+            afterAd() {
+                console.log('The ad ends playing');
+            },
+            error(err) {
+                // console.log('The ad failed to load');
+                tgxUITips.show(err)
+            }
+        });
+    }
+
+    /** 显示激励广告*/
+    showReawardVideo(cb?: () => void): void {
+        if (!this.adInstance) {
+            cb && cb();
+            return;
+        }
+
+        this.adInstance.rewardAd({
+            beforeAd() {
+                console.log('The ad starts playing, and the game should pause.');
+                EventDispatcher.instance.emit(ADEvent.REWARD_VIDEO_PLAY);
+                AudioMgr.inst.pause();
+            },
+            adDismissed() {
+                console.log('Player dismissed the ad before completion.');
+                EventDispatcher.instance.emit(ADEvent.REWARD_VIDEO_DISMISSED);
+                AudioMgr.inst.resume();
+            },
+            adViewed() {
+                console.log('Ad was viewed and closed.');
+                if (cb) cb();
+                EventDispatcher.instance.emit(ADEvent.REWARD_VIDEO_CLOSEED);
+                AudioMgr.inst.resume();
+            },
+            error(err: any) {
+                // console.log(`激励广告错误:${err}`);
+                tgxUITips.show(err);
+                EventDispatcher.instance.emit(ADEvent.REWARD_VIDEO_ERROR);
+                AudioMgr.inst.resume();
+                GtagMgr.inst.doGameDot(GtagType.ad_error);
+            }
+        });
+    }
+}

+ 9 - 0
assets/core_tgx/base/ad/AdvertMgr.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0d92a61d-636e-4b85-bd6a-a19d42db36d4",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/base/utils.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "d547fbb7-19f8-413d-8616-3df13d671f2b",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 109 - 0
assets/core_tgx/base/utils/JsonUtil.ts

@@ -0,0 +1,109 @@
+/*
+ * @Author: dgflash
+ * @Date: 2021-08-18 17:00:59
+ * @LastEditors: dgflash
+ * @LastEditTime: 2023-08-22 15:48:02
+ */
+
+import { JsonAsset } from "cc";
+import { ResLoader, resLoader } from "../ResLoader";
+
+/** 资源路径 */
+const path: string = "config/";
+
+/** 数据缓存 */
+const data: Map<string, any> = new Map();
+
+/** JSON数据表工具 */
+export class JsonUtil {
+    /**
+     * 通知资源名从缓存中获取一个Json数据表
+     * @param name  资源名
+     */
+    static get(name: string): any {
+        if (data.has(name))
+            return data.get(name);
+    }
+
+    /**
+     * 通知资源名加载Json数据表
+     * @param name      资源名
+     * @param callback  资源加载完成回调
+     */
+    static load(name: string, callback: Function): void {
+        if (data.has(name))
+            callback(data.get(name));
+        else {
+            const url = path + name;
+            resLoader.load(url, JsonAsset, (err: Error | null, content: JsonAsset) => {
+                if (err) {
+                    console.warn(err.message);
+                    callback(null);
+                }
+                else {
+                    data.set(name, content.json);
+                    resLoader.release(url);
+                    callback(content.json);
+                }
+            });
+        }
+    }
+
+    /**
+     * 异步加载Json数据表
+     * @param name 资源名
+     */
+    static loadAsync(name: string): Promise<any> {
+        return new Promise((resolve, reject) => {
+            if (data.has(name)) {
+                resolve(data.get(name))
+            }
+            else {
+                const url = path + name;
+                resLoader.load(url, JsonAsset, (err: Error | null, content: JsonAsset) => {
+                    if (err) {
+                        console.warn(err.message);
+                        resolve(null);
+                    }
+                    else {
+                        data.set(name, content.json);
+                        resLoader.release(url);
+                        resolve(content.json);
+                    }
+                });
+            }
+        });
+    }
+
+    /** 加载所有配置表数据到缓存中 */
+    static loadDirAsync(): Promise<boolean> {
+        return new Promise((resolve, reject) => {
+            resLoader.loadDir(path, (err: Error | null, assets: JsonAsset[]) => {
+                if (err) {
+                    console.warn(err.message);
+                    resolve(false);
+                }
+                else {
+                    assets.forEach(asset => {
+                        data.set(asset.name, asset.json);
+                    });
+                    resLoader.releaseDir(path);
+                    resolve(true);
+                }
+            });
+        });
+    }
+
+    /**
+     * 通过指定资源名释放资源内存
+     * @param name 资源名
+     */
+    static release(name: string) {
+        data.delete(name);
+    }
+
+    /** 清理所有数据 */
+    static clear() {
+        data.clear();
+    }
+}

+ 9 - 0
assets/core_tgx/base/utils/JsonUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "98ab3a4b-8beb-4de0-b746-5576bb95fd44",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/easy_adapter.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "7574c5d2-2abc-4dcb-ba37-6488a799d632",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 253 - 0
assets/core_tgx/easy_adapter/Adapter.ts

@@ -0,0 +1,253 @@
+
+import { _decorator, Component, UITransform, size, Size, screen, view, v4 } from 'cc';
+import { EDITOR, JSB } from 'cc/env';
+const { ccclass, property } = _decorator;
+/**
+ * @description 该适配方案参考 https://forum.cocos.org/t/cocos-creator/74001
+ */
+
+/**
+ * 屏幕分辨率下 的像素值
+ */
+interface SafeArea {
+    /**
+     * 屏幕分辨率下:画布(屏幕)宽度
+     */
+    width: number;
+
+    /**
+     * 屏幕分辨率下:画布(屏幕)高度
+     */
+    height: number;
+
+    /**@description 屏幕分辨率下:安全区域大小(像素) */
+    safe: Size;
+
+    /**@description 「设计分辨率」 非安全区域的大小(像素)即刘海单边的大小 */
+    outside: Size;
+
+    /**
+     * 「设计分辨率」像素值转换到 「屏幕分辨率」 下的像素比
+     *
+     * e.g.
+     *
+     * * screenPx = designPx * pixelRatio
+     * * designPx = screenPx / pixelRatio
+     */
+    designPxToScreenPxRatio: number;
+}
+
+/**@description 设备方向 */
+enum DeviceDirection {
+    /**@description 未知*/
+    Unknown,
+    /**@description 横屏(即摄像头向左) */
+    LandscapeLeft,
+    /**@description 横屏(即摄像头向右) */
+    LandscapeRight,
+    /**@description 竖屏(即摄像头向上) */
+    Portrait,
+    /**@description 竖屏(即摄像头向下) */
+    UpsideDown,
+}
+
+let SAFE_SIZE = size(0, 0);
+let OUTSIDE_SIZE = size(0, 0);
+export class Adapter extends Component {
+
+    static direction = DeviceDirection;
+
+    protected set width(value: number) {
+        let trans = this.getComponent(UITransform);
+        if (!trans) {
+            return;
+        }
+        trans.width = value;
+    }
+    protected get width() {
+        let trans = this.getComponent(UITransform);
+        if (!trans) {
+            return 0;
+        }
+        return trans.width;
+    }
+
+    protected set height(value: number) {
+        let trans = this.getComponent(UITransform);
+        if (!trans) {
+            return;
+        }
+        trans.height = value;
+    }
+
+    protected get height() {
+        let trans = this.getComponent(UITransform);
+        if (!trans) {
+            return 0;
+        }
+        return trans.height;
+    }
+
+    protected static get windowSize() {
+        if (EDITOR) {
+            return view.getDesignResolutionSize();
+        }
+        return screen.windowSize;
+    }
+
+    protected static get visibleSize() {
+        return view.getVisibleSize();
+    }
+
+    protected _func: any = null;
+
+    /**@description 适配完成回调 */
+    onAdapterComplete: () => void = null!;
+
+    protected doOnAdapterComplete() {
+        if (this.onAdapterComplete) {
+            this.onAdapterComplete();
+        }
+    }
+
+    onLoad() {
+        super.onLoad && super.onLoad();
+        this.onChangeSize();
+    }
+
+    onEnable() {
+        super.onEnable && super.onEnable();
+        this.addEvents();
+    }
+
+    onDisable() {
+        this.removeEvents();
+        super.onDisable && super.onDisable();
+    }
+
+    onDestroy() {
+        this.removeEvents();
+        super.onDestroy && super.onDestroy();
+    }
+
+    protected addEvents() {
+        if (this._func) {
+            return;
+        }
+        this._func = this.onChangeSize.bind(this);
+        window.addEventListener("resize", this._func);
+        window.addEventListener("orientationchange", this._func);
+    }
+
+    protected removeEvents() {
+        if (this._func) {
+            window.removeEventListener("resize", this._func);
+            window.removeEventListener("orientationchange", this._func);
+        }
+        this._func = null;
+    }
+
+    /**
+     * @description 视图发生大小变化
+     */
+    protected onChangeSize() {
+        this.doOnAdapterComplete();
+    }
+
+    /**@description 获取当前设备方向 */
+    get direction() {
+        let str = "未知"
+        let result = DeviceDirection.Unknown;
+        let orientation = window.orientation;
+        if (JSB) {
+            orientation = jsb.device.getDeviceOrientation();
+        }
+        if (orientation != undefined || orientation != null) {
+            if (orientation == 90) {
+                str = `横屏向左`
+                result = DeviceDirection.LandscapeLeft;
+            } else if (orientation == -90) {
+                str = `横屏向右`
+                result = DeviceDirection.LandscapeRight;
+            } else if (orientation == 0) {
+                str = "竖屏向上"
+                result = DeviceDirection.Portrait;
+            } else if (orientation == 180) {
+                str = "竖屏向下"
+                result = DeviceDirection.UpsideDown;
+            }
+        }
+        // console.log(`当前设备方向:${str}`)
+        return result;
+    }
+
+    static get safeAreaEdge() {
+        if (JSB) {
+            return jsb.device.getSafeAreaEdge();
+        } else {
+            return v4(0, 0, 0, 0);
+        }
+    }
+
+    private static _safeArea: SafeArea = null!;
+
+    static set safeArea(value: SafeArea) {
+        this._safeArea = value as any;
+    }
+
+    static screenPxToDesignPx(screenPx: number) {
+        return screenPx / this.safeArea.designPxToScreenPxRatio;
+    }
+
+    static designPxToScreenPx(designPx: number) {
+        return designPx * this.safeArea.designPxToScreenPxRatio;
+    }
+
+    /**
+     * 基于屏幕尺寸的安全区域
+     *
+     * 可以通过 screenPxToDesignPx 转换为基于设计画布尺寸的像素大小
+     */
+    static get safeArea() {
+        if (this._safeArea == null || this._safeArea == undefined) {
+            // 初始屏幕宽高像素
+            let screenWidth = Adapter.windowSize.width;
+            let screenHeight = Adapter.windowSize.height;
+            // 「设计分辨率」像素值转换到 「屏幕分辨率」 下的像素比
+            let designWidth = Adapter.visibleSize.width;
+            let designHeight = Adapter.visibleSize.height;
+            let designPxToScreenPxRatio = Math.min(screenWidth / designWidth, screenHeight / designHeight);
+
+            OUTSIDE_SIZE.width = 0
+            OUTSIDE_SIZE.height = 0
+            SAFE_SIZE.width = screenWidth - OUTSIDE_SIZE.width;
+            SAFE_SIZE.height = screenHeight - OUTSIDE_SIZE.height
+            if (JSB) {
+                OUTSIDE_SIZE.width = (this.safeAreaEdge.y + this.safeAreaEdge.w)
+                OUTSIDE_SIZE.height = (this.safeAreaEdge.x + this.safeAreaEdge.z)
+                SAFE_SIZE.width = screenWidth - OUTSIDE_SIZE.width;
+                SAFE_SIZE.height = screenHeight - OUTSIDE_SIZE.height
+                OUTSIDE_SIZE.width = OUTSIDE_SIZE.width / 2
+                OUTSIDE_SIZE.height = OUTSIDE_SIZE.height / 2
+            }
+
+            this._safeArea = {
+                width: screenWidth,
+                height: screenHeight,
+                outside: OUTSIDE_SIZE,
+                safe: SAFE_SIZE,
+                designPxToScreenPxRatio: designPxToScreenPxRatio,
+            };
+        }
+        return this._safeArea;
+    }
+
+    private _isFullScreenAdaption = true;
+    /** 是否全屏*/
+    public get isFullScreenAdaption() {
+        return this._isFullScreenAdaption;
+    }
+    public set isFullScreenAdaption(value) {
+        this._isFullScreenAdaption = value;
+    }
+}

+ 9 - 0
assets/core_tgx/easy_adapter/Adapter.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "adb5f3ab-9cb3-4054-803f-fc266817f3e5",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 234 - 0
assets/core_tgx/easy_adapter/AdapterSafeArea.ts

@@ -0,0 +1,234 @@
+import { Widget, _decorator } from "cc";
+import { EDITOR } from "cc/env";
+import { Adapter } from "./Adapter";
+
+const { ccclass, property, executeInEditMode, menu } = _decorator;
+
+enum Flags {
+    None = 0,
+    TOP = 1 << 0,
+    BOTTOM = 1 << 1,
+    LEFT = 1 << 2,
+    RIGHT = 1 << 3,
+}
+
+/**
+ * @classdesc  安全区域适配Widget , App.isFullScreenAdaption = true 时有效
+ * @description
+ *
+ * 用法:
+ *
+ * 1. 将本组件挂载在节点上即可(注意:该节点上必须挂在 Widget 组件)
+ *
+ * 适配原理:
+ *
+ * 1. 根据安全区域范围,修改widget组件属性
+ * 自动添加刘海宽度,以避免显示到安全区域之外
+ */
+@ccclass("AdapterSafeArea")
+@executeInEditMode
+@menu("Quick适配组件/AdapterSafeArea")
+export default class AdapterSafeArea extends Adapter {
+
+    @property
+    protected _flags: number = Flags.None;
+
+    protected setFlag(flag: Flags, value: boolean) {
+        const current = (this._flags & flag) > 0;
+        if (value == current) {
+            return;
+        }
+        if (value) {
+            this._flags |= flag;
+        } else {
+            this._flags &= ~flag;
+        }
+        this._isDirty = true;
+    }
+
+    @property({ tooltip: EDITOR ? "是否对齐上边" : "" })
+    get isAlignTop() {
+        return (this._flags & Flags.TOP) > 0;
+    }
+    set isAlignTop(v) {
+        this.setFlag(Flags.TOP, v);
+    }
+
+    @property({ tooltip: EDITOR ? "是否对齐下边" : "" })
+    get isAlignBottom() {
+        return (this._flags & Flags.BOTTOM) > 0;
+    }
+    set isAlignBottom(v) {
+        this.setFlag(Flags.BOTTOM, v);
+    }
+
+    @property({ tooltip: EDITOR ? "是否对齐左边" : "" })
+    get isAlignLeft() {
+        return (this._flags & Flags.LEFT) > 0;
+    }
+    set isAlignLeft(v) {
+        this.setFlag(Flags.LEFT, v);
+    }
+
+    @property({ tooltip: EDITOR ? "是否对齐右边" : "" })
+    get isAlignRight() {
+        return (this._flags & Flags.RIGHT) > 0;
+    }
+    set isAlignRight(v) {
+        this.setFlag(Flags.RIGHT, v);
+    }
+
+    @property
+    _top = 0;
+    @property({
+        visible: function (this: AdapterSafeArea) {
+            return this.isAlignTop;
+        }, tooltip: EDITOR ? "本节点顶边和父节点顶边的距离,可填写负值,只有在 isAlignTop 开启时才有作用" : ""
+    })
+    get top() {
+        return this._top;
+    }
+    set top(v) {
+        if (this._top == v) {
+            return;
+        }
+        this._top = v;
+        this._isDirty = true;
+    }
+
+    @property
+    _bottom = 0;
+    @property({
+        visible: function (this: AdapterSafeArea) {
+            return this.isAlignBottom;
+        }, tooltip: EDITOR ? "本节点顶边和父节点底边的距离,可填写负值,只有在 isAlignBottom 开启时才有作用" : ""
+    })
+    get bottom() {
+        return this._bottom;
+    }
+    set bottom(v) {
+        if (this._bottom == v) {
+            return;
+        }
+        this._bottom = v;
+        this._isDirty = true;
+    }
+
+    @property
+    _left = 0;
+    @property({
+        visible: function (this: AdapterSafeArea) {
+            return this.isAlignLeft;
+        }, tooltip: EDITOR ? "本节点顶边和父节点左边的距离,可填写负值,只有在 isAlignLeft 开启时才有作用" : ""
+    })
+    get left() {
+        return this._left;
+    }
+    set left(v) {
+        if (this._left == v) {
+            return;
+        }
+        this._left = v;
+        this._isDirty = true;
+    }
+
+    @property
+    _right = 0;
+    @property({
+        visible: function (this: AdapterSafeArea) {
+            return this.isAlignRight;
+        }, tooltip: EDITOR ? "本节点顶边和父节点右边的距离,可填写负值,只有在 isAlignRight 开启时才有作用" : ""
+    })
+    get right() {
+        return this._right;
+    }
+    set right(v) {
+        if (this._right == v) {
+            return;
+        }
+        this._right = v;
+        this._isDirty = true;
+    }
+
+    protected _isDirty = false;
+
+    protected get widget() {
+        let comp = this.getComponent(Widget);
+        if (comp) {
+            return comp;
+        }
+        return this.addComponent(Widget);
+    }
+
+    resetInEditor() {
+        this.doLayout(true);
+    }
+
+    protected doLayout(isForce = false) {
+        if (this._isDirty || isForce) {
+            let widget = this.widget!;
+            if (EDITOR) {
+                widget.left = this.left;
+                widget.right = this.right;
+                widget.top = this.top;
+                widget.bottom = this.bottom;
+                return;
+            }
+            if (!this.isFullScreenAdaption) {
+                return;
+            }
+            if (!widget || !widget.enabled) {
+                return;
+            }
+            // 屏幕向上时,加上安全区域高度
+            if (widget.isAlignTop && this.isAlignTop) {
+                widget.isAbsoluteTop = true;
+                if (this.direction == Adapter.direction.Portrait) {
+                    widget.top = this.top + Adapter.safeArea.outside.height;
+                } else {
+                    widget.top = this.top;
+                }
+            }
+            // 屏幕向下时,加上安全区域高度
+            if (widget.isAlignBottom && this.isAlignBottom) {
+                widget.isAbsoluteBottom = true;
+                if (this.direction == Adapter.direction.UpsideDown) {
+                    widget.bottom = this.bottom + Adapter.safeArea.outside.height;
+                } else {
+                    widget.bottom = this.bottom;
+                }
+            }
+            // 屏幕向左时,加上安全区域宽度
+            if (widget.isAlignLeft && this.isAlignLeft) {
+                widget.isAbsoluteLeft = true;
+                if (this.direction == Adapter.direction.LandscapeLeft) {
+                    widget.left = this.left + Adapter.safeArea.outside.width;
+                } else {
+                    widget.left = this.left;
+                }
+
+            }
+            // 屏幕向右时,加上安全区域宽度
+            if (widget.isAlignRight && this.isAlignRight) {
+                widget.isAbsoluteRight = true;
+                if (this.direction == Adapter.direction.LandscapeRight) {
+                    widget.right = this.right + Adapter.safeArea.outside.width;
+                } else {
+                    widget.right = this.right;
+                }
+            }
+            widget.updateAlignment();
+            this._isDirty = false;
+            this.doOnAdapterComplete();
+        }
+    }
+
+    protected onChangeSize() {
+        this.doLayout(true);
+    }
+
+    protected update(dt: number) {
+        super.update && super.update(dt);
+        this.doLayout();
+    }
+}

+ 9 - 0
assets/core_tgx/easy_adapter/AdapterSafeArea.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "dca7253a-8e54-4c44-b022-3b5b7eafb663",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 167 - 0
assets/core_tgx/easy_adapter/AdapterSprite.ts

@@ -0,0 +1,167 @@
+import { Enum, Sprite, UITransform, v3, Widget, _decorator } from "cc";
+import { EDITOR } from "cc/env";
+import { Adapter } from "./Adapter";
+
+const { ccclass, property , executeInEditMode,menu} = _decorator;
+
+/**
+ * 缩放方式
+ */
+export enum SpriteScaleType {
+    /**
+     * 缩放到填满父节点(如果父节点有裁剪,图像可能会被裁剪,节点可能会超出父节点)
+     */
+    FILL,
+
+    /**
+     * 缩放到刚好在父节点内部最大化显示(图像会完整显示,但父节点上下或者左右可能会留空)
+     */
+    SUIT,
+}
+
+/**
+ * 对齐方式
+ */
+export enum SpriteAlignType {
+    /**
+     * 缩放后靠左对齐
+     */
+    LEFT,
+
+    /**
+     * 缩放后靠上对齐
+     */
+    TOP,
+
+    /**
+     * 缩放后靠右对齐
+     */
+    RIGHT,
+
+    /**
+     * 缩放后靠下对齐
+     */
+    BOTTOM,
+
+    /**
+     * 缩放后居中对齐
+     */
+    CENTER,
+}
+
+/**
+ * Sprite 适配组件
+ *
+ * @author caizhitao
+ * @created 2020-12-27 21:22:43
+ */
+@ccclass("AdapterSprite")
+@executeInEditMode(true)
+@menu("Quick适配组件/AdapterSprite")
+export default class AdapterSprite extends Adapter {
+    @property({
+        type: Enum(SpriteScaleType),
+        tooltip: `缩放类型:
+        -FILL: 缩放到填满父节点(如果父节点有裁剪,图像可能会被裁剪,节点可能会超出父节点)
+        -SUIT: 缩放到刚好在父节点内部最大化显示(图像会完整显示,但父节点上下或者左右可能会留空)`,
+    })
+    get scaleType(){
+        return this._scaleType;
+    }
+    set scaleType(value){
+        this._scaleType = value;
+        if ( EDITOR ){
+            this.updateSprite(this._scaleType,this.alignType);
+        }
+    }
+    private _scaleType: SpriteScaleType = SpriteScaleType.SUIT;
+
+    @property({
+        type: Enum(SpriteAlignType),
+        tooltip: `齐方式类型:
+        -LEFT: 缩放后靠左对齐
+        -TOP: 缩放后靠上对齐
+        -RIGHT: 缩放后靠右对齐
+        -BOTTOM: 缩放后靠下对齐
+        -CENTER: 缩放后居中对齐`,
+    })
+    get alignType(){
+        return this._alignType;
+    }
+    set alignType(value){
+        this._alignType = value;
+        if ( EDITOR ){
+            this.updateSprite(this._scaleType,this._alignType);
+        }
+    }
+    private _alignType: SpriteAlignType = SpriteAlignType.CENTER;
+
+    private _sprite: Sprite = null!;
+
+    onLoad() {
+        this._sprite = this.node.getComponent(Sprite) as Sprite;
+    }
+
+    start() {
+        this.updateSprite(this.scaleType, this.alignType);
+    }
+
+    protected onChangeSize() {
+        this.updateSprite(this.scaleType, this.alignType);
+    }
+
+    updateSprite(scaleType: SpriteScaleType, alignType: SpriteAlignType) {
+        if (!this._sprite || !this._sprite.enabled || !this._sprite.spriteFrame) {
+            return;
+        }
+        let widget = this.node.parent?.getComponent(Widget);
+        if (widget) {
+            widget.updateAlignment();
+        }
+        this.width = this._sprite.spriteFrame.rect.width;
+        this.height = this._sprite.spriteFrame.rect.height;
+        let trans = this.parentTrans;
+        if (this.width / this.height > trans.width / trans.height) {
+            // 设计分辨率宽高比大于显示分辨率
+            if (scaleType == SpriteScaleType.SUIT) {
+                let scale = trans.width / this.width;
+                this.node.scale = v3(scale,scale);
+            } else if (scaleType == SpriteScaleType.FILL) {
+                let scale = trans.height / this.height;
+                this.node.scale = v3(scale,scale);
+            }
+        } else {
+            // 设计分辨率宽高比小于显示分辨率
+            if (scaleType == SpriteScaleType.SUIT) {
+                let scale = trans.height / this.height;
+                this.node.scale = v3(scale,scale);
+            } else if (scaleType == SpriteScaleType.FILL) {
+                let scale = trans.width / this.width;
+                this.node.scale = v3(scale,scale);
+            }
+        }
+
+        switch (alignType) {
+            case SpriteAlignType.CENTER:
+                this.node.setPosition(v3());
+                break;
+            case SpriteAlignType.LEFT:
+                this.node.setPosition(v3(-0.5 * (trans.width - this.width * this.node.scale.x), 0));
+                break;
+            case SpriteAlignType.RIGHT:
+                this.node.setPosition(v3(0.5 * (trans.width - this.width * this.node.scale.x), 0));
+                break;
+            case SpriteAlignType.TOP:
+                this.node.setPosition(v3(0, 0.5 * (trans.height - this.height * this.node.scale.x)));
+                break;
+            case SpriteAlignType.BOTTOM:
+                this.node.setPosition(v3(0, -0.5 * (trans.height - this.height * this.node.scale.x)));
+                break;
+        }
+        this.doOnAdapterComplete();
+    }
+
+    private get parentTrans(){
+        return this.node.parent?.getComponent(UITransform) as UITransform
+    }
+}

+ 9 - 0
assets/core_tgx/easy_adapter/AdapterSprite.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "df9c15de-4dc5-4526-9d89-edfb1a44c73e",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 60 - 0
assets/core_tgx/easy_adapter/AdapterView.ts

@@ -0,0 +1,60 @@
+/**
+ */
+
+import { v3, _decorator, UITransform } from "cc";
+import { EDITOR } from "cc/env";
+import { Adapter } from "./Adapter";
+import { Console } from "console";
+
+const { ccclass, property, executeInEditMode, menu } = _decorator;
+/**
+ * 全屏适配
+ * 用法:
+ *
+ * 1. 将本组件挂载在节点上即可(注意该节点不能挂在 Widget 组件)
+ * 2. 3.x版本引擎旋转时不会触屏旋转事件,所以目前只能设置一个方向才是正常的
+ *
+ * 适配原理:
+ *
+ * 1. 将节点的宽高设置为安全区域的宽高
+ */
+@ccclass("AdapterView")
+@executeInEditMode(true)
+@menu("Quick适配组件/AdapterView")
+export default class AdapterView extends Adapter {
+
+    protected onChangeSize() {
+        Adapter.safeArea = null as any;
+        if (this.node) {
+            if (this.isFullScreenAdaption) {
+                // 将屏幕尺寸下的大小,转换为设计分辨率下的大小,重新给节点设置大小
+                this.width = Adapter.safeArea.width / Adapter.safeArea.designPxToScreenPxRatio;
+                this.height = Adapter.safeArea.height / Adapter.safeArea.designPxToScreenPxRatio;
+            } else {
+                // 将屏幕尺寸下的安全区域大小,转换为设计分辨率下的大小,重新给节点设置大小
+                this.width = Adapter.safeArea.safe.width / Adapter.safeArea.designPxToScreenPxRatio;
+                this.height = Adapter.safeArea.safe.height / Adapter.safeArea.designPxToScreenPxRatio;
+
+                switch (this.direction) {
+                    case Adapter.direction.LandscapeLeft:
+                        this.node.setPosition(v3(Adapter.safeArea.outside.width / Adapter.safeArea.designPxToScreenPxRatio, 0));
+                        break;
+                    case Adapter.direction.LandscapeRight:
+                        this.node.setPosition(v3(-Adapter.safeArea.outside.width / Adapter.safeArea.designPxToScreenPxRatio, 0));
+                        break;
+                    case Adapter.direction.Portrait:
+                        this.node.setPosition(v3(0, -Adapter.safeArea.outside.height / Adapter.safeArea.designPxToScreenPxRatio));
+                        break;
+                    case Adapter.direction.UpsideDown:
+                        this.node.setPosition(v3(0, Adapter.safeArea.outside.height / Adapter.safeArea.designPxToScreenPxRatio));
+                        break;
+                    default:
+                        !EDITOR && console.log('获取不到设备方向,直接居中处理');
+                        this.node.setPosition(v3(0, 0));
+                        break;
+                }
+            }
+            this.doOnAdapterComplete();
+        }
+    }
+}

+ 9 - 0
assets/core_tgx/easy_adapter/AdapterView.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "bcfe4ff8-292a-4dce-855c-287fcf2e59ab",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/easy_camera.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "9367cd25-eba1-450a-9f68-48045b442197",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 14 - 0
assets/core_tgx/easy_camera/FPSCamera.ts

@@ -0,0 +1,14 @@
+import { _decorator, Component, Node } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('tgxFPSCamera')
+export class FPSCamera extends Component {
+    start() {
+
+    }
+
+    update(deltaTime: number) {
+        
+    }
+}
+

+ 9 - 0
assets/core_tgx/easy_camera/FPSCamera.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "35e790f2-e054-4aa2-9c28-c5ba9f3d4048",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 27 - 0
assets/core_tgx/easy_camera/FollowCamera2D.ts

@@ -0,0 +1,27 @@
+import { _decorator, Component, Node, Vec3, v3, Camera } from 'cc';
+const { ccclass, property } = _decorator;
+
+const tmpV3 = v3();
+
+@ccclass('tgxFollowCamera2D')
+export class FollowCamera2D extends Component {
+    @property(Node)
+    target:Node;
+
+    @property
+    offset:Vec3 = v3();
+
+    protected _camera:Camera;
+
+    start() {
+        this._camera = this.node.getComponent(Camera);
+    }
+
+    lateUpdate(deltaTime: number) {
+        this.target.getWorldPosition(tmpV3);
+        tmpV3.add(this.offset);
+        this.node.worldPosition = tmpV3;   
+    }
+}
+
+

+ 9 - 0
assets/core_tgx/easy_camera/FollowCamera2D.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "901c8f11-ccf3-42f8-acd8-6387356f2369",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 182 - 0
assets/core_tgx/easy_camera/FreeCamera.ts

@@ -0,0 +1,182 @@
+import { _decorator, Component, Quat, Vec2, Vec3, Input, game, EventTouch, EventMouse, input, EventKeyboard, KeyCode, v2 } from 'cc';
+const { ccclass, property } = _decorator;
+
+const v2_1 = new Vec2();
+const v2_2 = new Vec2();
+const v3_1 = new Vec3();
+const qt_1 = new Quat();
+const forward = new Vec3();
+const right = new Vec3();
+
+@ccclass('tgxFreeCamera')
+export class FreeCamera extends Component {
+
+    @property
+    public moveSpeed = 1;
+
+    @property
+    public moveSpeedShiftScale = 5;
+
+    @property({ slide: true, range: [0.05, 0.5, 0.01] })
+    public damp = 0.2;
+
+    @property
+    public rotateSpeed = 1;
+
+    private _euler = new Vec3();
+    private _velocity = new Vec3();
+    private _position = new Vec3();
+    private _speedScale = 1;
+    private _eulerP = new Vec3();
+
+    public onLoad () {
+        input.on(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
+        input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
+        input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
+        input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
+        Vec3.copy(this._euler, this.node.eulerAngles);
+        Vec3.copy(this._position, this.node.getPosition());
+        Vec3.copy(this._eulerP, this.node.eulerAngles);
+
+        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
+        input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
+    }
+
+    public onDestroy () {
+        input.off(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
+        input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
+        input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
+        input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this);
+
+        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
+        input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
+    }
+
+
+    public update (dt: number) {
+        const t = Math.min(dt / this.damp, 1);
+        // position
+        Vec3.transformQuat(v3_1, this._velocity, this.node.rotation);
+        Vec3.scaleAndAdd(this._position, this._position, v3_1, this.moveSpeed * this._speedScale);
+        Vec3.lerp(v3_1, this.node.getPosition(), this._position, t);
+        this.node.setPosition(v3_1);
+
+        if(this.moveDir.lengthSqr()){
+            Vec3.transformQuat(forward, Vec3.FORWARD, this.node.rotation);
+            forward.normalize();
+            Vec3.cross(right, forward, Vec3.UP);
+            right.normalize();
+
+            Vec3.scaleAndAdd(this._position, this._position, forward, this.moveSpeed * this._speedScale * this.moveDir.z);
+            Vec3.lerp(v3_1, this.node.getPosition(), this._position, t);
+            this.node.setPosition(v3_1);
+
+            Vec3.scaleAndAdd(this._position, this._position, right, this.moveSpeed * this._speedScale * this.moveDir.x);
+            Vec3.lerp(v3_1, this.node.getPosition(), this._position, t);
+            this.node.setPosition(v3_1);
+
+            Vec3.scaleAndAdd(this._position, this._position, Vec3.UP, this.moveSpeed * this._speedScale * this.moveDir.y);
+            Vec3.lerp(v3_1, this.node.getPosition(), this._position, t);
+            this.node.setPosition(v3_1);
+        }
+
+        // rotation
+        Quat.fromEuler(qt_1, this._eulerP.x, this._eulerP.y, this._eulerP.z);
+        Quat.slerp(qt_1, this.node.rotation, qt_1, t);
+        this.node.setWorldRotationFromEuler(this._eulerP.x, this._eulerP.y, this._eulerP.z);
+    }
+
+    public onMouseWheel (e: EventMouse) {
+        const delta = -e.getScrollY() * this.moveSpeed * 0.1; // delta is positive when scroll down
+        Vec3.transformQuat(v3_1, Vec3.UNIT_Z, this.node.rotation);
+        Vec3.scaleAndAdd(this._position, this.node.position, v3_1, delta);
+    }
+    
+    public onTouchStart (e: EventTouch) {
+        if (game.canvas.requestPointerLock) { game.canvas.requestPointerLock(); }
+    }
+
+    public onTouchMove (e: EventTouch) {
+        e.getStartLocation(v2_1);
+        if (v2_1.x > game.canvas.width * 0.4) { // rotation
+            e.getDelta(v2_2);
+            this._eulerP.y -= v2_2.x * this.rotateSpeed * 0.1;
+            this._eulerP.x += v2_2.y * this.rotateSpeed * 0.1;
+        } else { // position
+            e.getDelta(v2_2);
+            this._eulerP.y -= v2_2.x * this.rotateSpeed * 0.1;
+            this._eulerP.x += v2_2.y * this.rotateSpeed * 0.1;
+        }
+    }
+
+    public onTouchEnd (e: EventTouch) {
+        if (document.exitPointerLock) { document.exitPointerLock(); }
+        e.getStartLocation(v2_1);
+        if (v2_1.x < game.canvas.width * 0.4) { // position
+            this._velocity.x = 0;
+            this._velocity.z = 0;
+        }
+    }
+
+    private keys = [];
+    // x  -1 left, +1 right   y -1 backword, +1 forward
+    private moveDir:Vec3 = new Vec3();
+    onKeyDown(event:EventKeyboard){
+        let keyCode = event.keyCode;
+        if(keyCode == KeyCode.KEY_A || keyCode == KeyCode.KEY_S || keyCode == KeyCode.KEY_D || keyCode == KeyCode.KEY_W){
+            if(this.keys.indexOf(keyCode) == -1){
+                this.keys.push(keyCode);
+                this.updateDirection();
+            }
+        }
+        if(keyCode == KeyCode.KEY_Q){
+            this.moveDir.y = -1;
+        }
+        else if(keyCode == KeyCode.KEY_E){
+            this.moveDir.y = 1;
+        }
+    }
+
+    onKeyUp(event:EventKeyboard){
+        let keyCode = event.keyCode;
+        if(keyCode == KeyCode.KEY_A || keyCode == KeyCode.KEY_S || keyCode == KeyCode.KEY_D || keyCode == KeyCode.KEY_W){
+            let index = this.keys.indexOf(keyCode);
+            if(index != -1){
+                this.keys.splice(index,1);
+                this.updateDirection();
+            }
+        }
+
+        if(keyCode == KeyCode.KEY_Q || keyCode == KeyCode.KEY_E){
+            this.moveDir.y = 0;
+        }
+    }
+
+    private key2dirMap = null;
+
+    updateDirection(){
+        if(this.key2dirMap == null){
+            this.key2dirMap = {};
+            this.key2dirMap[0] = v2(0,0);
+            this.key2dirMap[KeyCode.KEY_A] = v2(-1,0);
+            this.key2dirMap[KeyCode.KEY_D] = v2(1,0);
+            this.key2dirMap[KeyCode.KEY_W] = v2(0,1);
+            this.key2dirMap[KeyCode.KEY_S] = v2(0,-1);
+
+            this.key2dirMap[KeyCode.KEY_A * 1000 + KeyCode.KEY_W] = this.key2dirMap[KeyCode.KEY_W * 1000 + KeyCode.KEY_A] = v2(-1,1);
+            this.key2dirMap[KeyCode.KEY_D * 1000 + KeyCode.KEY_W] = this.key2dirMap[KeyCode.KEY_W * 1000 + KeyCode.KEY_D] = v2(1,1);
+            this.key2dirMap[KeyCode.KEY_A * 1000 + KeyCode.KEY_S] = this.key2dirMap[KeyCode.KEY_S * 1000 + KeyCode.KEY_A] = v2(-1,-1);
+            this.key2dirMap[KeyCode.KEY_D * 1000 + KeyCode.KEY_S] = this.key2dirMap[KeyCode.KEY_S * 1000 + KeyCode.KEY_D] = v2(1,-1);
+
+            this.key2dirMap[KeyCode.KEY_A * 1000 + KeyCode.KEY_D] = this.key2dirMap[KeyCode.KEY_D];
+            this.key2dirMap[KeyCode.KEY_D * 1000 + KeyCode.KEY_A] = this.key2dirMap[KeyCode.KEY_A];
+            this.key2dirMap[KeyCode.KEY_W * 1000 + KeyCode.KEY_S] = this.key2dirMap[KeyCode.KEY_S];
+            this.key2dirMap[KeyCode.KEY_S * 1000 + KeyCode.KEY_W] = this.key2dirMap[KeyCode.KEY_W];
+        }
+        let keyCode0 = this.keys[this.keys.length - 1] || 0;
+        let keyCode1 = this.keys[this.keys.length - 2] || 0;
+        let dir = this.key2dirMap[keyCode1 * 1000 + keyCode0];
+        this.moveDir.x = dir.x;
+        this.moveDir.z = dir.y;
+    }
+}

+ 9 - 0
assets/core_tgx/easy_camera/FreeCamera.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "9cfdb127-a2c7-4370-8579-d7fa8a368052",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 100 - 0
assets/core_tgx/easy_camera/ThirdPersonCamera.ts

@@ -0,0 +1,100 @@
+import { _decorator, Component, Node, Vec3, v3 } from 'cc';
+const { ccclass, property } = _decorator;
+
+const v3_1 = v3();
+const v3_2 = v3();
+
+const ROTATION_STRENGTH = 20.0;
+
+@ccclass('tgxThirdPersonCamera')
+export class ThirdPersonCamera extends Component {
+    @property(Node)
+    target: Node;
+
+    @property
+    lookAtOffset: Vec3 = v3();
+
+    @property
+    zoomSensitivity: number = 1.0;
+
+    @property
+    lenMin: number = 10.0;
+
+    @property
+    lenMax: number = 100.0; //值越大镜头越远
+
+    @property
+    len: number = 5;
+
+    @property
+    rotateVHSeparately: boolean = false;
+
+    @property
+    tweenTime: number = 0.2;
+
+    protected _targetLen: number = 0;
+    protected _targetAngles: Vec3 = v3();
+
+    start() {
+        this._targetLen = this.len;
+        this._targetAngles.set(this.node.eulerAngles);
+    }
+
+    setLenFactor(factor: number) {
+        let len = (this.lenMax - this.lenMin) * factor + this.lenMin;
+        this._targetLen = len;
+    }
+
+    setTargetAngles(x: number, y: number, z: number) {
+        this._targetAngles.set(x, y, z);
+    }
+
+    lateUpdate(deltaTime: number) {
+        if (!this.target) {
+            return;
+        }
+        const t = Math.min(deltaTime / this.tweenTime, 1.0);
+        //rotation
+        v3_1.set(this.node.eulerAngles);
+        Vec3.lerp(v3_1, v3_1, this._targetAngles, t);
+        this.node.setRotationFromEuler(v3_1);
+
+        //lookat
+        v3_1.set(this.target.worldPosition);
+        v3_1.add(this.lookAtOffset);
+
+        //len and position
+        this.len = this.len * (1.0 - t) + this._targetLen * t;
+        v3_2.set(this.node.forward);
+        v3_2.multiplyScalar(this.len);
+
+        v3_1.subtract(v3_2);
+        this.node.setPosition(v3_1);
+    }
+
+    onCameraRotate(deltaX: number, deltaY: number) {
+        let eulerAngles = this.node.eulerAngles;
+        if (this.rotateVHSeparately) {
+            if (Math.abs(deltaX) > Math.abs(deltaY)) {
+                deltaY = 0;
+            }
+            else {
+                deltaX = 0;
+            }
+        }
+        this._targetAngles.set(eulerAngles.x + deltaX * ROTATION_STRENGTH, eulerAngles.y + deltaY * ROTATION_STRENGTH, eulerAngles.z);
+    }
+
+    onCameraZoom(view: number) {
+        // this._targetLen += delta * this.zoomSensitivity;
+        this._targetLen = view;
+        if (this._targetLen < this.lenMin) {
+            this._targetLen = this.lenMin;
+        }
+
+        if (this._targetLen > this.lenMax) {
+            this._targetLen = this.lenMax;
+        }
+    }
+}
+

+ 9 - 0
assets/core_tgx/easy_camera/ThirdPersonCamera.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0e5acc99-d55e-4cc1-8e0c-5d662625e3b6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/easy_controller.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "c97adc44-66ee-4fc5-9662-05e8e2c63051",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 207 - 0
assets/core_tgx/easy_controller/CharacterMovement.ts

@@ -0,0 +1,207 @@
+import { _decorator, Component, Node, v3, RigidBody, Vec3, find, Camera, SkeletalAnimation, AnimationClip, Collider, ICollisionEvent } from 'cc';
+import { EasyController, EasyControllerEvent } from './EasyController';
+
+const { ccclass, property } = _decorator;
+
+const v3_1 = v3();
+
+@ccclass('tgxCharacterMovement')
+export class CharacterMovement extends Component {
+
+    @property(Camera)
+    mainCamera: Camera;
+
+    @property
+    velocity = 1.0;
+
+    @property
+    jumpVelocity = 1.0;
+
+    @property
+    maxJumpTimes: number = 0;
+    private _curJumpTimes: number = 0;
+
+    @property(AnimationClip)
+    idleAnimClip: AnimationClip;
+
+    @property(AnimationClip)
+    moveAnimClip: AnimationClip;
+
+    @property(AnimationClip)
+    jumpBeginAnimClip: AnimationClip;
+
+    @property(AnimationClip)
+    jumpLoopAnimClip: AnimationClip;
+
+    @property(AnimationClip)
+    jumpLandAnimClip: AnimationClip;
+
+    _rigidBody: RigidBody;
+    _isMoving: boolean = false;
+    _velocityScale: number = 1.0;
+
+    _isInTheAir: boolean = false;
+    _currentVerticalVelocity: number = 0.0;
+
+    private _anim: SkeletalAnimation;
+
+    start() {
+        if (!this.mainCamera) {
+            this.mainCamera = find('Main Camera')?.getComponent(Camera);
+        }
+        this._rigidBody = this.node.getComponent(RigidBody);
+        this._anim = this.node.getComponent(SkeletalAnimation);
+        if (this._anim) {
+            let clipArr = [
+                this.idleAnimClip,
+                this.moveAnimClip,
+                this.jumpBeginAnimClip,
+                this.jumpLoopAnimClip,
+                this.jumpLandAnimClip
+            ];
+            for (let i = 0; i < clipArr.length; ++i) {
+                let clip = clipArr[i];
+                if (clip) {
+                    if (!this._anim.getState(clip.name)) {
+                        this._anim.addClip(clip);
+                    }
+                }
+            }
+            if (this.idleAnimClip) {
+                this._anim.play(this.idleAnimClip.name);
+            }
+        }
+
+        EasyController.on(EasyControllerEvent.MOVEMENT, this.onMovement, this);
+        EasyController.on(EasyControllerEvent.MOVEMENT_STOP, this.onMovementRelease, this);
+        EasyController.on(EasyControllerEvent.BUTTON, this.onJump, this);
+
+        let myCollider = this.getComponent(Collider);
+        myCollider?.on('onCollisionEnter',(target:ICollisionEvent)=>{
+            if(target.otherCollider != target.selfCollider){
+                this.onLand();
+            }
+        });
+    }
+
+    onDestroy() {
+        EasyController.off(EasyControllerEvent.MOVEMENT, this.onMovement, this);
+        EasyController.off(EasyControllerEvent.MOVEMENT_STOP, this.onMovementRelease, this);
+        EasyController.off(EasyControllerEvent.MOVEMENT_STOP, this.onJump, this);
+    }
+
+    update(deltaTime: number) {
+        if (this._isMoving) {
+            this._tmp.set(this.node.forward);
+            this._tmp.multiplyScalar(-1.0);
+            this._tmp.multiplyScalar(this.velocity * this._velocityScale);
+            if (this._rigidBody) {
+                this._rigidBody.getLinearVelocity(v3_1);
+                this._tmp.y = v3_1.y;
+                this._rigidBody.setLinearVelocity(this._tmp);
+            }
+            else {
+                this._tmp.multiplyScalar(deltaTime);
+                this._tmp.add(this.node.position);
+                this.node.setPosition(this._tmp);
+            }
+        }
+
+        if (this._isInTheAir) {
+            if(this.jumpBeginAnimClip && this._anim){
+                let state = this._anim.getState(this.jumpBeginAnimClip.name);
+                if(state.isPlaying && state.current >= state.duration){
+                    if(this.jumpLoopAnimClip){
+                        this._anim.crossFade(this.jumpLoopAnimClip.name);
+                    }
+                }
+            }
+
+            if(!this._rigidBody){
+                this._currentVerticalVelocity -= 9.8 * deltaTime;
+            
+                let oldPos = this.node.position;
+                let nextY = oldPos.y + this._currentVerticalVelocity * deltaTime;
+                if (nextY <= 0) {
+                    this.onLand();
+                    nextY = 0.0;
+                }
+                this.node.setPosition(oldPos.x, nextY, oldPos.z);
+            }
+        }
+    }
+
+    onLand(){
+        this._isInTheAir = false;
+        this._currentVerticalVelocity = 0.0;
+        this._curJumpTimes = 0;
+        if (this.moveAnimClip) {
+            if(this._isMoving){
+                this._anim.crossFade(this.moveAnimClip.name, 0.5);
+            }
+            else{
+                this._anim.crossFade(this.idleAnimClip.name, 0.5);
+            }
+        }
+    }
+
+    private _tmp = v3();
+    onMovement(degree: number, offset: number) {
+        let cameraRotationY = 0;
+        if (this.mainCamera) {
+            cameraRotationY = this.mainCamera.node.eulerAngles.y;
+        }
+        this._velocityScale = offset;
+        //2D界面是 正X 为 0, 3D场景是 正前方为0,所以需要 - 90 度。(顺时针转90度)
+        this._tmp.set(0, cameraRotationY + degree - 90 + 180, 0);
+        this.node.setRotationFromEuler(this._tmp);
+        if (this._anim) {
+            if (!this._isMoving && !this._isInTheAir) {
+                if (this.moveAnimClip) {
+                    this._anim.crossFade(this.moveAnimClip.name, 0.1);
+                }
+            }
+            if (this.moveAnimClip) {
+                this._anim.getState(this.moveAnimClip.name).speed = this._velocityScale;
+            }
+        }
+        this._isMoving = true;
+
+    }
+    onMovementRelease() {
+        if (!this._isInTheAir && this.idleAnimClip) {
+            this._anim?.crossFade(this.idleAnimClip.name, 0.5);
+        }
+        this._isMoving = false;
+        if (this._rigidBody) {
+            this._rigidBody.setLinearVelocity(Vec3.ZERO);
+        }
+    }
+
+    onJump(btnName:string) {
+        console.log(btnName);
+        if(btnName != 'btn_slot_0'){
+            return;
+        }
+        if (this._curJumpTimes >= this.maxJumpTimes) {
+            return;
+        }
+        if(this._curJumpTimes == 0 || true){
+            if(this.jumpBeginAnimClip){
+                this._anim?.crossFade(this.jumpBeginAnimClip.name);
+            }
+        }
+        this._curJumpTimes++;
+        if(this._rigidBody){
+            this._rigidBody.getLinearVelocity(v3_1);
+            v3_1.y = this.jumpVelocity;
+            this._rigidBody.setLinearVelocity(v3_1);
+        }
+        else{
+            this._currentVerticalVelocity = this.jumpVelocity;
+        }
+        
+        this._isInTheAir = true;
+    }
+}
+

+ 9 - 0
assets/core_tgx/easy_controller/CharacterMovement.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "d197a4ec-eefd-4692-9c32-a5d2757b327c",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 58 - 0
assets/core_tgx/easy_controller/CharacterMovement2D.ts

@@ -0,0 +1,58 @@
+import { _decorator, Component, Node, Vec2, v2, Prefab, Vec3 } from 'cc';
+import { EasyController, EasyControllerEvent } from './EasyController';
+const { ccclass, property } = _decorator;
+
+const tempV2 = v2();
+
+@ccclass('tgxCharacterMovement2D')
+export class CharacterMovement2D extends Component {
+    @property
+    moveSpeed: number = 100;
+
+    @property
+    needRotation:boolean = false;
+
+    start() {
+        EasyController.on(EasyControllerEvent.MOVEMENT, this.onMovement, this);
+        EasyController.on(EasyControllerEvent.MOVEMENT_STOP, this.onMovementStop, this);
+    }
+
+    private _moveFactor: number = 0;
+    private _moveDir: Vec2 = v2(1, 0);
+
+    public get moveDir():Vec2{
+        return this._moveDir;
+    }
+
+    public get realSpeed():number{
+        return this.moveSpeed * this._moveFactor;
+    }
+
+    onMovement(degree, strengthen) {
+        let angle = degree / 180 * Math.PI;
+        if(this.needRotation){
+            this.node.setRotationFromEuler(0, 0, degree);
+        }
+        this._moveDir.set(Math.cos(angle), Math.sin(angle));
+        this._moveDir.normalize();
+        this._moveFactor = strengthen;
+    }
+
+    onMovementStop() {
+        this._moveFactor = 0;
+    }
+
+    onDestroy() {
+        EasyController.off(EasyControllerEvent.MOVEMENT, this.onMovement, this);
+        EasyController.off(EasyControllerEvent.MOVEMENT_STOP, this.onMovementStop, this);
+    }
+
+
+    update(deltaTime: number) {
+        if (this._moveFactor) {
+            Vec2.multiplyScalar(tempV2, this._moveDir, this.realSpeed * deltaTime);
+            let pos = this.node.position;
+            this.node.setPosition(pos.x + tempV2.x, pos.y + tempV2.y, pos.z);
+        }
+    }
+}

+ 9 - 0
assets/core_tgx/easy_controller/CharacterMovement2D.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6bdfadd8-330d-4fc2-86d7-76357e6964d7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 43 - 0
assets/core_tgx/easy_controller/EasyController.ts

@@ -0,0 +1,43 @@
+import { _decorator, director } from 'cc';
+
+export class EasyControllerEvent{
+    /**
+    * Dispatched when camera rotating
+    * @params rx: horizontal rotation
+    * @params ry: vertical rotation.
+    */
+    public static CAMERA_ROTATE:string = 'EasyControllerEvent.CAMERA_ROTATE';
+    
+    /**
+     * Dispatched when camera zooming
+     * @params delta: amount of camera zoom
+    */
+    public static CAMERA_ZOOM:string = 'EasyControllerEvent.CAMERA_ZOOM';
+    /**
+     * Dispatched when the movement controller is moving
+     * @param degree: direction in degrees, with positive X-axis as 0, increasing in a counter-clockwise direction.
+     * @param strength: movement strength, [0.0, 1.0], can be used for fine-tuning the movement speed.
+     */
+    public static MOVEMENT:string = 'EasyControllerEvent.MOVEMENT';
+    /**
+     * Dispatched when the movement controller stops moving
+     */
+    public static MOVEMENT_STOP:string = 'EasyControllerEvent.MOVEMENT_STOP';
+    
+    /**
+     * Dispatched when one of the buttons is pressed.
+     * @param buttonName: string, indicates which button is pressed. 
+     */
+    public static BUTTON:string = 'EasyControllerEvent.BUTTON';
+}
+
+export class EasyController{
+
+    public static on(type:string,callback:Function,target?:any){
+        director.getScene().on(type,callback,target);
+    }
+
+    public static off(type:string,callback?:Function,target?:any){
+        director.getScene()?.off(type,callback,target);
+    }
+}

+ 9 - 0
assets/core_tgx/easy_controller/EasyController.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "cc9338a3-89a7-411b-8ec6-67ad715c0e76",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 22 - 0
assets/core_tgx/easy_controller/ThirdPersonCameraCtrl.ts

@@ -0,0 +1,22 @@
+import { _decorator, Component, Node } from 'cc';
+import { ThirdPersonCamera } from '../easy_camera/ThirdPersonCamera';
+import { EasyController, EasyControllerEvent } from './EasyController';
+const { ccclass, property } = _decorator;
+
+@ccclass('tgxThirdPersonCameraCtrl')
+export class ThirdPersonCameraCtrl extends ThirdPersonCamera {
+    start() {
+        EasyController.on(EasyControllerEvent.CAMERA_ROTATE, this.onCameraRotate, this);
+        EasyController.on(EasyControllerEvent.CAMERA_ZOOM, this.onCameraZoom, this);
+
+        this._targetLen = this.len;
+        this._targetAngles.set(this.node.eulerAngles);
+    }
+
+    onDestroy() {
+        EasyController.off(EasyControllerEvent.CAMERA_ROTATE, this.onCameraRotate, this);
+        EasyController.off(EasyControllerEvent.CAMERA_ZOOM, this.onCameraZoom, this);
+    }
+}
+
+

+ 9 - 0
assets/core_tgx/easy_controller/ThirdPersonCameraCtrl.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6ea3c115-de49-4801-99f3-f80cbaeaeb7e",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 348 - 0
assets/core_tgx/easy_controller/UI_Joystick.ts

@@ -0,0 +1,348 @@
+import { _decorator, Node, EventTouch, Touch, Component, UITransform, Input, EventKeyboard, KeyCode, v2, Vec3, input, Scene, director, EventMouse, macro, view, screen } from 'cc';
+import { EasyControllerEvent } from './EasyController';
+const { ccclass, property } = _decorator;
+
+/****
+ * split screen into three parts.
+ * ---------------------------------------------
+ *                                              |
+ *           1.camera rotation zone             |
+ *                                              |
+ *----------------------------------------------|
+ *                      |                       |
+ * 2.movement ctrl zone  | 3.camera rotation zone|
+ *                      |                       |
+ * ----------------------------------------------
+ * 
+ * multi-touch for camera zoom.
+ *  */
+
+@ccclass('tgxUI_Joystick')
+export class UI_Joystick extends Component {
+
+    private static _inst:UI_Joystick = null;
+    public static get inst():UI_Joystick{
+        return this._inst;
+    }
+
+
+    private _ctrlRoot: UITransform = null;
+    private _ctrlPointer: Node = null;
+    private _checkerCamera: UITransform = null;
+    private _buttons: Node = null;
+
+    private _cameraSensitivity: number = 0.1;
+    private _distanceOfTwoTouchPoint: number = 0;
+
+    private _movementTouch: Touch = null;
+    private _cameraTouchA: Touch = null;
+    private _cameraTouchB: Touch = null;
+
+    private _scene: Scene = null;
+
+    private _key2buttonMap = {};
+
+    protected onLoad(): void {
+        UI_Joystick._inst = this;
+    }
+
+    start() {
+        let checkerCamera = this.node.getChildByName('checker_camera').getComponent(UITransform);
+        checkerCamera.node.on(Input.EventType.TOUCH_START, this.onTouchStart_CameraCtrl, this);
+        checkerCamera.node.on(Input.EventType.TOUCH_MOVE, this.onTouchMove_CameraCtrl, this);
+        checkerCamera.node.on(Input.EventType.TOUCH_END, this.onTouchUp_CameraCtrl, this);
+        checkerCamera.node.on(Input.EventType.TOUCH_CANCEL, this.onTouchUp_CameraCtrl, this);
+
+        let checkerMovement = this.node.getChildByName('checker_movement').getComponent(UITransform);
+        checkerMovement.node.on(Input.EventType.TOUCH_START, this.onTouchStart_Movement, this);
+        checkerMovement.node.on(Input.EventType.TOUCH_MOVE, this.onTouchMove_Movement, this);
+        checkerMovement.node.on(Input.EventType.TOUCH_END, this.onTouchUp_Movement, this);
+        checkerMovement.node.on(Input.EventType.TOUCH_CANCEL, this.onTouchUp_Movement, this);
+
+
+        this._checkerCamera = checkerCamera;
+
+        this._ctrlRoot = this.node.getChildByName('ctrl').getComponent(UITransform);
+        this._ctrlRoot.node.active = false;
+        this._ctrlPointer = this._ctrlRoot.node.getChildByName('pointer');
+
+        this._buttons = this.node.getChildByName('buttons');
+
+        this._key2buttonMap[KeyCode.KEY_J] = 'btn_slot_0';
+        this._key2buttonMap[KeyCode.KEY_K] = 'btn_slot_1';
+        this._key2buttonMap[KeyCode.KEY_L] = 'btn_slot_2';
+        this._key2buttonMap[KeyCode.KEY_U] = 'btn_slot_3';
+        this._key2buttonMap[KeyCode.KEY_I] = 'btn_slot_4';
+
+        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
+        input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
+        input.on(Input.EventType.MOUSE_WHEEL,this.onMouseWheel, this);
+
+        this._scene = director.getScene();
+    }
+
+    onDestroy() {
+        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
+        input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
+        input.off(Input.EventType.MOUSE_WHEEL,this.onMouseWheel, this);
+
+        UI_Joystick._inst = null;
+    }
+
+    bindKeyToButton(keyCode:KeyCode, btnName:string){
+        this._key2buttonMap[keyCode] = btnName;
+    }
+
+    setButtonVisible(btnName:string, visible:boolean){
+        let node = this._buttons?.getChildByName(btnName);
+        if(node){
+            node.active = visible;
+        }
+    }
+
+    getButtonByName(btnName:string):Node{
+        return this._buttons.getChildByName(btnName);
+    }
+
+    onTouchStart_Movement(event: EventTouch) {
+        let touches = event.getTouches();
+        for (let i = 0; i < touches.length; ++i) {
+            let touch = touches[i];
+            let x = touch.getUILocationX();
+            let y = touch.getUILocationY();
+            if (!this._movementTouch) {
+                //we sub halfWidth,halfHeight here.
+                //because, the touch event use left bottom as zero point(0,0), ui node use the center of screen as zero point(0,0)
+                //this._ctrlRoot.setPosition(x - halfWidth, y - halfHeight, 0);
+
+                let halfWidth = this._checkerCamera.width / 2;
+                let halfHeight = this._checkerCamera.height / 2;
+
+                this._ctrlRoot.node.active = true;
+                this._ctrlRoot.node.setPosition(x - halfWidth, y - halfHeight, 0);
+                this._ctrlPointer.setPosition(0, 0, 0);
+                this._movementTouch = touch;
+            }
+        }
+    }
+
+    onTouchMove_Movement(event: EventTouch) {
+        let touches = event.getTouches();
+        for (let i = 0; i < touches.length; ++i) {
+            let touch = touches[i];
+            if (this._movementTouch && touch.getID() == this._movementTouch.getID()) {
+                let halfWidth = this._checkerCamera.width / 2;
+                let halfHeight = this._checkerCamera.height / 2;
+                let x = touch.getUILocationX();
+                let y = touch.getUILocationY();
+
+                let pos = this._ctrlRoot.node.position;
+                let ox = x - halfWidth - pos.x;
+                let oy = y - halfHeight - pos.y;
+
+                let len = Math.sqrt(ox * ox + oy * oy);
+                if (len <= 0) {
+                    return;
+                }
+
+                let dirX = ox / len;
+                let dirY = oy / len;
+                let radius = this._ctrlRoot.width / 2;
+                if (len > radius) {
+                    len = radius;
+                    ox = dirX * radius;
+                    oy = dirY * radius;
+                }
+
+                this._ctrlPointer.setPosition(ox, oy, 0);
+
+                // degree 0 ~ 360 based on x axis.
+                let degree = Math.atan(dirY / dirX) / Math.PI * 180;
+                if (dirX < 0) {
+                    degree += 180;
+                }
+                else {
+                    degree += 360;
+                }
+
+                this._scene.emit(EasyControllerEvent.MOVEMENT, degree, len / radius);
+            }
+        }
+    }
+
+    onTouchUp_Movement(event: EventTouch) {
+        let touches = event.getTouches();
+        for (let i = 0; i < touches.length; ++i) {
+            let touch = touches[i];
+            if (this._movementTouch && touch.getID() == this._movementTouch.getID()) {
+                this._scene.emit(EasyControllerEvent.MOVEMENT_STOP);
+                this._movementTouch = null;
+                this._ctrlRoot.node.active = false;
+            }
+        }
+    }
+
+
+
+    private getDistOfTwoTouchPoints(): number {
+        let touchA = this._cameraTouchA;
+        let touchB = this._cameraTouchB;
+        if (!touchA || !touchB) {
+            return 0;
+        }
+        let dx = touchA.getLocationX() - touchB.getLocationX();
+        let dy = touchB.getLocationY() - touchB.getLocationY();
+        return Math.sqrt(dx * dx + dy * dy);
+    }
+    
+    private onTouchStart_CameraCtrl(event: EventTouch) {
+        let touches = event.getAllTouches();
+        this._cameraTouchA = null;
+        this._cameraTouchB = null;
+        for (let i = touches.length - 1; i >= 0; i--) {
+            let touch = touches[i];
+            if (this._movementTouch && touch.getID() == this._movementTouch.getID()) {
+                continue;
+            }
+            if (this._cameraTouchA == null) {
+                this._cameraTouchA = touches[i];
+            }
+            else if (this._cameraTouchB == null) {
+                this._cameraTouchB = touches[i];
+                break;
+            }
+        }
+        this._distanceOfTwoTouchPoint = this.getDistOfTwoTouchPoints();
+    }
+
+    private onTouchMove_CameraCtrl(event: EventTouch) {
+        let touches = event.getTouches();
+        for (let i = 0; i < touches.length; ++i) {
+            let touch = touches[i];
+            let touchID = touch.getID();
+            //two touches, do camera zoom.
+            if (this._cameraTouchA && this._cameraTouchB) {
+                console.log(touchID, this._cameraTouchA.getID(), this._cameraTouchB.getID());
+                let needZoom = false;
+                if (touchID == this._cameraTouchA.getID()) {
+                    this._cameraTouchA = touch;
+                    needZoom = true;
+                }
+                if (touchID == this._cameraTouchB.getID()) {
+                    this._cameraTouchB = touch;
+                    needZoom = true;
+                }
+
+                if (needZoom) {
+                    let newDist = this.getDistOfTwoTouchPoints();
+                    let delta = this._distanceOfTwoTouchPoint - newDist;
+                    this._scene.emit(EasyControllerEvent.CAMERA_ZOOM, delta);
+                    this._distanceOfTwoTouchPoint = newDist;
+                }
+            }
+            //only one touch, do camera rotate.
+            else if (this._cameraTouchA && touchID == this._cameraTouchA.getID()) {
+                let dt = touch.getDelta();
+                let rx = dt.y * this._cameraSensitivity;
+                let ry = -dt.x * this._cameraSensitivity;
+                this._scene.emit(EasyControllerEvent.CAMERA_ROTATE, rx, ry);
+            }
+        }
+    }
+
+    private onTouchUp_CameraCtrl(event: EventTouch) {
+        let touches = event.getAllTouches();
+        let hasTouchA = false;
+        let hasTouchB = false;
+        for (let i = 0; i < touches.length; ++i) {
+            let touch = touches[i];
+            let touchID = touch.getID();
+            if (this._cameraTouchA && touchID == this._cameraTouchA.getID()) {
+                hasTouchA = true;
+            }
+            else if (this._cameraTouchB && touchID == this._cameraTouchB.getID()) {
+                hasTouchB = true;
+            }
+        }
+
+        if (!hasTouchA) {
+            this._cameraTouchA = null;
+        }
+        if (!hasTouchB) {
+            this._cameraTouchB = null;
+        }
+    }
+
+    private _keys = [];
+    private _degree: number = 0;
+
+    onKeyDown(event: EventKeyboard) {
+        let keyCode = event.keyCode;
+        if (keyCode == KeyCode.KEY_A || keyCode == KeyCode.KEY_S || keyCode == KeyCode.KEY_D || keyCode == KeyCode.KEY_W) {
+            if (this._keys.indexOf(keyCode) == -1) {
+                this._keys.push(keyCode);
+                this.updateDirection();
+            }
+        }
+        else{
+            let btnName = this._key2buttonMap[keyCode];
+            if(btnName){
+                this._scene.emit(EasyControllerEvent.BUTTON,btnName);
+            }
+        }
+    }
+
+    onKeyUp(event: EventKeyboard) {
+        let keyCode = event.keyCode;
+        if (keyCode == KeyCode.KEY_A || keyCode == KeyCode.KEY_S || keyCode == KeyCode.KEY_D || keyCode == KeyCode.KEY_W) {
+            let index = this._keys.indexOf(keyCode);
+            if (index != -1) {
+                this._keys.splice(index, 1);
+                this.updateDirection();
+            }
+        }
+    }
+
+    onMouseWheel(event:EventMouse){
+        let delta = event.getScrollY() * 0.1;
+        console.log(delta);
+        this._scene.emit(EasyControllerEvent.CAMERA_ZOOM, delta);
+    }
+
+    onButtonSlot(event){
+        let btnName = event.target.name;
+        this._scene.emit(EasyControllerEvent.BUTTON,btnName);
+    }
+
+    private _key2dirMap = null;
+
+    updateDirection() {
+        if (this._key2dirMap == null) {
+            this._key2dirMap = {};
+            this._key2dirMap[0] = -1;
+            this._key2dirMap[KeyCode.KEY_A] = 180;
+            this._key2dirMap[KeyCode.KEY_D] = 0;
+            this._key2dirMap[KeyCode.KEY_W] = 90;
+            this._key2dirMap[KeyCode.KEY_S] = 270;
+
+            this._key2dirMap[KeyCode.KEY_A * 1000 + KeyCode.KEY_W] = this._key2dirMap[KeyCode.KEY_W * 1000 + KeyCode.KEY_A] = 135;
+            this._key2dirMap[KeyCode.KEY_D * 1000 + KeyCode.KEY_W] = this._key2dirMap[KeyCode.KEY_W * 1000 + KeyCode.KEY_D] = 45;
+            this._key2dirMap[KeyCode.KEY_A * 1000 + KeyCode.KEY_S] = this._key2dirMap[KeyCode.KEY_S * 1000 + KeyCode.KEY_A] = 225;
+            this._key2dirMap[KeyCode.KEY_D * 1000 + KeyCode.KEY_S] = this._key2dirMap[KeyCode.KEY_S * 1000 + KeyCode.KEY_D] = 315;
+
+            this._key2dirMap[KeyCode.KEY_A * 1000 + KeyCode.KEY_D] = this._key2dirMap[KeyCode.KEY_D];
+            this._key2dirMap[KeyCode.KEY_D * 1000 + KeyCode.KEY_A] = this._key2dirMap[KeyCode.KEY_A];
+            this._key2dirMap[KeyCode.KEY_W * 1000 + KeyCode.KEY_S] = this._key2dirMap[KeyCode.KEY_S];
+            this._key2dirMap[KeyCode.KEY_S * 1000 + KeyCode.KEY_W] = this._key2dirMap[KeyCode.KEY_W];
+        }
+        let keyCode0 = this._keys[this._keys.length - 1] || 0;
+        let keyCode1 = this._keys[this._keys.length - 2] || 0;
+        this._degree = this._key2dirMap[keyCode1 * 1000 + keyCode0];
+        if (this._degree == null || this._degree < 0) {
+            this._scene.emit(EasyControllerEvent.MOVEMENT_STOP);
+        }
+        else {
+            this._scene.emit(EasyControllerEvent.MOVEMENT, this._degree, 1.0);
+        }
+    }
+}

+ 9 - 0
assets/core_tgx/easy_controller/UI_Joystick.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0d3fd50c-507d-45ab-81b5-dc85a3ecb7f6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 3565 - 0
assets/core_tgx/easy_controller/ui_joystick_panel.prefab

@@ -0,0 +1,3565 @@
+[
+  {
+    "__type__": "cc.Prefab",
+    "_name": "ui_joystick_panel",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_native": "",
+    "data": {
+      "__id__": 1
+    },
+    "optimizationPolicy": 0,
+    "persistent": false
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "ui_joystick_panel",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": null,
+    "_children": [
+      {
+        "__id__": 2
+      },
+      {
+        "__id__": 10
+      },
+      {
+        "__id__": 20
+      },
+      {
+        "__id__": 66
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 155
+      },
+      {
+        "__id__": 157
+      },
+      {
+        "__id__": 159
+      }
+    ],
+    "_prefab": {
+      "__id__": 161
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "checker_camera",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 1
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 3
+      },
+      {
+        "__id__": 5
+      },
+      {
+        "__id__": 7
+      }
+    ],
+    "_prefab": {
+      "__id__": 9
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 2
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 4
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 1280,
+      "height": 720
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "0fZyqsL71Jo5X5Qe6tNsnC"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 2
+    },
+    "_enabled": false,
+    "__prefab": {
+      "__id__": 6
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 57
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "d0AoChjSREeLgHKkf59Vzb"
+  },
+  {
+    "__type__": "cc.Widget",
+    "_name": "Button<Widget>",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 2
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 8
+    },
+    "_alignFlags": 45,
+    "_target": null,
+    "_left": 0,
+    "_right": 0,
+    "_top": 0,
+    "_bottom": 0,
+    "_horizontalCenter": 0,
+    "_verticalCenter": 0,
+    "_isAbsLeft": true,
+    "_isAbsRight": true,
+    "_isAbsTop": true,
+    "_isAbsBottom": true,
+    "_isAbsHorizontalCenter": true,
+    "_isAbsVerticalCenter": true,
+    "_originalWidth": 640,
+    "_originalHeight": 320,
+    "_alignMode": 2,
+    "_lockFlags": 0,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "25m5M04TNAl4BNHIjAMrrL"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "0cMqkAXtpP1Jpj0Hz+MYxg",
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "checker_movement",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 1
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 11
+      },
+      {
+        "__id__": 13
+      },
+      {
+        "__id__": 15
+      },
+      {
+        "__id__": 17
+      }
+    ],
+    "_prefab": {
+      "__id__": 19
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": -320,
+      "y": -180,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 10
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 12
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 640,
+      "height": 360
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "c3kE+xlWdKP42qlgQXWPi7"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 10
+    },
+    "_enabled": false,
+    "__prefab": {
+      "__id__": 14
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 145,
+      "g": 32,
+      "b": 32,
+      "a": 129
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "b9MreVDjBOKJh1aR0UFV4C"
+  },
+  {
+    "__type__": "cc.Widget",
+    "_name": "Button<Widget>",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 10
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 16
+    },
+    "_alignFlags": 45,
+    "_target": null,
+    "_left": 0,
+    "_right": 0.5,
+    "_top": 0.5,
+    "_bottom": 0,
+    "_horizontalCenter": 0,
+    "_verticalCenter": 0,
+    "_isAbsLeft": true,
+    "_isAbsRight": false,
+    "_isAbsTop": false,
+    "_isAbsBottom": true,
+    "_isAbsHorizontalCenter": true,
+    "_isAbsVerticalCenter": true,
+    "_originalWidth": 640,
+    "_originalHeight": 320,
+    "_alignMode": 2,
+    "_lockFlags": 0,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "5fpMWtX9VDEIN1lSKC3+iO"
+  },
+  {
+    "__type__": "cc.Button",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 10
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 18
+    },
+    "clickEvents": [],
+    "_interactable": true,
+    "_transition": 0,
+    "_normalColor": {
+      "__type__": "cc.Color",
+      "r": 214,
+      "g": 214,
+      "b": 214,
+      "a": 255
+    },
+    "_hoverColor": {
+      "__type__": "cc.Color",
+      "r": 211,
+      "g": 211,
+      "b": 211,
+      "a": 255
+    },
+    "_pressedColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_disabledColor": {
+      "__type__": "cc.Color",
+      "r": 124,
+      "g": 124,
+      "b": 124,
+      "a": 255
+    },
+    "_normalSprite": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_hoverSprite": null,
+    "_pressedSprite": null,
+    "_disabledSprite": null,
+    "_duration": 0.1,
+    "_zoomScale": 1.2,
+    "_target": {
+      "__id__": 10
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "47ImTsej1IxZzl08BeNJ5N"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "55mHcC2xxEDI4zcUpi9jjH",
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "ctrl",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 1
+    },
+    "_children": [
+      {
+        "__id__": 21
+      },
+      {
+        "__id__": 49
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 63
+      }
+    ],
+    "_prefab": {
+      "__id__": 65
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": -437.524,
+      "y": -168.153,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "bg",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 20
+    },
+    "_children": [
+      {
+        "__id__": 22
+      },
+      {
+        "__id__": 28
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 42
+      },
+      {
+        "__id__": 44
+      },
+      {
+        "__id__": 46
+      }
+    ],
+    "_prefab": {
+      "__id__": 48
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 21
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 23
+      },
+      {
+        "__id__": 25
+      }
+    ],
+    "_prefab": {
+      "__id__": 27
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 22
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 24
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 200,
+      "height": 200
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "330hXv5fNDIoJcPkluEZRC"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 22
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 26
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "d5DBbkpuJDHqUEboCZJBpq"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "155mXp7QxLQ6rSQqjKBV9N",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "bg",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 21
+    },
+    "_children": [
+      {
+        "__id__": 29
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 35
+      },
+      {
+        "__id__": 37
+      },
+      {
+        "__id__": 39
+      }
+    ],
+    "_prefab": {
+      "__id__": 41
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 28
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 30
+      },
+      {
+        "__id__": 32
+      }
+    ],
+    "_prefab": {
+      "__id__": 34
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 29
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 31
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 200,
+      "height": 200
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "b4b+vIgdhE5INrZcdqsLWg"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 29
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 33
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "4dvg+TolRBTIJkmXXsk7e6"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "23M5L4yeRI8Y6hzkpL+FCe",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 28
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 36
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 185,
+      "height": 185
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "216/LDx2tPzphG3fjCxye+"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 28
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 38
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "1aKo5aGehJboNl4hOWEBMo"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 28
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 40
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "14rk9pHEtDz5in/yBpDyLl"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "78UVt6ZrtLhZax+chb7E/p",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 21
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 43
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 200,
+      "height": 200
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "cfTiBJ1LRKnqIgyLxpKExR"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 21
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 45
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "28qJfL/xRO9Z5lmtrZW9/j"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 21
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 47
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "4cHz4mgF1BGbLN0QWBG3NI"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "93JDZm285OVoyAXp6qXmwW",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "pointer",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 20
+    },
+    "_children": [
+      {
+        "__id__": 50
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 56
+      },
+      {
+        "__id__": 58
+      },
+      {
+        "__id__": 60
+      }
+    ],
+    "_prefab": {
+      "__id__": 62
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 49
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 51
+      },
+      {
+        "__id__": 53
+      }
+    ],
+    "_prefab": {
+      "__id__": 55
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 50
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 52
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 200,
+      "height": 200
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "9a9lIrJfFHVKHDZT62NZ6m"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 50
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 54
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 170,
+      "g": 27,
+      "b": 27,
+      "a": 255
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "dcI06aayBASJNr/hRJ89aG"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "9b13ak09dAe7xrTgLNVUL2",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 49
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 57
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 80,
+      "height": 80
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "adequmQjREpoUJ0dk84JA0"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 49
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 59
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "44tESj1WZMP7z80rcz6r7C"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 49
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 61
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "0bOzHhVY5PFJCCaKNllzV3"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "b7w1ruaU5L7b+1uFnOQIQx",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 20
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 64
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 200,
+      "height": 200
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "f6kGxWOihLP63f3kZc7xbA"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "a4DfMArKhELaPwA+TvA+0Z",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "buttons",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 1
+    },
+    "_children": [
+      {
+        "__id__": 67
+      },
+      {
+        "__id__": 84
+      },
+      {
+        "__id__": 101
+      },
+      {
+        "__id__": 118
+      },
+      {
+        "__id__": 135
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 152
+      }
+    ],
+    "_prefab": {
+      "__id__": 154
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 526,
+      "y": -262,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_slot_0",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 66
+    },
+    "_children": [
+      {
+        "__id__": 68
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 74
+      },
+      {
+        "__id__": 76
+      },
+      {
+        "__id__": 78
+      },
+      {
+        "__id__": 80
+      }
+    ],
+    "_prefab": {
+      "__id__": 83
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 67
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 69
+      },
+      {
+        "__id__": 71
+      }
+    ],
+    "_prefab": {
+      "__id__": 73
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 68
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 70
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 150,
+      "height": 150
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "2atdsdGPNM0aT5MY1V1ktA"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 68
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 72
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "fcNqjl+SRIQ7MHHxRtNJOf"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "bau+2rH21Aj7r8DqrX4r1z",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 67
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 75
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 145,
+      "height": 145
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "2dHICoBZVFY4KL6BwgHAvi"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 67
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 77
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "ca9ZVXsJxMDa1C1Njx7HT0"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 67
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 79
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "c5BzPzgh9FTbIickzeHfr/"
+  },
+  {
+    "__type__": "cc.Button",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 67
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 81
+    },
+    "clickEvents": [
+      {
+        "__id__": 82
+      }
+    ],
+    "_interactable": true,
+    "_transition": 0,
+    "_normalColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_hoverColor": {
+      "__type__": "cc.Color",
+      "r": 211,
+      "g": 211,
+      "b": 211,
+      "a": 255
+    },
+    "_pressedColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_disabledColor": {
+      "__type__": "cc.Color",
+      "r": 124,
+      "g": 124,
+      "b": 124,
+      "a": 255
+    },
+    "_normalSprite": null,
+    "_hoverSprite": null,
+    "_pressedSprite": null,
+    "_disabledSprite": null,
+    "_duration": 0.1,
+    "_zoomScale": 1.2,
+    "_target": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "bcV5VHVx5KcLLbzdAvy9Ee"
+  },
+  {
+    "__type__": "cc.ClickEvent",
+    "target": {
+      "__id__": 1
+    },
+    "component": "",
+    "_componentId": "0d3fdUMUH1Fq4G13IWj7Lf2",
+    "handler": "onButtonSlot",
+    "customEventData": ""
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "ccEKdclRtN441KT5DHcVNl",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_slot_1",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 66
+    },
+    "_children": [
+      {
+        "__id__": 85
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 91
+      },
+      {
+        "__id__": 93
+      },
+      {
+        "__id__": 95
+      },
+      {
+        "__id__": 97
+      }
+    ],
+    "_prefab": {
+      "__id__": 100
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 20,
+      "y": 162,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 84
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 86
+      },
+      {
+        "__id__": 88
+      }
+    ],
+    "_prefab": {
+      "__id__": 90
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 85
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 87
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 80,
+      "height": 80
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "88m1h1wVdOGZxYW6F9gUEC"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 85
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 89
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "a9qSA6I1FCj4Rf4HSmK31b"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "66xil0plpLdrmEVEjs++Q7",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 84
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 92
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 75,
+      "height": 75
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "9fmUoMBctKi63OqPdrnnRq"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 84
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 94
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "a2QeFppMBBFKRrc8TMYIGz"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 84
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 96
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "c3RVl6G6VJt5b3KtGyDbY8"
+  },
+  {
+    "__type__": "cc.Button",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 84
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 98
+    },
+    "clickEvents": [
+      {
+        "__id__": 99
+      }
+    ],
+    "_interactable": true,
+    "_transition": 0,
+    "_normalColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_hoverColor": {
+      "__type__": "cc.Color",
+      "r": 211,
+      "g": 211,
+      "b": 211,
+      "a": 255
+    },
+    "_pressedColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_disabledColor": {
+      "__type__": "cc.Color",
+      "r": 124,
+      "g": 124,
+      "b": 124,
+      "a": 255
+    },
+    "_normalSprite": null,
+    "_hoverSprite": null,
+    "_pressedSprite": null,
+    "_disabledSprite": null,
+    "_duration": 0.1,
+    "_zoomScale": 1.2,
+    "_target": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "fby9NaiydCR6KX9V/vbpVm"
+  },
+  {
+    "__type__": "cc.ClickEvent",
+    "target": {
+      "__id__": 1
+    },
+    "component": "",
+    "_componentId": "0d3fdUMUH1Fq4G13IWj7Lf2",
+    "handler": "onButtonSlot",
+    "customEventData": ""
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "c8n4U+w/NMw43H85mvryWv",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_slot_2",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 66
+    },
+    "_children": [
+      {
+        "__id__": 102
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 108
+      },
+      {
+        "__id__": 110
+      },
+      {
+        "__id__": 112
+      },
+      {
+        "__id__": 114
+      }
+    ],
+    "_prefab": {
+      "__id__": 117
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": -80.68,
+      "y": 133.072,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 101
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 103
+      },
+      {
+        "__id__": 105
+      }
+    ],
+    "_prefab": {
+      "__id__": 107
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 102
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 104
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 80,
+      "height": 80
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "d4deUhUkVHUboSSiFy0F3X"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 102
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 106
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "08/qgDefdHRKrJ0E0sWvF/"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "b4b1kl8RlD06C4VNz6Iygb",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 101
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 109
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 75,
+      "height": 75
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "95ZkIYBsBFgLsUygJt+7Oh"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 101
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 111
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "bcwQBb41dHp4HsbQa2IIQ1"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 101
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 113
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "50NhAT9FdFCrcVMxFj3SIL"
+  },
+  {
+    "__type__": "cc.Button",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 101
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 115
+    },
+    "clickEvents": [
+      {
+        "__id__": 116
+      }
+    ],
+    "_interactable": true,
+    "_transition": 0,
+    "_normalColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_hoverColor": {
+      "__type__": "cc.Color",
+      "r": 211,
+      "g": 211,
+      "b": 211,
+      "a": 255
+    },
+    "_pressedColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_disabledColor": {
+      "__type__": "cc.Color",
+      "r": 124,
+      "g": 124,
+      "b": 124,
+      "a": 255
+    },
+    "_normalSprite": null,
+    "_hoverSprite": null,
+    "_pressedSprite": null,
+    "_disabledSprite": null,
+    "_duration": 0.1,
+    "_zoomScale": 1.2,
+    "_target": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "bdnOQ+Yk9GTLboQd2DgLJv"
+  },
+  {
+    "__type__": "cc.ClickEvent",
+    "target": {
+      "__id__": 1
+    },
+    "component": "",
+    "_componentId": "0d3fdUMUH1Fq4G13IWj7Lf2",
+    "handler": "onButtonSlot",
+    "customEventData": ""
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "98/3PSxQREu4vt8e5EQGPC",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_slot_3",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 66
+    },
+    "_children": [
+      {
+        "__id__": 119
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 125
+      },
+      {
+        "__id__": 127
+      },
+      {
+        "__id__": 129
+      },
+      {
+        "__id__": 131
+      }
+    ],
+    "_prefab": {
+      "__id__": 134
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": -142,
+      "y": 52,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 118
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 120
+      },
+      {
+        "__id__": 122
+      }
+    ],
+    "_prefab": {
+      "__id__": 124
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 119
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 121
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 80,
+      "height": 80
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "8fxaiTLEFG2JVkZk+9LKCx"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 119
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 123
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "47HcrQAdJHd4IgGp80xNqy"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "97yW+4f/NM1JNUW/Tonl/O",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 118
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 126
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 75,
+      "height": 75
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "48eZ3Vm7dKAo0aPHRcrxKO"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 118
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 128
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "772K/kx1tHoq0kMZ1BCIVA"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 118
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 130
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "3fooQ4VwhBDodiM4ri6Fne"
+  },
+  {
+    "__type__": "cc.Button",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 118
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 132
+    },
+    "clickEvents": [
+      {
+        "__id__": 133
+      }
+    ],
+    "_interactable": true,
+    "_transition": 0,
+    "_normalColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_hoverColor": {
+      "__type__": "cc.Color",
+      "r": 211,
+      "g": 211,
+      "b": 211,
+      "a": 255
+    },
+    "_pressedColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_disabledColor": {
+      "__type__": "cc.Color",
+      "r": 124,
+      "g": 124,
+      "b": 124,
+      "a": 255
+    },
+    "_normalSprite": null,
+    "_hoverSprite": null,
+    "_pressedSprite": null,
+    "_disabledSprite": null,
+    "_duration": 0.1,
+    "_zoomScale": 1.2,
+    "_target": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "4d+OJjWFNIIZESl24KgXyQ"
+  },
+  {
+    "__type__": "cc.ClickEvent",
+    "target": {
+      "__id__": 1
+    },
+    "component": "",
+    "_componentId": "0d3fdUMUH1Fq4G13IWj7Lf2",
+    "handler": "onButtonSlot",
+    "customEventData": ""
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "3cVpy0IIlLDYkv7r48ymH2",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_slot_4",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 66
+    },
+    "_children": [
+      {
+        "__id__": 136
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 142
+      },
+      {
+        "__id__": 144
+      },
+      {
+        "__id__": 146
+      },
+      {
+        "__id__": 148
+      }
+    ],
+    "_prefab": {
+      "__id__": 151
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": -154,
+      "y": -46,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "btn_gen",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 135
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 137
+      },
+      {
+        "__id__": 139
+      }
+    ],
+    "_prefab": {
+      "__id__": 141
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 136
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 138
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 80,
+      "height": 80
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "ff6S/rKI5FDZvdxzJm/F7g"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 136
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 140
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 180
+    },
+    "_spriteFrame": {
+      "__uuid__": "20835ba4-6145-4fbc-a58a-051ce700aa3e@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 1,
+    "_fillType": 0,
+    "_sizeMode": 0,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "891wYbHoRAs4dvZxBmafGl"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "722yKBoOFNJZVp2+6pOlu4",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 135
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 143
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 75,
+      "height": 75
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "085x7WealEvI1F0fR4p4va"
+  },
+  {
+    "__type__": "cc.Mask",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 135
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 145
+    },
+    "_type": 1,
+    "_inverted": false,
+    "_segments": 64,
+    "_alphaThreshold": 0.1,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "dbkZp8qcFPyIzu05/1dZNS"
+  },
+  {
+    "__type__": "cc.Graphics",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 135
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 147
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_lineWidth": 1,
+    "_strokeColor": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_lineJoin": 2,
+    "_lineCap": 0,
+    "_fillColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 0
+    },
+    "_miterLimit": 10,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "df6qBz7SREoqTkniEKY+nh"
+  },
+  {
+    "__type__": "cc.Button",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 135
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 149
+    },
+    "clickEvents": [
+      {
+        "__id__": 150
+      }
+    ],
+    "_interactable": true,
+    "_transition": 0,
+    "_normalColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_hoverColor": {
+      "__type__": "cc.Color",
+      "r": 211,
+      "g": 211,
+      "b": 211,
+      "a": 255
+    },
+    "_pressedColor": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_disabledColor": {
+      "__type__": "cc.Color",
+      "r": 124,
+      "g": 124,
+      "b": 124,
+      "a": 255
+    },
+    "_normalSprite": null,
+    "_hoverSprite": null,
+    "_pressedSprite": null,
+    "_disabledSprite": null,
+    "_duration": 0.1,
+    "_zoomScale": 1.2,
+    "_target": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "694a0S7zBDvZQlgOPhm5Jn"
+  },
+  {
+    "__type__": "cc.ClickEvent",
+    "target": {
+      "__id__": 1
+    },
+    "component": "",
+    "_componentId": "0d3fdUMUH1Fq4G13IWj7Lf2",
+    "handler": "onButtonSlot",
+    "customEventData": ""
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "efAUyEQUFMnI3ErfBo/lvM",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 66
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 153
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 100,
+      "height": 100
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "9aY0foPx9Ml5YHJPUweARm"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "30T2Nxx6BCL5T3LtY8Iets",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 156
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 1280,
+      "height": 720
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "0f76PkyR9CI6GSE13j2hvT"
+  },
+  {
+    "__type__": "cc.Widget",
+    "_name": "HUD<Widget>",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 158
+    },
+    "_alignFlags": 45,
+    "_target": null,
+    "_left": 0,
+    "_right": 0,
+    "_top": 0,
+    "_bottom": 0,
+    "_horizontalCenter": 0,
+    "_verticalCenter": 0,
+    "_isAbsLeft": true,
+    "_isAbsRight": true,
+    "_isAbsTop": true,
+    "_isAbsBottom": true,
+    "_isAbsHorizontalCenter": true,
+    "_isAbsVerticalCenter": true,
+    "_originalWidth": 100,
+    "_originalHeight": 100,
+    "_alignMode": 2,
+    "_lockFlags": 0,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "61G89wei9G5ZMYyMlGMFfu"
+  },
+  {
+    "__type__": "0d3fdUMUH1Fq4G13IWj7Lf2",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 160
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "easj9qwJ5K1ZLYsbWsVi7m"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "4eEaTnahNPd5NO5OdceSOx",
+    "instance": null,
+    "targetOverrides": null
+  }
+]

+ 13 - 0
assets/core_tgx/easy_controller/ui_joystick_panel.prefab.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "1.1.50",
+  "importer": "prefab",
+  "imported": true,
+  "uuid": "55ac4e22-a145-4a46-92f1-a45afa1b8f08",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {
+    "syncNodeName": "ui_joystick_panel"
+  }
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "86b8756c-d2bc-4eaa-b9cc-5d8948149be6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 74 - 0
assets/core_tgx/easy_ui_framework/EventDispatcher.ts

@@ -0,0 +1,74 @@
+/**
+ * @en the classes inherit from class:EventDispatcher will have the ability to dispatch events.
+ * @zh 事件派发器,继承自EventDispatcher的类将拥有事件派发能力
+ * 
+ *  */
+export class EventDispatcher {
+    private static _instance: EventDispatcher | null = null;
+    public static get instance(): EventDispatcher {
+        if (!this._instance) this._instance = new EventDispatcher();
+        return this._instance;
+    }
+
+    private _handlersMap: any = {};
+    public on(event: string, cb: Function, thisArg?: any, args?: [], once?: boolean) {
+        if (!event || !cb) {
+            return;
+        }
+
+        let handlers = this._handlersMap[event];
+        if (!handlers) {
+            handlers = this._handlersMap[event] = [];
+        }
+
+        handlers.push({
+            event: event,
+            cb: cb,
+            thisArg: thisArg,
+            once: once,
+            args: args
+        });
+    }
+
+    public once(event: string, cb: Function, thisArg: any, args: []) {
+        this.on(event, cb, thisArg, args, true);
+    }
+
+    public off(event: string, cb: Function, thisArg?: any, once?: boolean) {
+        let handlers = this._handlersMap[event];
+        if (!handlers) {
+            return;
+        }
+        for (let i = 0; i < handlers.length; ++i) {
+            let h = handlers[i];
+            if (h.cb == cb && h.thisArg == thisArg && h.once == once) {
+                handlers.splice(i, 1);
+                return;
+            }
+        }
+    }
+
+    public clearAll(event?: string) {
+        if (event) {
+            delete this._handlersMap[event];
+        }
+        else {
+            this._handlersMap = {};
+        }
+    }
+
+    public emit(event: string, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any) {
+
+        let handlers = this._handlersMap[event];
+        if (!handlers || !handlers.length) {
+            return;
+        }
+        let args = [arg0, arg1, arg2, arg3, arg4];
+        for (let i = 0; i < handlers.length; ++i) {
+            let h = handlers[i];
+            if (h.event == event) {
+                h.cb.apply(h.thisArg, args);
+            }
+        }
+    }
+}

+ 13 - 0
assets/core_tgx/easy_ui_framework/EventDispatcher.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "67accfbe-c0d5-4d50-b3ff-39481eb8e5b8",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/EventDispatcher.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 279 - 0
assets/core_tgx/easy_ui_framework/UICanvas.prefab

@@ -0,0 +1,279 @@
+[
+  {
+    "__type__": "cc.Prefab",
+    "_name": "UICanvas",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_native": "",
+    "data": {
+      "__id__": 1
+    },
+    "optimizationPolicy": 0,
+    "persistent": false
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "UICanvas",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": null,
+    "_children": [
+      {
+        "__id__": 2
+      }
+    ],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 6
+      },
+      {
+        "__id__": 8
+      },
+      {
+        "__id__": 10
+      }
+    ],
+    "_prefab": {
+      "__id__": 12
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 360,
+      "y": 667,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 33554432,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "Camera",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 1
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 3
+      }
+    ],
+    "_prefab": {
+      "__id__": 5
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 1000
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 1073741824,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.Camera",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 2
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 4
+    },
+    "_projection": 0,
+    "_priority": 1073741824,
+    "_fov": 45,
+    "_fovAxis": 0,
+    "_orthoHeight": 667,
+    "_near": 1,
+    "_far": 2000,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 0,
+      "g": 0,
+      "b": 0,
+      "a": 255
+    },
+    "_depth": 1,
+    "_stencil": 0,
+    "_clearFlags": 6,
+    "_rect": {
+      "__type__": "cc.Rect",
+      "x": 0,
+      "y": 0,
+      "width": 1,
+      "height": 1
+    },
+    "_aperture": 19,
+    "_shutter": 7,
+    "_iso": 0,
+    "_screenScale": 1,
+    "_visibility": 41943040,
+    "_targetTexture": null,
+    "_postProcess": null,
+    "_usePostProcess": false,
+    "_cameraType": -1,
+    "_trackingType": 0,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "42DBpTD0dE+qIujWZxlqAy"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "a0vsi/abBAhrtQm8+CUIPW",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 7
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 720,
+      "height": 1334
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "80p16DFJZKKJtTQXJb+s7I"
+  },
+  {
+    "__type__": "cc.Canvas",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 9
+    },
+    "_cameraComponent": {
+      "__id__": 3
+    },
+    "_alignCanvasWithScreen": true,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "b7JoyvJQ9Gaq71x8ryx35W"
+  },
+  {
+    "__type__": "cc.Widget",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 11
+    },
+    "_alignFlags": 45,
+    "_target": null,
+    "_left": 0,
+    "_right": 0,
+    "_top": 0,
+    "_bottom": 0,
+    "_horizontalCenter": 0,
+    "_verticalCenter": 0,
+    "_isAbsLeft": true,
+    "_isAbsRight": true,
+    "_isAbsTop": true,
+    "_isAbsBottom": true,
+    "_isAbsHorizontalCenter": true,
+    "_isAbsVerticalCenter": true,
+    "_originalWidth": 0,
+    "_originalHeight": 0,
+    "_alignMode": 2,
+    "_lockFlags": 0,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "9a9ZvNn/5DpKsAH5Y8nMqi"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "145iqBjTFBMqKGpO3jp/qF",
+    "instance": null,
+    "targetOverrides": null
+  }
+]

+ 13 - 0
assets/core_tgx/easy_ui_framework/UICanvas.prefab.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "1.1.50",
+  "importer": "prefab",
+  "imported": true,
+  "uuid": "469e39c1-3bdc-4490-987b-99aaa169fdf4",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {
+    "syncNodeName": "UICanvas"
+  }
+}

+ 390 - 0
assets/core_tgx/easy_ui_framework/UIController.ts

@@ -0,0 +1,390 @@
+import { _decorator, game, Prefab, isValid, Button, find, EventHandler, Toggle, ToggleContainer, Node, SkeletalAnimation, Component, EventTouch } from "cc";
+const { ccclass, property } = _decorator;
+/***
+ * @en internal class, used for handling node event.
+ * @zh 内部类,用于节点事件监听
+ * 
+ *  */
+@ccclass('tgxNodeEventAgent')
+export class __NodeEventAgent__ extends Component {
+    /***
+     * @en recieve button click event and deliver them to the real handlers.
+     * @zh 接受按钮事件,并转发给真正的处理函数
+     * */
+    onButtonClicked(evt: EventTouch, customEventData) {
+        let btn = (evt.target as Node).getComponent(Button);
+        let clickEvents = btn.clickEvents;
+        for (let i = 0; i < clickEvents.length; ++i) {
+            let h = clickEvents[i];
+            if (h.customEventData == customEventData) {
+                let cb = h['$cb$'];
+                let target = h['$target$']
+                let args = h['$args$'];
+                cb.apply(target, [btn, args]);
+            }
+        }
+    }
+
+    /***
+     * @en recieve toggle event and deliver them to the real handlers.
+     * @zh 接受Toggle事件,并转发给真正的处理函数
+     * */
+    onToggleEvent(toggle: Toggle, customEventData) {
+        let checkEvents = toggle.checkEvents;
+        //if (toggle['_toggleContainer']) {
+        //    checkEvents = toggle['_toggleContainer'].checkEvents;
+        //}
+        for (let i = 0; i < checkEvents.length; ++i) {
+            let h = checkEvents[i];
+            if (h.customEventData == customEventData) {
+                let cb = h['$cb$'];
+                let target = h['$target$']
+                let args = h['$args$'];
+                cb.apply(target, [toggle, args]);
+            }
+        }
+    }
+
+}
+
+/**
+ * @en manage event-handlers automatically, will remove all handlers when the ui destroyed.
+ * @zh 自动管理事件,将在UI销毁时自动清理
+ * */
+export class AutoEventHandler {
+    private _handlers = [];
+    on(event: string, cb: () => void, target?: any, once?: boolean) {
+        this._handlers.push({
+            event: event,
+            cb: cb,
+            target: target,
+            once: once
+        });
+        game.on(event, cb, target, once);
+    }
+
+    off(event: string, cb: () => void, target?: any, once?: boolean) {
+        game.off(event, cb, target);
+        for (let i = 0; i < this._handlers.length; ++i) {
+            let h = this._handlers[i];
+            if (h.event == event && h.cb == cb && h.target == target && h.once == once) {
+                this._handlers.splice(i, 1);
+                return;
+            }
+        }
+    }
+
+    dispose() {
+        for (let i = 0; i < this._handlers.length; ++i) {
+            let h = this._handlers[i];
+            game.off(h.event, h.cb, h.target);
+        }
+    }
+}
+
+/**
+ * @en base class of UI Panel
+ * @zh 各类UI面板基类
+ * */
+export class UIController extends AutoEventHandler {
+    private static _idBase = 1000;
+
+    private static _controllers: UIController[] = [];
+    private _instId: number = 0;
+    private _prefab: string | Prefab;
+    private _layer: number;
+    private _layout: any;
+    protected node: Node;
+    constructor(prefab: string | Prefab, layer: number, layoutCls: any) {
+        super();
+        this._prefab = prefab;
+        this._layer = layer;
+        this._layout = layoutCls;
+        this._instId = UIController._idBase++;
+        UIController._controllers.push(this);
+    }
+
+    /***
+     * @en the instance id to indicate an unique ui panel.
+     * @zh 实例ID,用于标记一个唯一面板实例
+     *  */
+    public get instId(): number {
+        return this._instId;
+    }
+
+    /***
+     * @en url of the prefab used by this ui panel.
+     * @zh 本UI使用prefab路径
+     *  */
+    public get prefab(): string | Prefab {
+        return this._prefab;
+    }
+
+    /***
+     * @en layer of this ui panel.
+     * @zh 本UI所在的UI层级
+     *  */
+    public get layer(): number {
+        return this._layer;
+    }
+
+    /***
+     * @en layout of this ui panel.
+     * @zh 本UI所在的UI层级
+     *  */
+    public get layout(): any {
+        return this._layout;
+    }
+
+    /***
+     * @en hide and destroy all ui panel.
+     * @zh 隐藏并销毁所有UI面板
+     *  */
+    public static hideAll() {
+        while (this._controllers.length) {
+            this._controllers[0].hide();
+        }
+    }
+
+    //update all ui, called by UIMgr.
+    public static updateAll() {
+        for (let i = 0; i < this._controllers.length; ++i) {
+            let ctrl = this._controllers[i];
+            if (ctrl.node && isValid(ctrl.node)) {
+                this._controllers[i].onUpdate();
+            }
+        }
+    }
+
+    //setup this ui,called by UIMgr.
+    public setup(node: Node) {
+        this.node = node;
+        if (this._layout) {
+            this._layout = this.node.getComponent(this._layout);
+        }
+        //结点创建完毕,调用子类的处理函数。
+        this.onCreated();
+    }
+
+    /**
+     * @en hide and destroy this ui panel.
+     * @zh 隐藏并销毁此UI面板
+     *  */
+    public hide() {
+        this.node.removeFromParent();
+        for (let i = 0; i < UIController._controllers.length; ++i) {
+            if (UIController._controllers[i] == this) {
+                UIController._controllers.splice(i, 1);
+                break;
+            }
+        }
+        this.dispose();
+        this.onDispose();
+        this.node.destroy();
+        this.node = null;
+    }
+
+    /**
+     * @en add button event handler
+     * @zh 添加按钮事件
+     * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
+     * @param cb will be called when event emits. method format:(btn:Button,args:any)=>void
+     * @param target the `this` argument of `cb`
+     *  */
+    onButtonEvent(relativeNodePath: string | Node | Button, cb: Function, target?: any, args?: any) {
+
+        let buttonNode: Node = null;
+        if (relativeNodePath instanceof Node) {
+            buttonNode = relativeNodePath;
+        }
+        else if (relativeNodePath instanceof Button) {
+            buttonNode = relativeNodePath.node;
+        }
+        else {
+            buttonNode = find(relativeNodePath, this.node);
+        }
+
+        if (!buttonNode) {
+            return null;
+        }
+
+        //添加转发器
+        let agent = this.node.getComponent(__NodeEventAgent__);
+        if (!agent) {
+            agent = this.node.addComponent(__NodeEventAgent__);
+        }
+
+        let btn = buttonNode.getComponent(Button);
+        let clickEvents = btn.clickEvents;
+        let handler = new EventHandler();
+        handler.target = this.node;
+        handler.component = 'tgxNodeEventAgent';
+        handler.handler = 'onButtonClicked';
+        handler.customEventData = '' + UIController._idBase++;
+
+        //附加额外信息 供事件转发使用
+        handler['$cb$'] = cb;
+        handler['$target$'] = target;
+        handler['$args$'] = args;
+
+        clickEvents.push(handler);
+        btn.clickEvents = clickEvents;
+    }
+
+    /**
+     * @en remove button event handler
+     * @zh 移除按钮事件
+     * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
+     * @param cb will be called when event emits.
+     * @param target the `this` argument of `cb`
+     *  */
+    offButtonEvent(relativeNodePath: string | Node | Button, cb: Function, target: any) {
+        let buttonNode: Node = null;
+        if (relativeNodePath instanceof Node) {
+            buttonNode = relativeNodePath;
+
+        }
+        else if (relativeNodePath instanceof Button) {
+            buttonNode = relativeNodePath.node;
+        }
+        else {
+            buttonNode = find(relativeNodePath, this.node);
+        }
+
+        if (!buttonNode) {
+            return; ``
+        }
+
+        let agent = this.node.getComponent(__NodeEventAgent__);
+        if (!agent) {
+            return;
+        }
+        let btn = buttonNode.getComponent(Button);
+        if (!btn) {
+            return;
+        }
+        let clickEvents = btn.clickEvents;
+        for (let i = 0; i < clickEvents.length; ++i) {
+            let h = clickEvents[i];
+            if (h['$cb$'] == cb && h['$target$'] == target) {
+                clickEvents.splice(i, 1);
+                btn.clickEvents = clickEvents;
+                break;
+            }
+        }
+    }
+
+    /**
+     * @en add toggle event handler
+     * @zh 添加Toggle事件
+     * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
+     * @param cb will be called when event emits. method format:(btn:Toggle,args:any)=>void
+     * @param target the `this` argument of `cb`
+     *  */
+
+    onToggleEvent(relativeNodePath: string | Node | Toggle | ToggleContainer, cb: Function, target?: any, args?: any) {
+        let buttonNode: Node = null;
+        if (relativeNodePath instanceof Node) {
+            buttonNode = relativeNodePath;
+        }
+        else if (relativeNodePath instanceof Toggle) {
+            buttonNode = relativeNodePath.node;
+        }
+        else if (relativeNodePath instanceof ToggleContainer) {
+            buttonNode = relativeNodePath.node;
+        }
+        else {
+            buttonNode = find(relativeNodePath, this.node);
+        }
+
+        if (!buttonNode) {
+            return null;
+        }
+
+        //添加转发器
+        let agent = this.node.getComponent(__NodeEventAgent__);
+        if (!agent) {
+            agent = this.node.addComponent(__NodeEventAgent__);
+        }
+
+        let btn = buttonNode.getComponent(Toggle) as any;
+        if (!btn) {
+            btn = buttonNode.getComponent(ToggleContainer) as any;
+        }
+        let checkEvents = btn.checkEvents;
+        let handler = new EventHandler();
+        handler.target = this.node;
+        handler.component = 'tgxNodeEventAgent';
+        handler.handler = 'onToggleEvent';
+        handler.customEventData = '' + UIController._idBase++;
+
+        //附加额外信息 供事件转发使用
+        handler['$cb$'] = cb;
+        handler['$target$'] = target;
+        handler['$args$'] = args;
+
+        checkEvents.push(handler);
+        btn.checkEvents = checkEvents;
+    }
+
+    /**
+     * @en remove toggle event handler
+     * @zh 移除Toggle事件
+     * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
+     * @param cb will be called when event emits. method format:(btn:Toggle,args:any)=>void
+     * @param target the `this` argument of `cb`
+     *  */
+    offToggleEvent(relativeNodePath: string | Node | Toggle | ToggleContainer, cb: Function, target: any) {
+        let buttonNode: Node = null;
+        if (relativeNodePath instanceof Node) {
+            buttonNode = relativeNodePath;
+        }
+        else if (relativeNodePath instanceof Toggle) {
+            buttonNode = relativeNodePath.node;
+        }
+        else if (relativeNodePath instanceof ToggleContainer) {
+            buttonNode = relativeNodePath.node;
+        }
+        else {
+            buttonNode = find(relativeNodePath, this.node);
+        }
+
+        if (!buttonNode) {
+            return null;
+        }
+
+        //添加转发器
+        let agent = this.node.getComponent(__NodeEventAgent__);
+        if (!agent) {
+            return;
+        }
+        let btn = buttonNode.getComponent(Toggle) as any;
+        if (!btn) {
+            btn = buttonNode.getComponent(ToggleContainer) as any;
+        }
+        let checkEvents = btn.checkEvents;
+        for (let i = 0; i < checkEvents.length; ++i) {
+            let h = checkEvents[i];
+            if (h['$cb$'] == cb && h['$target$'] == target) {
+                checkEvents.splice(i, 1);
+                btn.checkEvents = checkEvents;
+                break;
+            }
+        }
+    }
+
+    /***
+     * @en the extra resource needed by this ui panel.the ui will not be created until these res loaded.
+     * @zh 本UI使用的依赖资源.UI会等这些资源加载完成后才创建。
+     *  */
+    public getRes(): [] {
+        return [];
+    }
+
+    //子类的所有操作,需要在这个函数之后。
+    protected onCreated() { }
+    //销毁
+    protected onDispose() { }
+    //
+    protected onUpdate() { }
+}

+ 13 - 0
assets/core_tgx/easy_ui_framework/UIController.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "de70297f-154d-46c8-8ce1-7cb5b678da8f",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/UIController.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 32 - 0
assets/core_tgx/easy_ui_framework/UILayers.ts

@@ -0,0 +1,32 @@
+/****
+ * @en ui layers.each project can modify it based on needs.
+ * @zh UI层级划分,
+ * */
+enum UILayers {
+    GAME,
+    JOY_STICK,
+    HUD,
+    POPUP,
+    POPUP1,
+    POPUP2,
+    ALERT,
+    NOTICE,
+    LOADING,
+    OVERLAY,
+    NUM
+}
+
+const UILayerNames = [
+    'game',
+    'joy_stick',
+    'hud',
+    'popup',
+    'popup1',
+    'popup2',
+    'alert',
+    'notice',
+    'loading',
+    'overlay'
+];
+
+export { UILayers, UILayerNames };

+ 9 - 0
assets/core_tgx/easy_ui_framework/UILayers.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "c15e517f-242c-4163-bcd6-f924efeced3c",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 191 - 0
assets/core_tgx/easy_ui_framework/UIMgr.ts

@@ -0,0 +1,191 @@
+import { _decorator, Node, Prefab, instantiate, Widget, UITransform, view, ResolutionPolicy, assetManager, AssetManager, director, error, Component } from 'cc';
+import { UIController } from './UIController';
+import { ModuleContext } from '../base/ModuleContext';
+import { ResolutionAutoFit } from '../base/ResolutionAutoFit';
+
+const { ccclass, property } = _decorator;
+
+@ccclass('tgxUIMgr.UIUpdater')
+class UIUpdater extends Component {
+    update() {
+        UIController.updateAll();
+    }
+}
+
+/**
+ * @en the `User Interface Manager`, handles some stuffs like the ui loads,ui layers,resizing etc.
+ * @zh UI管理器,处理UI加载,层级,窗口变化等
+ * 
+ * */
+export class UIMgr {
+
+    private static _inst: UIMgr;
+    public static get inst(): UIMgr {
+        if (this._inst == null) {
+            this._inst = new UIMgr();
+        }
+        return this._inst;
+    }
+
+    private _uiCanvas: Node;
+    private _uiRoot: Node;
+
+    private createFullScreenNode() {
+        let canvas = this._uiCanvas.getComponent(UITransform);
+        let node = new Node();
+        node.layer = this._uiCanvas.layer;
+        let uiTransform = node.addComponent(UITransform);
+        uiTransform.width = canvas.width;
+        uiTransform.height = canvas.height;
+
+        let widget = node.addComponent(Widget);
+        widget.isAlignBottom = true;
+        widget.isAlignTop = true;
+        widget.isAlignLeft = true;
+        widget.isAlignRight = true;
+
+        widget.left = 0;
+        widget.right = 0;
+        widget.top = 0;
+        widget.bottom = 0;
+        return node;
+    }
+
+    /**
+     * @en setup this UIMgr,`don't call more than once`.
+     * @zh 初始化UIMgr,`不要多次调用`
+     *  */
+    public setup(uiCanvas: Node | Prefab, maxLayers: number, layerNames?: Array<string>) {
+        if (this._uiCanvas) {
+            return;
+        }
+
+        if (!uiCanvas) {
+            throw error('uiCanvas must be a Node or Prefab');
+        }
+        if (uiCanvas instanceof Node) {
+            this._uiCanvas = uiCanvas;
+        }
+        else {
+            this._uiCanvas = instantiate(uiCanvas);
+            director.getScene().addChild(this._uiCanvas);
+        }
+
+        this._uiCanvas.name = '$tgxUICanvas$';
+        director.addPersistRootNode(this._uiCanvas);
+
+        if (!this._uiCanvas.getComponent(UIUpdater)) {
+            this._uiCanvas.addComponent(UIUpdater);
+        }
+
+        //this.resize();
+        let canvas = this._uiCanvas.getComponent(UITransform);
+        this._uiCanvas.addComponent(ResolutionAutoFit);
+
+        layerNames ||= [];
+
+        this._uiRoot = this.createFullScreenNode();
+        this._uiRoot.name = 'ui_root'
+        canvas.node.addChild(this._uiRoot);
+
+        //create layers
+        for (let i = 0; i < maxLayers; ++i) {
+            let layerNode = this.createFullScreenNode();
+            layerNode.name = 'ui_layer_' + (layerNames[i] ? layerNames[i] : i);
+            this._uiRoot.addChild(layerNode);
+        }
+    }
+
+    public getLayerNode(layerIndex: number): Node {
+        return this._uiRoot.children[layerIndex] || this._uiRoot;
+    }
+
+    public hideAll() {
+        UIController.hideAll();
+    }
+
+    public getUI<T extends UIController>(uiCls): T {
+        let allControllers = (UIController as any)._controllers;
+        for (let i = 0; i < allControllers.length; ++i) {
+            let c = allControllers[i];
+            if (c instanceof uiCls) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    public isShowing(uiCls: any): boolean {
+        let allControllers = (UIController as any)._controllers;
+        for (let i = 0; i < allControllers.length; ++i) {
+            if (allControllers[i] instanceof uiCls) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /***
+     * @en show ui by the given parameters.
+     * @zh 显示UI
+     * @param uiCls the class, must inherits from the class `UIController`.
+     * @param cb will be called after ui created.
+     * @param thisArg the this argument for param `cb`.
+     * @returns the instance of `uiCls`
+     *  */
+    public showUI(uiCls: any, cb?: Function, thisArg?: any): any {
+        let bundleName = ModuleContext.getClassModule(uiCls);
+        if (bundleName) {
+            let bundle = assetManager.getBundle(bundleName);
+            if (!bundle) {
+                assetManager.loadBundle(bundleName, null, (err, loadedBundle) => {
+                    if (err) {
+                        console.log(err);
+                    }
+                    else {
+                        this.showUI(uiCls, cb, thisArg);
+                    }
+                });
+                return;
+            }
+        }
+
+        let ui = ModuleContext.createFromModule(uiCls) as UIController;
+        let resArr = ui.getRes() || [];
+        if (typeof (ui.prefab) == 'string') {
+            resArr.push(ui.prefab as never);
+        }
+
+        let fnLoadAndCreateFromBundle = (bundle: AssetManager.Bundle) => {
+            bundle.load(resArr, (err, data) => {
+                if (err) {
+                    console.log(err);
+                }
+                let node: Node = null;
+                let prefab: Prefab = ui.prefab as Prefab;
+                if (typeof (ui.prefab) == 'string') {
+                    prefab = bundle.get(ui.prefab) as Prefab;
+                }
+                if (prefab) {
+                    node = instantiate(prefab);
+                }
+                else {
+                    //special for empty ui
+                    node = this.createFullScreenNode();
+                }
+
+                let parent = UIMgr.inst.getLayerNode(ui.layer);
+                parent.addChild(node);
+                ui.setup(node);
+                if (cb) {
+                    cb.apply(thisArg, [ui]);
+                }
+            });
+            return ui;
+        }
+
+        bundleName = bundleName || 'resources';
+        let bundle = assetManager.getBundle(bundleName);
+        return fnLoadAndCreateFromBundle(bundle);
+    }
+}

+ 13 - 0
assets/core_tgx/easy_ui_framework/UIMgr.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "82a59405-4865-4862-b064-ae0017d54dab",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/UIMgr.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework/alert.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "164a7aa2-93fa-4b70-9d5b-de1a4ffcbde6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 28 - 0
assets/core_tgx/easy_ui_framework/alert/Layout_UIAlert.ts

@@ -0,0 +1,28 @@
+import { _decorator, Button, Component, Label, Node } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('tgxLayout_UIAlert')
+export class Layout_UIAlert extends Component {
+
+    @property(Label)
+    title: Label;
+
+    @property(Label)
+    content: Label;
+
+    @property(Button)
+    btnOK: Button;
+
+    @property(Button)
+    btnCancel: Button;
+
+    @property(Node)
+    fillUp: Node;
+
+    @property(Node)
+    remove: Node;
+
+    @property(Node)
+    refresh: Node;
+}
+

+ 9 - 0
assets/core_tgx/easy_ui_framework/alert/Layout_UIAlert.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6ecd6225-cc8f-473c-b208-a90e1a1d1710",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 100 - 0
assets/core_tgx/easy_ui_framework/alert/UIAlert.ts

@@ -0,0 +1,100 @@
+import { UIController } from "../UIController";
+import { UIMgr } from "../UIMgr";
+import { Layout_UIAlert } from "./Layout_UIAlert";
+
+export class UIAlertOptions {
+    private _title?: string;
+    private _type?: string;
+    private _content?: string;
+    private _showCancel?: boolean;
+    private _cbClick?: Function;
+    private _cbClickThisArg?: Function;
+
+    setTitle(title: string): UIAlertOptions {
+        this._title = title;
+        return this;
+    }
+
+    setType(type: string): UIAlertOptions {
+        this._type = type;
+        return this;
+    }
+
+    onClick(cb: (isOK: boolean) => void, thisArg?: any): UIAlertOptions {
+        this._cbClick = cb;
+        this._cbClickThisArg = thisArg;
+        return this;
+    }
+}
+
+export class UIAlert extends UIController {
+    private _options: UIAlertOptions;
+
+    public static show(content: string, type: string, showCancel?: boolean): UIAlertOptions {
+        let opts = new UIAlertOptions() as any;
+        opts._type = type;
+        opts._content = content;
+        opts._showCancel = showCancel;
+        UIMgr.inst.showUI(UIAlert, (alert: UIAlert) => {
+            alert.init(opts);
+        }) as UIAlert;
+        return opts;
+    }
+
+    private init(opts: UIAlertOptions) {
+        this._options = opts;
+        let options = this._options as any as { _title: string, _type: string, _content: string, _showCancel: boolean };
+        let layout = this.layout as Layout_UIAlert;
+        if (options.hasOwnProperty('title')) {
+            layout.title.string = options._title || '';
+        }
+
+        layout.content.string = options._content || '';
+        layout.btnCancel.node.active = options._showCancel;
+        if (!options._showCancel) {
+            let pos = layout.btnOK.node.position;
+            layout.btnOK.node.setPosition(0, pos.y, pos.z);
+        }
+
+        this.showAlerType(options._type);
+    }
+
+    protected onCreated(): void {
+        let layout = this.layout as Layout_UIAlert;
+        this.onButtonEvent(layout.btnOK, () => {
+            this.hide();
+            let options = this._options as any as { _cbClick: Function, _cbClickThisArg: any };
+            if (options._cbClick) {
+                options._cbClick.call(options._cbClickThisArg, true);
+            }
+        });
+
+        this.onButtonEvent(layout.btnCancel, () => {
+            this.hide();
+            let options = this._options as any as { _cbClick: Function, _cbClickThisArg: any };
+            if (options._cbClick) {
+                options._cbClick.call(options._cbClickThisArg, false);
+            }
+        });
+    }
+
+    private showAlerType(type: String): void {
+        let layout = this.layout as Layout_UIAlert;
+        layout.fillUp.active = false;
+        layout.remove.active = false;
+        layout.refresh.active = false;
+
+        switch (type) {
+            case "FillUp":
+                layout.fillUp.active = true;
+                break;
+            case "Remove":
+                layout.remove.active = true;
+                break;
+            case "Refresh":
+                layout.refresh.active = true;
+                break;
+        }
+
+    }
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework/alert/UIAlert.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "7e91ff06-d46b-4c43-8f8b-bedb3d884783",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework/tips.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "401061ea-055d-4417-bf05-9e66b52af9c7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 10 - 0
assets/core_tgx/easy_ui_framework/tips/Layout_UITips.ts

@@ -0,0 +1,10 @@
+import { _decorator, Button, Component, Label, Node } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('tgxLayout_UITips')
+export class Layout_UITips extends Component {
+
+    @property(Label)
+    content: Label;
+}
+

+ 9 - 0
assets/core_tgx/easy_ui_framework/tips/Layout_UITips.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2ac192ec-efed-4bd8-8899-99449ce9cbaf",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 96 - 0
assets/core_tgx/easy_ui_framework/tips/UITips.ts

@@ -0,0 +1,96 @@
+import { tween, Tween, UIOpacity, UITransform, Vec3 } from "cc";
+import { UIController } from "../UIController";
+import { UIMgr } from "../UIMgr";
+import { Layout_UITips } from "./Layout_UITips";
+
+export class UITipsOptions {
+    private _content?: string;
+
+    setTitle(content: string): UITipsOptions {
+        this._content = content;
+        return this;
+    }
+
+}
+
+export class UITips extends UIController {
+    private _curOpacity: UIOpacity | null = null;
+    private _curPositon = new Vec3;
+    private _transform: UITransform = null!;
+    private _options: UITipsOptions;
+    private _time: number = 1;
+
+    public static show(content: string): UITipsOptions {
+        let opts = new UITipsOptions() as any;
+        opts._content = content;
+        UIMgr.inst.showUI(UITips, (alert: UITips) => {
+            alert.init(opts);
+        }) as UITips;
+        return opts;
+    }
+
+    private init(opts: UITipsOptions) {
+        this._options = opts;
+        let options = this._options as any as { _content: string };
+        let layout = this.layout as Layout_UITips;
+        if (options.hasOwnProperty('_content')) {
+            layout.content.string = options._content || '';
+        }
+    }
+
+    protected onCreated(): void {
+        this._curOpacity = this.node.getComponent(UIOpacity);
+        this._transform = this.node.getComponent(UITransform) as UITransform;
+        this.fadeIn();
+        this.runTimeOut(this._time);
+    }
+
+    private runTimeOut(time: number) {
+        let self = this;
+        tween(this.node).delay(time).call(() => {
+            self.fadeOut();
+        }).start();
+    }
+
+    public fadeIn() {
+        if (!this._curOpacity) return;
+        Tween.stopAllByTarget(this._curOpacity);
+        this._curOpacity.opacity = 0;
+        tween(this._curOpacity)
+            .to(0.5, { opacity: 255 })
+            .start();
+        // this.moveTo(0, this.node.position.y + this._transform.height * 2);
+        this.node.setPosition(0, this.node.position.y + this._transform.height * 4);
+    }
+
+    public fadeOut() {
+        if (!this._curOpacity) return;
+        Tween.stopAllByTarget(this._curOpacity);
+        tween(this._curOpacity)
+            .to(2, { opacity: 0 })
+            .call(() => {
+                this.stopAllActions();
+                this.node?.removeFromParent();
+            })
+            .start();
+        // this.moveTo(0, this.node.position.y + this._transform.height * 2);
+    }
+
+    public moveTo(x: number, y: number) {
+        Tween.stopAllByTarget(this._curPositon);
+        this._curPositon.set(this.node.position);
+        tween(this._curPositon).to(0.5, { x: x, y: y }, {
+            onUpdate: (target) => {
+                this.node?.setPosition(target as Vec3);
+            }, easing: "expoOut"
+        }).start();
+    }
+
+    public stopAllActions(): void {
+        Tween.stopAllByTarget(this.node);
+        Tween.stopAllByTarget(this._curPositon);
+        if (this._curOpacity) {
+            Tween.stopAllByTarget(this._curOpacity);
+        }
+    }
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework/tips/UITips.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "32922a9b-1632-4a4b-b2e0-b9231c94de4f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework/waiting.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "1e43872b-96f1-4acb-a791-9192a489f7d7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 12 - 0
assets/core_tgx/easy_ui_framework/waiting/Layout_UIWaiting.ts

@@ -0,0 +1,12 @@
+import { _decorator, Component, Label, Node } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('tgxLayout_UIWaiting')
+export class Layout_UIWaiting extends Component {
+    @property(Node)
+    loadingIcon:Node;
+
+    @property(Label)
+    loadingTxt:Label;
+}
+

+ 9 - 0
assets/core_tgx/easy_ui_framework/waiting/Layout_UIWaiting.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "cf09698a-2b2a-4173-904e-cb5d62fb9346",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 48 - 0
assets/core_tgx/easy_ui_framework/waiting/UIWaiting.ts

@@ -0,0 +1,48 @@
+
+import { UIController } from "../UIController";
+import { UIMgr } from "../UIMgr";
+import { Layout_UIWaiting } from "./Layout_UIWaiting";
+
+const loadingTxtArr = ['Loading.','Loading..','Loading...'];
+
+let _inst = null;
+
+export class UIWaiting extends UIController{
+
+    protected onCreated(): void {
+        
+    }
+
+    public static show():UIWaiting{
+        if(_inst){
+            return _inst;
+        }
+        _inst = UIMgr.inst.showUI((UIWaiting));
+        return _inst;
+    }
+
+    public static hide():void{
+        if(_inst){
+            _inst.hide();
+            _inst = null;
+        }
+    }
+
+    protected onUpdate() { 
+        let layout = this.layout as Layout_UIWaiting;
+        if(layout.loadingIcon){
+            let euler = layout.loadingIcon.eulerAngles;
+            let rot = (Date.now() / 1000) * 90;
+            layout.loadingIcon.setRotationFromEuler(euler.x,euler.y,rot);
+        }
+
+        if(layout.loadingTxt){
+            let idx = Math.floor(Date.now() / 500) % 3;
+            layout.loadingTxt.string = loadingTxtArr[idx];
+        }
+    }
+
+    onDispose(){
+        _inst = null;
+    }
+}

+ 9 - 0
assets/core_tgx/easy_ui_framework/waiting/UIWaiting.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0dda6990-3427-42a3-9190-f79f6a17f0db",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 32 - 0
assets/core_tgx/tgx.ts

@@ -0,0 +1,32 @@
+//base
+export { AudioMgr as tgxAudioMgr } from "./base/AudioMgr";
+export { InputMgr as tgxInputMgr } from "./base/InputMgr";
+export { ResourceMgr as tgxResourceMgr } from "./base/ResourceMgr";
+export { SafeJSON as tgxSafeJSON } from "./base/SafeJSON";
+export { ResolutionAutoFit as tgxResolutionAutoFit } from "./base/ResolutionAutoFit";
+export { ModuleContext as tgxModuleContext } from "./base/ModuleContext";
+
+//camera
+export { FPSCamera as tgxFPSCamera } from "./easy_camera/FPSCamera";
+export { FollowCamera2D as tgxFollowCamera2D } from "./easy_camera/FollowCamera2D";
+export { FreeCamera as tgxFreeCamera } from "./easy_camera/FreeCamera";
+export { ThirdPersonCamera as tgxThirdPersonCamera } from "./easy_camera/ThirdPersonCamera";
+
+//easy controller
+export { CharacterMovement as tgxCharacterMovement } from "./easy_controller/CharacterMovement";
+export { CharacterMovement2D as tgxCharacterMovement2D } from "./easy_controller/CharacterMovement2D";
+export { EasyController as tgxEasyController, EasyControllerEvent as tgxEasyControllerEvent } from "./easy_controller/EasyController";
+export { ThirdPersonCameraCtrl as tgxThirdPersonCameraCtrl } from "./easy_controller/ThirdPersonCameraCtrl";
+export { UI_Joystick as tgxUI_Joystick } from "./easy_controller/UI_Joystick";
+
+//ui framework
+export { Layout_UIAlert as tgxLayout_UIAlert } from "./easy_ui_framework/alert/Layout_UIAlert";
+export { UIAlert as tgxUIAlert } from "./easy_ui_framework/alert/UIAlert";
+export { Layout_UITips as tgxLayout_UITips } from "./easy_ui_framework/tips/Layout_UITips";
+export { UITips as tgxUITips } from "./easy_ui_framework/tips/UITips";
+export { Layout_UIWaiting as tgxLayout_UIWaiting } from "./easy_ui_framework/waiting/Layout_UIWaiting";
+export { UIWaiting as tgxUIWaiting } from "./easy_ui_framework/waiting/UIWaiting";
+export { EventDispatcher as tgxEventDispatcher } from "./easy_ui_framework/EventDispatcher";
+export { UIController as tgxUIController } from "./easy_ui_framework/UIController";
+export { UILayers as tgxUILayers, UILayerNames as gdsUILayerNames } from "./easy_ui_framework/UILayers";
+export { UIMgr as tgxUIMgr } from "./easy_ui_framework/UIMgr";

+ 9 - 0
assets/core_tgx/tgx.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "f6aff908-69b8-4d9e-a779-291ba1915cf2",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 12 - 0
assets/module_basic.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "e137dcbf-4ded-494f-85c4-a378409551b0",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "isBundle": true,
+    "priority": 6
+  }
+}

+ 9 - 0
assets/module_basic/config.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "e11ded6e-f26d-4ec0-9d10-b82c8fed0053",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 1 - 0
assets/module_basic/config/color_config.json

@@ -0,0 +1 @@
+{"1":{"colour":"#FF0707","color":"红"},"2":{"colour":"#FF9202","color":"橙"},"3":{"colour":"#FFF10A","color":"黄"},"4":{"colour":"#37FF13","color":"绿"},"5":{"colour":"#00F5FF","color":"青"},"6":{"colour":"#0160FF","color":"蓝"},"7":{"colour":"#E500FF","color":"紫"},"8":{"colour":"#B45422","color":"棕"},"9":{"colour":"#FF61B7","color":"粉"}}

+ 11 - 0
assets/module_basic/config/color_config.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "f154f905-0bf0-4474-93f9-52ff23cccc4c",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

File diff suppressed because it is too large
+ 0 - 0
assets/module_basic/config/levels_config.json


+ 11 - 0
assets/module_basic/config/levels_config.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "96eebdcc-a7da-449d-beda-22d724394f97",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 1 - 0
assets/module_basic/config/main_config.json

@@ -0,0 +1 @@
+{"1":{"param":5,"content":"加时弹窗获得的时间,单位秒"},"2":{"param":2,"content":"吸力范围,填黑洞当前直径的倍数"}}

+ 11 - 0
assets/module_basic/config/main_config.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "d661bd6b-1b3c-4fc6-b411-44c87d358e68",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 1 - 0
assets/module_basic/config/music_config.json

@@ -0,0 +1 @@
+{"1":{"name":"bgm","type":1,"cd":0,"content":"背景音乐"},"2":{"name":"dianji","type":2,"cd":0,"content":"UI按钮点击音效"},"3":{"name":"dianjiliangbei","type":2,"cd":0,"content":"点击量杯音效"},"4":{"name":"daoshui","type":2,"cd":0,"content":"倒水音效"},"5":{"name":"daoman","type":2,"cd":0,"content":"倒满调酒杯音效"},"6":{"name":"shuaxin","type":2,"cd":0,"content":"量杯刷新音效"},"7":{"name":"shengli","type":2,"cd":0,"content":"胜利"},"8":{"name":"shibai","type":2,"cd":0,"content":"失败"},"9":{"name":"suilie","type":2,"cd":0,"content":"冰块碎裂"}}

+ 11 - 0
assets/module_basic/config/music_config.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "478de945-a7b4-40b7-abfe-f061027e5387",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

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