tjt 1 year ago
parent
commit
078c64c2f6
100 changed files with 7420 additions and 0 deletions
  1. 2 0
      llk/.creator/asset-template/typescript/Custom Script Template Help Documentation.url
  2. 13 0
      llk/.creator/default-meta.json
  3. 9 0
      llk/assets/ch.meta
  4. 9 0
      llk/assets/ch/Sign.meta
  5. 9 0
      llk/assets/ch/audio.meta
  6. 338 0
      llk/assets/ch/audio/audio.ts
  7. 9 0
      llk/assets/ch/audio/audio.ts.meta
  8. 9 0
      llk/assets/ch/ch-sdk.meta
  9. 0 0
      llk/assets/ch/ch-sdk/ch-sdk.umd.js
  10. 17 0
      llk/assets/ch/ch-sdk/ch-sdk.umd.js.meta
  11. 284 0
      llk/assets/ch/ch-sdk/chsdk.d.ts
  12. 9 0
      llk/assets/ch/ch-sdk/chsdk.d.ts.meta
  13. 45 0
      llk/assets/ch/ch-sdk/提示说明.txt
  14. 11 0
      llk/assets/ch/ch-sdk/提示说明.txt.meta
  15. 27 0
      llk/assets/ch/ch.ts
  16. 9 0
      llk/assets/ch/ch.ts.meta
  17. 348 0
      llk/assets/ch/ch_util.ts
  18. 9 0
      llk/assets/ch/ch_util.ts.meta
  19. 379 0
      llk/assets/ch/chsdk_inside.d.ts
  20. 9 0
      llk/assets/ch/chsdk_inside.d.ts.meta
  21. 9 0
      llk/assets/ch/pvp.meta
  22. 241 0
      llk/assets/ch/pvp/ch_pvp.ts
  23. 1 0
      llk/assets/ch/pvp/ch_pvp.ts.meta
  24. 147 0
      llk/assets/ch/sign/sign.ts
  25. 9 0
      llk/assets/ch/sign/sign.ts.meta
  26. 9 0
      llk/assets/ch/start.meta
  27. 161 0
      llk/assets/ch/start/ch_start_pack.ts
  28. 1 0
      llk/assets/ch/start/ch_start_pack.ts.meta
  29. 9 0
      llk/assets/ch_ui_module.meta
  30. 9 0
      llk/assets/core.meta
  31. 9 0
      llk/assets/core/sdk.meta
  32. 9 0
      llk/assets/core/ui.meta
  33. 279 0
      llk/assets/core/ui/UICanvas.prefab
  34. 13 0
      llk/assets/core/ui/UICanvas.prefab.meta
  35. 320 0
      llk/assets/core/ui/ui.ts
  36. 1 0
      llk/assets/core/ui/ui.ts.meta
  37. 41 0
      llk/assets/core/ui/ui_ResolutionAutoFit.ts
  38. 9 0
      llk/assets/core/ui/ui_ResolutionAutoFit.ts.meta
  39. 358 0
      llk/assets/core/ui/ui_base.ts
  40. 9 0
      llk/assets/core/ui/ui_base.ts.meta
  41. 9 0
      llk/assets/core/util.meta
  42. 209 0
      llk/assets/core/util/ArrayUtil.ts
  43. 9 0
      llk/assets/core/util/ArrayUtil.ts.meta
  44. 420 0
      llk/assets/core/util/DataTimeUtil.ts
  45. 9 0
      llk/assets/core/util/DataTimeUtil.ts.meta
  46. 172 0
      llk/assets/core/util/DirectorUtil.ts
  47. 9 0
      llk/assets/core/util/DirectorUtil.ts.meta
  48. 46 0
      llk/assets/core/util/Instance.ts
  49. 9 0
      llk/assets/core/util/Instance.ts.meta
  50. 158 0
      llk/assets/core/util/LocalStorageUtil.ts
  51. 9 0
      llk/assets/core/util/LocalStorageUtil.ts.meta
  52. 496 0
      llk/assets/core/util/MathUtil.ts
  53. 9 0
      llk/assets/core/util/MathUtil.ts.meta
  54. 40 0
      llk/assets/core/util/PathUtil.ts
  55. 9 0
      llk/assets/core/util/PathUtil.ts.meta
  56. 189 0
      llk/assets/core/util/ProjectileMathUtil.ts
  57. 1 0
      llk/assets/core/util/ProjectileMathUtil.ts.meta
  58. 190 0
      llk/assets/core/util/ResUtil.ts
  59. 9 0
      llk/assets/core/util/ResUtil.ts.meta
  60. 99 0
      llk/assets/core/util/StringUtil.ts
  61. 9 0
      llk/assets/core/util/StringUtil.ts.meta
  62. 23 0
      llk/assets/core/util/TableLoadUtil.ts
  63. 9 0
      llk/assets/core/util/TableLoadUtil.ts.meta
  64. 53 0
      llk/assets/core/util/UrlUtil.ts
  65. 9 0
      llk/assets/core/util/UrlUtil.ts.meta
  66. 261 0
      llk/assets/core/util/Utils.ts
  67. 9 0
      llk/assets/core/util/Utils.ts.meta
  68. 9 0
      llk/assets/core/util_class.meta
  69. 95 0
      llk/assets/core/util_class/Action.ts
  70. 9 0
      llk/assets/core/util_class/Action.ts.meta
  71. 75 0
      llk/assets/core/util_class/Container.ts
  72. 9 0
      llk/assets/core/util_class/Container.ts.meta
  73. 48 0
      llk/assets/core/util_class/Delay.ts
  74. 9 0
      llk/assets/core/util_class/Delay.ts.meta
  75. 44 0
      llk/assets/core/util_class/FMS.ts
  76. 9 0
      llk/assets/core/util_class/FMS.ts.meta
  77. 230 0
      llk/assets/core/util_class/GameData.ts
  78. 9 0
      llk/assets/core/util_class/GameData.ts.meta
  79. 36 0
      llk/assets/core/util_class/HeadIcon.ts
  80. 9 0
      llk/assets/core/util_class/HeadIcon.ts.meta
  81. 78 0
      llk/assets/core/util_class/Line2DMove.ts
  82. 9 0
      llk/assets/core/util_class/Line2DMove.ts.meta
  83. 198 0
      llk/assets/core/util_class/PeriodData.ts
  84. 9 0
      llk/assets/core/util_class/PeriodData.ts.meta
  85. 61 0
      llk/assets/core/util_class/PrefabPool.ts
  86. 9 0
      llk/assets/core/util_class/PrefabPool.ts.meta
  87. 182 0
      llk/assets/core/util_class/Queue.ts
  88. 9 0
      llk/assets/core/util_class/Queue.ts.meta
  89. 107 0
      llk/assets/core/util_class/Random.ts
  90. 9 0
      llk/assets/core/util_class/Random.ts.meta
  91. 74 0
      llk/assets/core/util_class/SafeNumber.ts
  92. 9 0
      llk/assets/core/util_class/SafeNumber.ts.meta
  93. 50 0
      llk/assets/core/util_class/Shake.ts
  94. 9 0
      llk/assets/core/util_class/Shake.ts.meta
  95. 68 0
      llk/assets/core/util_class/State.ts
  96. 9 0
      llk/assets/core/util_class/State.ts.meta
  97. 198 0
      llk/assets/core/util_class/TimeJobCenter.ts
  98. 9 0
      llk/assets/core/util_class/TimeJobCenter.ts.meta
  99. 60 0
      llk/assets/core/util_class/Timer.ts
  100. 9 0
      llk/assets/core/util_class/Timer.ts.meta

+ 2 - 0
llk/.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

+ 13 - 0
llk/.creator/default-meta.json

@@ -0,0 +1,13 @@
+{
+    "texture": {
+      "minfilter": "linear",
+      "magfilter": "linear",
+      "mipfilter": "nearest"
+    },
+    "erp-texture-cube": {
+      "minfilter": "linear",
+      "magfilter": "linear",
+      "mipfilter": "nearest"
+    }
+  }
+  

+ 9 - 0
llk/assets/ch.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "60f90ec2-edfa-45a9-98aa-5169f753cc2f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/ch/Sign.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "b8872c79-0efd-4c7e-a793-8bbad5aca643",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/ch/audio.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "ef867cd6-6777-4779-ab30-581043020c10",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 338 - 0
llk/assets/ch/audio/audio.ts

@@ -0,0 +1,338 @@
+import { assetManager, AudioClip, AudioSource, director, Node } from "cc";
+const ch_log = chsdk.log;
+const ch_storage = chsdk.storage;
+/**音频等资源加载方式*/
+export enum loadType {
+    /**未知*/
+    none = 0,
+    /**bundle*/
+    bundle = 1,
+    /**远程*/
+    remote = 2,
+}
+/**音频播放控制
+ * 需要初始化资源加载方式
+ * loadType.bundle 时需要设置 bundle名
+ * loadType.remote 测试使用远程的音频需要设置远程地址
+*/
+export default class ch_audio {
+    private static _instance: ch_audio;
+    public static getInstance(): ch_audio {
+        if (!this._instance) this._instance = new ch_audio();
+        return this._instance;
+    }
+    private _volume_music: number = 1;
+    private _volume_effect: number = 1;
+    private _switch_music: boolean = true;
+    private _switch_effect: boolean = true;
+    private readonly _effect_max: number = 5;
+    private _effect_index: number = 0;
+    private _effect_source_pool: AudioSource[] = [];
+    private _music_source: AudioSource;
+    private _load_type: loadType = loadType.none;
+    private _bundle_name: string;
+    private _remote_url: string;
+    private _playing_sound: Set<string> = new Set();
+    constructor() {
+        const audio = new Node();
+        audio.name = '__ch_audio__';
+        director.getScene().addChild(audio);
+        director.addPersistRootNode(audio);
+        this._music_source = this._create(audio);
+        for (let i = 0; i < this._effect_max; i++) {
+            this._effect_source_pool.push(this._create(audio));
+        }
+        this.load();
+    }
+    /**
+     * 创建音频源
+     * @param node 节点
+     * @param volume 音量
+     * @returns AudioSource 音频源组件
+     */
+    private _create(node: Node): AudioSource {
+        const source = node.addComponent(AudioSource);
+        source.loop = false;
+        source.playOnAwake = false;
+        source.volume = 0.5;
+        return source;
+    }
+    /**初始化*/
+    init(load_type: loadType, bundle_name: string, remote_url: string): void {
+        this._load_type = load_type;
+        this._bundle_name = bundle_name;
+        this._remote_url = remote_url;
+    }
+    /**切换bundle*/
+    set_bundle_name(bundle_name: string): void {
+        this._bundle_name = bundle_name;
+    }
+    /**
+     * 释放通过 [[load]] 或者 [[loadDir]] 加载的声音资源。
+     * @param sound 声音资源路径
+     */
+    release(sound?: string): void {
+        if (this._load_type == loadType.none) {
+            ch_log.warn('音频模块未初始化');
+        } else if (this._load_type == loadType.bundle) {
+            const bundle = assetManager.getBundle(this._bundle_name);
+            if (!sound) {
+                bundle.releaseAll();
+            } else {
+                bundle.release(sound, AudioClip);
+            }
+        }
+    }
+    /** 保存音乐音效的音量、开关配置数据到本地 */
+    save() {
+        const local_data: any = {};
+        local_data.volume_music = this._volume_music;
+        local_data.volume_effect = this._volume_effect;
+        local_data.switch_music = this._switch_music;
+        local_data.switch_effect = this._switch_effect;
+        ch_storage.set("ch_audio", local_data);
+    }
+    /** 本地加载音乐音效的音量、开关配置数据并设置到游戏中 */
+    load() {
+        const local_data = ch_storage.getObject("ch_audio");
+        if (local_data) {
+            try {
+                this.setState(local_data);
+            }
+            catch (e) {
+                this.setStateDefault();
+            }
+        }
+        else {
+            this.setStateDefault();
+        }
+    }
+    private setState(local_data: { volume_music: number, volume_effect: number, switch_music: boolean, switch_effect: boolean }) {
+        this.volumeMusic = local_data.volume_music;
+        this.volumeEffect = local_data.volume_effect;
+        this.switchMusic = local_data.switch_music;
+        this.switchEffect = local_data.switch_effect;
+    }
+    private setStateDefault() {
+        this.volumeMusic = 0.8;
+        this.volumeEffect = 0.8;
+        this.switchMusic = true;
+        this.switchEffect = true;
+    }
+    /**
+     * 获取背景音乐音量
+     */
+    get volumeMusic(): number {
+        return this._volume_music;
+    }
+    /** 
+     * 设置背景音乐音量
+     * @param value     音乐音量值
+     */
+    set volumeMusic(value: number) {
+        this._volume_music = value;
+        this._music_source.volume = value;
+    }
+    /** 
+     * 获取背景音乐开关值 
+     */
+    get switchMusic(): boolean {
+        return this._switch_music;
+    }
+    /** 
+     * 设置背景音乐开关值
+     * @param value     开关值
+     */
+    set switchMusic(value: boolean) {
+        this._switch_music = value;
+        if (value == false) this._music_source.stop();
+    }
+    /** 
+     * 获取音效音量 
+     */
+    get volumeEffect(): number {
+        return this._volume_effect;
+    }
+    /**
+     * 设置获取音效音量
+     * @param value     音效音量值
+     */
+    set volumeEffect(value: number) {
+        this._volume_effect = value;
+        for (let i = 0; i < this._effect_source_pool.length; i++) {
+            this._effect_source_pool[i].volume = this._volume_effect;
+        }
+    }
+
+    /** 
+     * 获取音效开关值 
+     */
+    get switchEffect(): boolean {
+        return this._switch_effect;
+    }
+    /**
+     * 设置音效开关值
+     * @param value     音效开关值
+     */
+    set switchEffect(value: boolean) {
+        this._switch_effect = value;
+        if (value == false) {
+            for (let i = 0; i < this._effect_source_pool.length; i++) {
+                this._effect_source_pool[i].stop();
+            }
+        }
+    }
+    /**
+     * @en
+     * play short audio, such as strikes,explosions
+     * @zh
+     * 播放短音频,比如 打击音效,爆炸音效等
+     * @param sound clip or url for the audio
+     * @param interval 同名字音频限制播放间隔(毫秒)   (默认:0不限制  特殊系数:>0 <=1 使用音频时间X此系数)
+     */
+    playOneShot(sound: AudioClip | string, interval: number = 0, remote_ext: string = '.mp3') {
+        if (!this._switch_effect) return;
+        if (sound instanceof AudioClip) {
+            this.doPlayOneShot(sound, interval);
+        }
+        else {
+            if (this._load_type == loadType.none) {
+                ch_log.warn('音频模块未初始化');
+            } else if (this._load_type == loadType.bundle) {
+                const bundle = assetManager.getBundle(this._bundle_name);
+                if (!bundle) {
+                    ch_log.warn(`请确保 bundle${this._bundle_name} 已加载`);
+                } else {
+                    bundle.load(sound, (err, clip: AudioClip) => {
+                        if (err) {
+                            ch_log.error(err);
+                        }
+                        else {
+                            this.doPlayOneShot(clip, interval);
+                        }
+                    });
+                }
+            } else if (this._load_type == loadType.remote) {
+                assetManager.loadRemote(this._remote_url + sound + remote_ext, (err: Error | null, clip: AudioClip) => {
+                    if (err) {
+                        ch_log.error(err);
+                    }
+                    else {
+                        this.doPlayOneShot(clip, interval);
+                    }
+                });
+            }
+        }
+    }
+    private doPlayOneShot(clip: AudioClip, interval: number): void {
+        const name: string = clip.name;
+        if (interval > 0) {
+            if (this._playing_sound.has(name)) return;
+            this._playing_sound.add(name);
+            const time = interval <= 1 ? clip.getDuration() * interval * 1000 : interval;
+            setTimeout(() => { this._playing_sound.delete(name); }, time);
+            this.getNextEffectSource().playOneShot(clip, this._volume_effect);
+        } else {
+            this.getNextEffectSource().playOneShot(clip, this._volume_effect);
+        }
+    }
+    private getNextEffectSource(): AudioSource {
+        const source = this._effect_source_pool[this._effect_index];
+        this._effect_index = (this._effect_index + 1) % this._effect_max;
+        return source;
+    }
+    /**
+     * @en
+     * play long audio, such as the bg music
+     * @zh
+     * 播放长音频,比如 背景音乐
+     * @param sound clip or url for the sound
+     */
+    play(sound: AudioClip | string, remote_ext: string = '.mp3') {
+        if (!this._switch_music) return;
+        if (sound instanceof AudioClip) {
+            this._music_source.loop = true;
+            this._music_source.stop();
+            this._music_source.clip = sound;
+            this._music_source.play();
+            this._music_source.volume = this._volume_music;
+        }
+        else {
+            if (this._load_type == loadType.none) {
+                ch_log.warn('音频模块未初始化');
+            } else if (this._load_type == loadType.bundle) {
+                const bundle = assetManager.getBundle(this._bundle_name);
+                if (!bundle) {
+                    ch_log.warn(`请确保 bundle${this._bundle_name} 已加载`);
+                } else {
+                    bundle.load(sound, (err, clip: AudioClip) => {
+                        if (err) {
+                            ch_log.error(err);
+                        }
+                        else {
+                            this._music_source.loop = true;
+                            this._music_source.stop();
+                            this._music_source.clip = clip;
+                            this._music_source.play();
+                            this._music_source.volume = this._volume_music;
+                        }
+                    });
+                }
+            } else if (this._load_type == loadType.remote) {
+                assetManager.loadRemote(this._remote_url + sound + remote_ext, (err: Error | null, clip: AudioClip) => {
+                    if (err) {
+                        ch_log.error(err);
+                    }
+                    else {
+                        this._music_source.loop = true;
+                        this._music_source.stop();
+                        this._music_source.clip = clip;
+                        this._music_source.play();
+                        this._music_source.volume = this._volume_music;
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * stop the audio play
+     */
+    stop() {
+        this._music_source.stop();
+        for (let i = 0; i < this._effect_source_pool.length; i++) {
+            this._effect_source_pool[i].stop();
+
+        }
+    }
+    /**stop and clean */
+    clean() {
+        this._music_source.stop();
+        this._music_source.clip?.name
+        this._music_source.clip = null;
+        for (let i = 0; i < this._effect_source_pool.length; i++) {
+            this._effect_source_pool[i].stop();
+            this._effect_source_pool[i].clip = null;
+        }
+    }
+    /**
+     * pause the audio play
+     */
+    pause() {
+        this._music_source.pause();
+    }
+
+    /**
+     * resume the audio play
+     */
+    resume() {
+        if (!this._switch_music) return;
+        this._music_source.play();
+    }
+
+    /** 重播当前音乐 */
+    public replay_music(): void {
+        this._music_source.stop();
+        this._music_source.play();
+    }
+}

+ 9 - 0
llk/assets/ch/audio/audio.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "e18034b6-02ea-4a07-92d0-bc9ef0df5fc4",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/ch/ch-sdk.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "c77415ec-dc2b-41a4-8aca-b4fdbd6acb3b",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

File diff suppressed because it is too large
+ 0 - 0
llk/assets/ch/ch-sdk/ch-sdk.umd.js


+ 17 - 0
llk/assets/ch/ch-sdk/ch-sdk.umd.js.meta

@@ -0,0 +1,17 @@
+{
+  "ver": "4.0.24",
+  "importer": "javascript",
+  "imported": true,
+  "uuid": "1165cb67-475d-486e-91f1-55d85d3dd6eb",
+  "files": [
+    ".js"
+  ],
+  "subMetas": {},
+  "userData": {
+    "loadPluginInEditor": true,
+    "loadPluginInWeb": true,
+    "loadPluginInNative": true,
+    "loadPluginInMiniGame": true,
+    "isPlugin": true
+  }
+}

+ 284 - 0
llk/assets/ch/ch-sdk/chsdk.d.ts

@@ -0,0 +1,284 @@
+declare namespace chsdk {
+    /**服务器类型对应地址*/
+    enum serverType {
+        /**本地服*/
+        test = 1,
+        /**测试服*/
+        dev = 2,
+        /**正线服*/
+        online = 3
+    }
+    /**广告类型*/
+    enum ad_type {
+        /**激励视频*/
+        rewarded = "Rewarded",
+        /**插屏广告*/
+        interstitial = "Interstitial",
+        /**banner 广告*/
+        banner = "Banner",
+        /**自定义广告*/
+        custom = "Custom"
+    }
+    /**广告状态*/
+    enum ad_state {
+        /**展示失败*/
+        fail = 0,
+        /**展示成功*/
+        show = 1,
+        /**完整展示或点击*/
+        rewarded = 2
+    }
+    /**日志等级*/
+    enum loglevel {
+        OFF = 0,
+        ERROR = 1,
+        WARN = 2,
+        DEBUG = 3,
+        INFO = 4,
+        ALL = 5
+    }
+    /**请求码*/
+    enum code {
+        fail = 999,
+        /**请求成功*/
+        success = 0,
+        /**token失效 */
+        token_err = -1,
+        /**请求超时*/
+        time_out = 1001,
+        /**同一接口请求次数超出限制*/
+        too_many_requests = 1002
+    }
+    /**更新周期
+      1永久,
+      2日更新,
+      3周更新,
+      4月更新
+    */
+    enum updateType {
+        none = 1,
+        day = 2,
+        week = 3,
+        month = 4
+    }
+    /**初始化sdk,并登录对应平台
+     * @param gid 游戏ID(开发后台配置)
+     * @param loglevel 日志级别 关闭=0, ERROR = 1, WARN = 2, DEBUG = 3, INFO = 4, ALL = 5,
+     * @param serverIP 服务器类型  测试服 =2, 正线服 = 3,
+     * @returns code === 0 成功 err?=错误信息
+     */
+    function init(gid: string, loglevel: loglevel, serverIP: serverType): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**当前平台*/
+    function get_pf(): string;
+    /**玩家平台信息*/
+    function get_player_info(): {
+        nickName: string;
+        avatarUrl: string;
+        gender: number;
+        hid: number;
+        province: string;
+        ip: string;
+        loginTime: number;
+        registerTime: number;
+    };
+    /**游戏id*/
+    function get_gid(): string;
+    /**游戏是否初始完毕*/
+    function get_inited(): boolean;
+    /**玩家id*/
+    function get_uid(): number;
+    /**玩家openid*/
+    function get_openid(): string;
+    /**获取平台上用户的呢称和头像,获取成功后自动同步到服务器,一般在进排行榜前调用*/
+    function getUserInfo(): Promise<{
+        nickName: string;
+        avatarUrl: string;
+        gender: number;
+    }>;
+    /**上报自定义埋点事件
+    * @param evt 后台定义的事件名
+    * @param data 后台定义的事件参数
+    */
+    function reportEvent(evt: string, data: {
+        [key: string]: any;
+    }): void;
+    /**上报广告
+    * @param adsId 广告id
+    * @param adsState 广告状态 展示失败=0,展示成功=1,完整展示获得奖励=2
+    * @param adsScene 广告场景目的
+    * @param adsType 广告类型  (默认)激励视频 ='Rewarded',插屏广告='Interstitial', banner 广告 ='Banner',自定义广告 ='Custom'
+    * @param adStartTime 开始播放时间戳ms
+    * @returns code = 0 上报成功 err 上报错误提示
+    */
+    function reportSeeAds(adsId: string, adsType: ad_type, adsState: ad_state, adsScene: string, adStartTime: number): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 上传游戏远程存储数据
+    * @param key 要保存的key
+    * @param save_data 游戏数据
+    * @returns code 错误码 errMsg 错误信息
+    */
+    function saveGameData(key: string, save_data: {
+        [key: string]: any;
+    }): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取游戏远程存储数据
+    * @param key 数据key
+    * @returns code 错误码 errMsg 错误信息 data 游戏数据
+    */
+    function loadGameData(key: string): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 更改用户名称头像等信息
+    * @param nickName 名称
+    * @param avatar 头像
+    */
+    function changeUserData(nickName?: string, avatar?: string): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+  * @description 上传排行榜数据 
+  * @param rankField 排行榜类型标识
+  * @param rankValue 分数(如果分数和缓存中的数据一致将不上传)
+  * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+  * @param valueType 数值类 0覆盖(默认) 1:累加
+  * @param extend 扩展数据(非必须)
+  */
+    function saveRankData(rankField: string, rankValue: number, update?: chsdk.updateType, valueType?: 0 | 1, extend?: {
+        [key: string]: any;
+    }): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取排行榜信息
+    * @param rankField 排行榜类型标识
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param count 拉取个数
+    * @param isOwn 是否拉取自己的排行 默认为false
+    * @param cache 默认为true,1分钟内优先从缓存中拿排行数据,否则请求服务器
+    * @returns data:{errCode 错误码 errMsg 错误信息 data 排行榜数据(list) own 自己的排名}
+    */
+    function loadRankData(rankField: string, update?: updateType, count?: number, isOwn?: boolean, cache?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            list?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            }[];
+            own?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            };
+        };
+    }>;
+    /**
+    * @description 上传地区排行榜数据
+    * @param rankField 排行榜类型标识
+    * @param rankValue 分数(如果分数和缓存中的数据一致将不上传)
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param extend 扩展数据(非必须)
+    */
+    function saveProvinceRankData(rankField: string, rankValue: number, update?: updateType, extend?: {
+        [key: string]: any;
+    }): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取具体某地区排行榜信息
+    * @param hid 省份代码
+    * @param rankField 排行榜类型标识
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param count 拉取个数
+    * @param isOwn 是否显示自己的排行 默认为false不显示
+    * @param cache 默认为true,1分钟内优先从缓存中拿排行数据,否则请求服务器
+    * @returns data:{errCode 错误码 errMsg 错误信息 data 排行榜数据数组}
+    */
+    function loadProvinceRankData(hid: number, rankField: string, update?: updateType, count?: number, isOwn?: boolean, cache?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            list?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            }[];
+            own?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            };
+        };
+    }>;
+    /**
+    * @description 获取全国省份地区排行榜信息
+    * @param rankField 排行榜类型
+    * @param cache 默认为true,1分钟内优先从缓存中拿排行数据,否则请求服务器
+    * @returns data:{errCode 错误码 errMsg 错误信息 data 排行榜数据数组{hid 省份代码,province省份名...}}
+    */
+    function loadProvinceRankInfo(rankField: string, cache?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            hid: number;
+            province: string;
+            rank: number;
+            score: number;
+        }[];
+    }>;
+    /**玩家改变玩家默认省份
+    * @param hid 省份代码
+    * @returns errCode 错误码 errMsg 错误信息 time服务器时间戳(秒)
+    */
+    function setUserLocation(hid: number): Promise<{
+        code: number;
+        err?: string;
+        data?: number;
+    }>;
+    /**获取服务器时间戳(秒)
+    * @returns errCode 错误码 errMsg 错误信息 time服务器时间戳(秒)
+    */
+    function getServerTime(): Promise<{
+        code: number;
+        err?: string;
+        time?: number;
+    }>;
+    /**获取城市代码对应地名 */
+    function provinceCode2Name(hid: number): string;
+    /**获取所有城市代码名字*/
+    function getAllProvince(): [number, string][];
+}

+ 9 - 0
llk/assets/ch/ch-sdk/chsdk.d.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0677b62c-5ed1-4c63-b79b-8aa69e6cfb88",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 45 - 0
llk/assets/ch/ch-sdk/提示说明.txt

@@ -0,0 +1,45 @@
+1.
+chsdk.d.ts
+ch-sdk.umd.js
+直接将上面的文件复制到cocosreator assets里
+
+2.
+尽早调用chsdk.init 初始化后成功后可以调用其它接口
+具体接口参看接口chsdk.d.ts提示说明
+3.
+注意 chsdk.init 初始化的参数
+上线测试阶段初始化可以使用测试服
+正式上线后改为正式服关闭调试输出
+游戏 id和自定义上报事件需要在后台设置,需要和运营人员沟通
+
+下面的地址需要微信或抖音后台添加到白名单
+测试服务器地址
+https://dev.ichunhao.cn
+https://receivetest.ichunhao.cn
+正式服务器地址
+https://receive.ichunhao.cn
+https://app.ichunhao.cn
+
+
+cocoscreator 测试脚本
+@ccclass('test')
+export class test extends Component {
+    async start() {
+        const ret = await chsdk.init('1001',chsdk.loglevel.ALL,chsdk.serverType.dev);
+        console.log('初始化',ret); 
+        let r = await chsdk.saveRankData('test',99,chsdk.updateType.day);
+        console.log('上传排行傍数据',r); 
+        r = await chsdk.loadRankData('test',chsdk.updateType.day,50,true);
+        console.log('拉取排行傍数据',r); 
+        r = await chsdk.saveGameData('test',{test_data:"测试数据"});
+        console.log('保存游戏数据',r);
+        r = await chsdk.loadGameData('test');
+        console.log('加载游戏数据',r);
+        r = await chsdk.getServerTime();
+        console.log('获取服务器时间',r);
+        r=await chsdk.loadProvinceRankInfo('level');
+        console.log('获取全国榜',r);
+    }
+    update(deltaTime: number) {
+    }
+}

+ 11 - 0
llk/assets/ch/ch-sdk/提示说明.txt.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "1.0.1",
+  "importer": "text",
+  "imported": true,
+  "uuid": "0151a2ff-cb6e-4173-94db-c63348e8b154",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 27 - 0
llk/assets/ch/ch.ts

@@ -0,0 +1,27 @@
+import ch_audio from "./audio/audio";
+import pvp from "./pvp/ch_pvp";
+import util from "./ch_util";
+import sign from "./sign/sign";
+export const ch = {
+    /**主sdk(需要初始化)*/
+    sdk: chsdk,
+    /**日志*/
+    log: chsdk.log,
+    /**本地缓存*/
+    storage: chsdk.storage,
+    /**日期*/
+    date: chsdk.date,
+    /**全局事件模块*/
+    get event() { return chsdk.get_new_event<any>() },
+    /**创建一个模块事件*/
+    get_new_event<CT>() { return chsdk.get_new_event<CT>() },
+    //---------------------------------------------
+    /**交互*/
+    pvp: pvp,
+    /**工具*/
+    util: util,
+    /**登录签到模块*/
+    get sign(): sign { return sign.getInstance(); },
+    /**音频播放模块(需要初始化)*/
+    get audio(): ch_audio { return ch_audio.getInstance(); },
+} 

+ 9 - 0
llk/assets/ch/ch.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "168012b2-7345-4efb-b543-bcd5b60a2689",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 348 - 0
llk/assets/ch/ch_util.ts

@@ -0,0 +1,348 @@
+import { size, view, Node, UITransform, screen, ImageAsset, SpriteFrame, Texture2D, sys, assetManager } from "cc";
+/**工具*/
+class ch_util {
+    private static _instance: ch_util;
+    public static getInstance(): ch_util {
+        if (!this._instance) this._instance = new ch_util();
+        return this._instance;
+    }
+
+    /**
+    * 随机数 (包含min,不包含max)
+    * @param min
+    * @param max
+    * @param isInt
+    * @return {*}
+    */
+    public getRandom(min: number = 0, max: number = 1): number {
+        if (min == null) min = 0;
+        if (max == null) max = 1;
+        if (min === max) return min;
+        return min + (Math.random() * (max - min));
+    }
+    /**
+    * 随机整数-不包含最大值
+    * @param min
+    * @param max
+    * @return {*}
+    */
+    public getRandomInt(min: number, max: number): number {
+        min = Math.ceil(min); max = Math.ceil(max);
+        return Math.floor(Math.random() * (max - min)) + min;
+    }
+    /** 生成随机整数 -1 或 1*/
+    public getRandomDir(): -1 | 1 {
+        return Math.floor(Math.random() * 2) === 0 ? -1 : 1;
+    }
+    /**
+    * 在指定数组中随机取出N个不重复的数据
+    * @param resArr
+    * @param ranNum
+    * @returns {Array}
+    */
+    public getRandomDiffValueFromArr<T>(resArr: Array<T>, ranNum: number): Array<T> {
+        let arr = new Array<T>();
+        let result = new Array<T>();
+        if (!resArr || resArr.length <= 0 || ranNum <= 0) {
+            return result;
+        }
+        for (let i = 0; i < resArr.length; i++) {
+            arr.push(resArr[i]);
+        }
+        if (ranNum >= arr.length) return arr;
+        ranNum = Math.min(ranNum, arr.length - 1);
+        for (let i = 0; i < ranNum; i++) {
+            let ran = this.getRandomInt(0, arr.length - 1);
+            result.push(arr.splice(ran, 1)[0]);
+        }
+        return result;
+    }
+    /**
+     * 取小数位
+     * @param decimal 小数
+     * @param places 位数
+     * @return {number}
+     */
+    public numberToDecimal(decimal: number, places: number): number {
+        let round: number = Math.pow(10, places);
+        return Math.round(decimal * round) / round;
+    }
+
+    public parse(text: string, reciver?: (key: any, value: any) => any): any {
+        try {
+            return JSON.parse(text, reciver);
+        } catch (error) {
+            //ch_log.error(error);
+            return null;
+        }
+    }
+
+    public stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number) {
+        try {
+            return JSON.stringify(value, replacer, space);
+        } catch (error) {
+            return null;
+        }
+    }
+    /**
+     * 判断字符是否为双字节字符(如中文字符)
+     * @param string 原字符串
+     */
+    public str_isDoubleWord(string: string): boolean {
+        return /[^\x00-\xff]/.test(string);
+    }
+    /**
+    * 是否为空
+    * @param str 
+    */
+    public str_isEmpty(str: string): boolean {
+        if (str == null || str == undefined || str.length == 0) {
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 转美式计数字符串
+     * @param value 数字
+     * @example
+     * 123456789 = 123,456,789
+     */
+    public numberTotPermil(value: number): string {
+        return value.toLocaleString();
+    }
+    private readonly _k: number = 1000;
+    private readonly _k_sizes_en: string[] = ['', 'K', 'M', 'G', 'T', 'P', 'E'];
+    private readonly _k_sizes_cn: string[] = ['', '千', '百万', '十亿', '万亿', '拍(千万亿)', '艾(十亿亿)'];
+    private readonly _w: number = 10000;
+    private readonly _w_sizes_en: string[] = ['', 'W', 'M', 'B', 'T'];
+    private readonly _w_sizes_cn: string[] = ['', '万', '亿', '万亿'];
+    /**  
+    * 通用单位转换方法  
+    */
+    private convertNumber(value: number, base: number, sizes: string[], fixed: number): string {
+        if (value < base) return value.toString();
+        const i = Math.floor(Math.log(value) / Math.log(base));
+        const r = value / Math.pow(base, i);
+        if (i >= sizes.length) return value.toString();
+        return `${r.toFixed(fixed)}${sizes[i]}`;
+    }
+    /**   
+     * 转单位计数(默认英文)  
+     * @param value 数字  
+     * @param fixed 保留小数位数  
+     * @param isEn 是否英文  
+     * @example  
+     * 12345 = 12.35K  
+     */
+    public numberToThousand(value: number, fixed: number = 2, isEn: boolean = true): string {
+        const sizes = isEn ? this._k_sizes_en : this._k_sizes_cn;
+        return this.convertNumber(value, this._k, sizes, fixed);
+    }
+    /**   
+     * 转单位计数(默认中文)  
+     * @param value 数字  
+     * @param fixed 保留小数位数  
+     * @param isEn 是否英文  
+     * @example  
+     * 12345 = 1.23万  
+     */
+    public numberToTenThousand(value: number, fixed: number = 2, isEn: boolean = false): string {
+        const sizes = isEn ? this._w_sizes_en : this._w_sizes_cn;
+        return this.convertNumber(value, this._w, sizes, fixed);
+    }
+    /**获取一个唯一标识的字符串 */
+    public guid() {
+        let guid: string = Math.random().toString(36).substring(2);
+        return guid;
+    }
+    /**排序一个json Object*/
+    public obj_sort(obj: any): any {
+        let sorted_keys: string[] = Object.keys(obj).sort();
+        let new_obj = {};
+        for (let i = 0; i < sorted_keys.length; i++) {
+            const key = sorted_keys[i];
+            const value = obj[key];
+            const t = this.obj_get_value_type(value);
+            new_obj[key] = t == 'object' ? this.obj_sort(value) : value;
+        }
+        return new_obj;
+    }
+    /**获取一个josn值的类型*/
+    public obj_get_value_type(value: any): string {
+        let type: string;
+        if (value === null) {
+            type = 'null';
+        } else {
+            type = typeof value;
+        }
+        // 如果是对象或数组,还可以进一步判断
+        if (type === 'object') {
+            if (Array.isArray(value)) { type = 'array'; }
+            //else if (value instanceof Date) {  type = 'date';   }  
+        }
+        return type;
+    }
+    /**
+     * 判断指定的值是否为对象
+     * @param value 值
+     */
+    public valueIsObject(value: any): boolean {
+        return Object.prototype.toString.call(value) === '[object Object]';
+    }
+    /**
+    * 深拷贝
+    * @param target 目标
+    */
+    /** 克隆对象 */
+    public obj_clone<T = any>(target_: T, record_set = new Set()): T {
+        let result: any;
+
+        switch (typeof target_) {
+            case "object": {
+                // 数组:遍历拷贝
+                if (Array.isArray(target_)) {
+                    if (record_set.has(target_)) {
+                        return target_;
+                    }
+
+                    record_set.add(target_);
+                    result = [];
+                    for (let k_n = 0; k_n < target_.length; ++k_n) {
+                        // 递归克隆数组中的每一项
+                        result.push(this.obj_clone(target_[k_n], record_set));
+                    }
+                }
+                // null:直接赋值
+                else if (target_ === null) {
+                    result = null;
+                }
+                // RegExp:直接赋值
+                else if ((target_ as any).constructor === RegExp) {
+                    result = target_;
+                }
+                // 普通对象:循环递归赋值对象的所有值
+                else {
+                    if (record_set.has(target_)) {
+                        return target_;
+                    }
+
+                    record_set.add(target_);
+                    result = {};
+                    for (const k_s in target_) {
+                        result[k_s] = this.obj_clone(target_[k_s], record_set);
+                    }
+                }
+
+                break;
+            }
+
+            case "function": {
+                result = target_.bind({});
+                break;
+            }
+
+            default: {
+                result = target_;
+            }
+        }
+
+        return result;
+    }
+    /**
+    * 拷贝对象
+    * @param target 目标
+    */
+    public copy(target: object): object {
+        return this.parse(this.stringify(target));
+    }
+    //请求相关--------------------------------------------
+    public getReqId(): string {
+        return Date.now() + "_" + Math.ceil(1e3 * Math.random());
+    }
+    private _url_data: Record<string, string> | null = null;
+    /**获取链接中传入的某个值*/
+    public getUrlData<T extends (string | number)>(key: string): T | null {
+        if (!this._url_data) {
+            this._url_data = this.parseUrl();
+        }
+        const value = this._url_data[key];
+        if (value === undefined) return null;
+        if (typeof value === 'string') {
+            const numberValue = parseFloat(value);
+            if (!isNaN(numberValue)) return (numberValue as unknown) as T;
+            return value as T;
+        }
+        return null;
+    }
+    private parseUrl(): Record<string, string> {
+        if (!sys.isBrowser || typeof window !== "object" || !window.document) return {};
+        const url = window.document.location.href;
+        const queryString = url.split("?")[1];
+        if (!queryString) return {};
+        return queryString.split("&").reduce<Record<string, string>>((acc, param) => {
+            const [key, value] = param.split("=");
+            if (key) acc[decodeURIComponent(key)] = value ? decodeURIComponent(value) : '';
+            return acc;
+        }, {});
+    }
+    /**获到node的坐标和范围用于平台在对应位置创建按纽*/
+    public getBtnOp(btnNode: Node): { left: number, top: number, width: number, height: number } {
+        let btnNodeUiTransform = btnNode.getComponent(UITransform);
+        const btnSize = size(btnNodeUiTransform.width + 0, btnNodeUiTransform.height + 0);
+        //let frameWidth = screen.windowSize.width / screen.devicePixelRatio
+        let frameHeight = screen.windowSize.height / screen.devicePixelRatio
+        //const winSize = screen.windowSize;
+        //const designSize = view.getDesignResolutionSize();
+        //console.log('designSize', designSize);
+        //console.log('winSize:', winSize);
+        //console.log('frameSize:', frameWidth,frameHeight);
+        //适配不同机型来创建微信授权按钮
+        let rect = btnNodeUiTransform.getBoundingBoxToWorld();
+        let ratio = screen.devicePixelRatio;
+        let scale = view.getScaleX();
+        let factor = scale / ratio;
+        let offsetX = 0;
+        let offsetY = 0;
+        let top = frameHeight - (rect.y + rect.height) * factor - offsetY;
+        let left = rect.x * factor + offsetX;
+        const width = btnSize.width * factor;
+        const height = btnSize.height * factor;
+        return { left: left, top: top, width: width, height: height }
+    }
+    /**远程加载图片*/
+    public async loadImage(url: string): Promise<SpriteFrame> {
+        if (chsdk.get_pf() == chsdk.pf.web) {
+            return await this.loadRemoteSprite(url);
+        } else {
+            const idata = await chsdk.loadImage(url);
+            return this.getSpriteFrame(idata);
+        }
+    }
+    /**cocos加载远程图片*/
+    public loadRemoteSprite(remoteUrl: string, ext: '.png' | '.jpg' = '.png'): Promise<SpriteFrame> {
+        return new Promise((resolve) => {
+            assetManager.loadRemote<ImageAsset>(remoteUrl, { ext: ext }, function (err, imageAsset) {
+                const spriteFrame = new SpriteFrame();
+                const texture = new Texture2D();
+                texture.image = imageAsset;
+                spriteFrame.texture = texture;
+                resolve(spriteFrame);
+            });
+        });
+    }
+    /**远程加载的图片数据转成spriteFrame*/
+    private getSpriteFrame(img: any): SpriteFrame {
+        if (!img) return null;
+        try {
+            const spriteFrame = new SpriteFrame();
+            const texture = new Texture2D();
+            texture.image = img instanceof ImageAsset ? img : new ImageAsset(img);
+            spriteFrame.texture = texture;
+            return spriteFrame;
+        } catch (error) {
+            //ch_log.warn(error);
+            return null;
+        }
+    }
+}
+export default ch_util.getInstance();

+ 9 - 0
llk/assets/ch/ch_util.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1badfb4c-c407-40ee-9e7f-6f1aef485f28",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 379 - 0
llk/assets/ch/chsdk_inside.d.ts

@@ -0,0 +1,379 @@
+declare namespace chsdk {
+    /**日志等级*/
+    enum loglevel {
+        OFF = 0,
+        ERROR = 1,
+        WARN = 2,
+        DEBUG = 3,
+        INFO = 4,
+        ALL = 5
+    }
+    /**上报类型*/
+    export enum reportType {
+        /**默认不上报*/
+        off = 0,
+        /**使用ch服务器*/
+        ch = 1,
+        /**上报到应用平台(需在各平台后台设置上报事件,微信,抖音)*/
+        platform = 2,
+        /**上面两个都选*/
+        ch__platform = 3
+    }
+    /**平台*/
+    export enum pf {
+        /**测试*/
+        web = "web",
+        /**微信*/
+        wx = "wx",
+        /**抖音*/
+        tt = "tt"
+    }
+    export function init_inside(gid: string, loglevel: number, serverIP: number | string, isLocal?: boolean, report?: reportType): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+     * 获取基本功能完成接口地址
+     * @param action 接口字符串如 '/user/shareList'
+     * @returns
+     */
+    export function getUrl(action: string): string;
+    /**
+    * 获取上报功能完成接口地址
+    * @param action 接口字符串如 '/user/shareList'
+    * @returns
+    */
+    export function getReportUrl(action: string): string;
+    export function check_req_time(req_type: string): {
+        code: number;
+        err: string;
+    } | null;
+    export function makePostTokenRequest(url: string, body?: any): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    export function base64_encode(input: string): string;
+    export function base64_decode(input: string): string;
+    /**
+     * md5加密
+     */
+    export function md5HashStr(str: string): string;
+    /**
+     * 一次http请求
+     * @param url 请求地址
+     * @param method 请求方式
+     * @param data
+     * @param timeout
+     * @param responseType
+     * @param headers
+     * @returns
+     */
+    export function do_request(url: string, method: 'POST' | 'GET' | 'PUT', data?: any, timeout?: number, responseType?: XMLHttpRequestResponseType, headers?: {
+        [key in string]: string;
+    }): Promise<any>;
+    /**验证权限功能
+    * key 某个功能权限
+    * 返回 是否验证成功
+    */
+    export function verify_option(key: string): boolean;
+    /**获取一个唯一标识的字符串 */
+    export function guid(): string;
+    /**判断是否有名字,没有返回玩家*/
+    export function getDefNickName(info: any): string;
+    /**
+    * 主动记录分数到缓存
+    * @param rankType 排行榜类型
+    * @param update 更新类型
+    * @param type  0 个人排行 1地区排行
+    * @param score 分数
+    */
+    export function recordCacheScore(rankField: string, update: number, type: 0 | 1, score: number): void;
+    /**
+     * 获取缓存中的分数
+     * @param rankField
+     * @param update
+     * @param type
+     * @returns
+     */
+    export function getCacheScore(rankField: string, update: number, type: 0 | 1): number;
+    /**清空所有缓存分数*/
+    export function cleanCacheScore(): void;
+    /**清空所有缓存排行榜数据*/
+    export function cleanCacheRank(): void;
+    /**当前平台是否有分享功能*/
+    export function canShareAppMessage(): boolean;
+    /**
+    * 主动拉起分享转发
+    */
+    export function shareAppMessage(title?: string, imageUrlId?: string, imageUrl?: string, message?: string): void;
+    /**
+    * 主动拉起分享转发并等待是错分享成功
+    */
+    export function shareAppMessageAsync(title?: string, imageUrlId?: string, imageUrl?: string, message?: string): Promise<boolean>;
+    /**设置被动分享参数,具体参看平台的参数设置*/
+    export function setOnShareAppMessage(res: {}): void;
+    export function getQuery(): any;
+    /**
+    * @description 上传从分享进入(无需主动调用,除非需要在message里加入游戏特别数据)
+    * @param openid 服务器拿到的玩家oid 如果为空从平台拿
+    * @param message 分享的自定义数据
+    */
+    export function sendShare(openid?: string | null, message?: string | null): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取从自己分享进入游戏的玩家列表
+    */
+    export function getShareList(): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            gid: string;
+            head: string;
+            hid: number;
+            ip: string;
+            loginTime: number;
+            nickName: string;
+            openId: string;
+            option: string;
+            pf: string;
+            registerTime: number;
+            userId: number;
+            msg?: string;
+        }[];
+    }>;
+    /**播放奖励广告
+        * @param scene 奖励目的场景
+       playRewardAd("复活")
+       .then(success => {
+         if (success) {
+             console.log("广告播放成功,用户获得奖励");
+             // 在这里执行成功后的操作
+         } else {
+             console.log("广告播放失败,用户未获得奖励");
+             // 处理广告播放失败的逻辑
+         }
+       })
+       .catch(error => {
+           console.error("发生未处理的错误: ", error);
+        });
+        */
+    export function playRewardAd(scene: string): Promise<boolean>;
+    /**播放插屏广告*/
+    export function playInsterAd(scene: string): Promise<boolean>;
+    /**设置广告配置*/
+    export function setConf(pf: pf, conf: {
+        adUnitId: string;
+        multiton: boolean;
+        inster_unitId?: string;
+        tmplIds?: string[];
+    }): void;
+    /**当前平台是否有侧边栏功能*/
+    export function checkHasSidebar(): boolean;
+    /**抖音是否从侧边栏进入游戏,微信是否从我的小程序进入游戏*/
+    export function checkFromSidebar(): boolean;
+    /**抖音进入侧边栏*/
+    export function goToSidebar(): boolean;
+    /**开始录屏*/
+    export function recorderStart(duration?: number): void;
+    /**结束录屏*/
+    export function recorderStop(): void;
+    /**分享录屏*/
+    export function shareRecord(title?: string, desc?: string, path?: string, topics?: string[]): void;
+    /**创建反馈按钮
+    * @param op 按纽坐标大小
+    * 例:feed_node.active = ch.sdk.createFeedbackButton(ch.util.getBtnOp(feed_node));
+    * 如果对应平台有此功能(目前只有wx)返回true,没有返回false
+    * 成功记得 destoryFeedbackButton();*/
+    export function createFeedbackButton(op: {
+        left: number;
+        top: number;
+        width: number;
+        height: number;
+    }): boolean;
+    /**销毁反馈按钮*/
+    export function destoryFeedbackButton(): void;
+    /**复制到剪切板*/
+    export function setClipboardData(content: string): void;
+    /**短震动(15ms)*/
+    export function vibrateShort(): void;
+    /**长震动(400ms)*/
+    export function vibrateLong(): void;
+    /**界面提示信息*/
+    export function showToast(title: string, duration?: number, icon?: 'success' | 'error' | 'fail' | 'loading' | 'none'): boolean;
+    /**获取菜单坐标范围*/
+    export function getMenuButtonBoundingClientRect(): any;
+    export function showLoading(title?: string): boolean;
+    export function hideLoading(): void;
+    export function showModal(title?: string, content?: string, confirmText?: string, showCancel?: boolean, cancelText?: string): Promise<{
+        confirm: boolean;
+        cancel: boolean;
+    }>;
+    export function openSetting(scope: string | 'scope.userInfo' | 'scope.userLocation'): Promise<boolean>;
+    /**加载图片
+    * cocos里不要直接调用,使用 ch.util.loadImage(url);
+    */
+    export function loadImage(imgURL: string): Promise<any>;
+    /**当前平台是否有关注功能*/
+    export function canAwemeUserProfile(): boolean;
+    /**检查是否已经关注*/
+    export function checkFollowAwemeState(): Promise<boolean>;
+    /**打开关注返回关注结果*/
+    export function openAwemeUserProfile(): Promise<boolean>;
+    /**当前平台是否有订阅功能*/
+    export function canSubscribeMessage(): boolean;
+    /**订阅消息 参数为空的话使用配置里的模板id*/
+    export function requestSubscribeMessage(tmplIds?: string[]): Promise<boolean>;
+    class ch_log {
+        private static _instance;
+        static getInstance(): ch_log;
+        private _log_level;
+        set_log_level(loglevel: loglevel): void;
+        private _no;
+        private _log;
+        private _info;
+        private _debug;
+        private _trace;
+        private _warn;
+        private _error;
+        private _log_start;
+        private _log_end;
+        get log(): (message?: any, ...optionalParams: any[]) => void;
+        get info(): (message?: any, ...optionalParams: any[]) => void;
+        get debug(): (message?: any, ...optionalParams: any[]) => void;
+        get trace(): (message?: any, ...optionalParams: any[]) => void;
+        get warn(): (message?: any, ...optionalParams: any[]) => void;
+        get error(): (message?: any, ...optionalParams: any[]) => void;
+        /**记录开始计时*/
+        get log_start(): (label?: string) => void;
+        /** 打印范围内时间消耗*/
+        get log_end(): (label?: string) => void;
+    }
+    /**日志打印*/
+    export const log: ch_log;
+    /**事件*/
+    export interface EventsMap {
+        [event: string]: (...args: any[]) => void;
+    }
+    export type EventNames<Map extends EventsMap> = keyof Map & string;
+    export type OmitIndex<T> = {
+        [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K];
+    };
+    export type EventParams<Map extends EventsMap, Ev extends EventNames<Map>> = Parameters<Map[Ev]>;
+    class EventModel<CT extends OmitIndex<EventsMap>> {
+        private _handlersMap;
+        /** 事件键 */
+        key: {
+            [k in EventNames<CT>]: k;
+        };
+        /**
+        * 监听事件
+        * @param type_ 事件类型
+        * @param callback_ 触发回调
+        * @param target_ 事件目标对象
+        * @param once_b_ 是否触发单次
+        * @returns 触发回调
+        */
+        on<T extends EventNames<CT>, T2 extends (...event_: EventParams<CT, T>) => void>(type_: T, callback_: T2, target_?: any, once_?: boolean): typeof callback_ | null;
+        once<T extends EventNames<CT>, T2 extends (...event_: EventParams<CT, T>) => void>(type_: T, callback_: T2, target_?: any): void;
+        /**
+         * 取消监听事件
+         * @param type_ 事件类型
+         * @param callback_ 触发回调
+         * @param target_ 事件目标对象
+         * @returns 触发回调
+         */
+        off<T extends EventNames<CT>, T2 extends (...event_: EventParams<CT, T>) => void>(type_: T, callback_?: T2, target_?: any): void;
+        /**
+         * 清除某个事件队列
+         * @param type_ 事件名,为空的话清除所有
+         */
+        clearAll<T extends EventNames<CT>>(type_?: T): void;
+        /**
+        * 派发事件
+        * @param type_ 事件类型
+        * @param args_ 事件参数
+        */
+        emit<T extends EventNames<CT>, T2 extends EventParams<CT, T>>(type_: T, ...args_: T2): void;
+        private _emit;
+    }
+    /**sdk事件*/
+    export interface event_sdk {
+        show(): void;
+        hide(): void;
+        onShare(success: boolean): void;
+    }
+    export const sdk_event: EventModel<event_sdk>;
+    export function get_new_event<CT>(): EventModel<CT>;
+    /**日期时间*/
+    class ch_date {
+        private static _instance;
+        static getInstance(): ch_date;
+        private _day_s;
+        private _diff;
+        updateServerTime(s: number): void;
+        now(): number;
+        getTime(): number;
+        getDayStartTime(s: number): number;
+        getDayEndTime(e: number): number;
+        getWeekEndTime(e: number): number;
+        getMonthEndTime(e: number): number;
+        /**两个时间相隔天数*/
+        getDiffDayNum(e: number, t: number): number;
+        /**判定两个时间是否是同一天*/
+        isSameDate(timestamp1: number, timestamp2: number): boolean;
+        /**
+         * 单位毫秒格式化
+         * - $H: 替换为小时,补全空位(02:00:00)
+         * - $h: 替换为小时,不补全(2:00:00)
+         * - $M: 替换为分钟,补全空位(00:02:00)
+         * - $m: 替换为分钟,不补全(00:2:00)
+         * - $S: 替换为秒,补全空位(00:00:02)
+         * - $s: 替换为秒,不补全(0:00:2)
+         */
+        ms_format(ms_n_: number, format_s_?: string): string;
+    }
+    export const date: ch_date;
+    /**本地存储*/
+    class ch_storage {
+        private static _instance;
+        static getInstance(): ch_storage;
+        /**
+         * 缓存变量存储
+         * @param  key
+         * @param  value
+         * @param  user_id 区别用户
+         */
+        set(key: string, value: any, user_id?: string | null): any;
+        /**
+         * 获取缓存变量区别用户
+         * @param {*} key
+         * @returns
+         */
+        private get;
+        /** 获取指定关键字的string*/
+        getString(key: string, user_id?: string | null): string | null;
+        /** 获取指定关键字的数值 */
+        getNumber(key: string, user_id?: string | null, defaultValue?: number): number;
+        /** 获取指定关键字的布尔值*/
+        getBoolean(key: string, user_id?: string | null): boolean;
+        /** 获取指定关键字的JSON对象*/
+        getJson(key: string, user_id?: string | null): any;
+        /** 获取指定关键字的JSON对象*/
+        getObject(key: string, user_id?: string | null): any;
+        /**
+         * 删除缓存变量
+         * @param {*} key
+        */
+        remove(key: string, user_id?: string | null): any;
+        /** 清空整个本地存储 */
+        clear(): void;
+    }
+    /**本地存储*/
+    export const storage: ch_storage;
+    export { };
+}

+ 9 - 0
llk/assets/ch/chsdk_inside.d.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "21d5e548-813e-465e-8ac1-1429b03fc0b5",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/ch/pvp.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "f99f28eb-fd03-4ae2-be42-4872a3d23ba1",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 241 - 0
llk/assets/ch/pvp/ch_pvp.ts

@@ -0,0 +1,241 @@
+import ch_util from "../ch_util";
+/** 
+ * pvp扩展玩法 基于 chsdk
+*/
+export type pvp_player = { head: string, nickName: string, uid: number, hid: number, province: string, pvp: number, rank: number };
+export type pvp_own_data<T> = { extends: T, type: 0 | 1 | 2, time: number, own: pvp_player };
+export type pvp_data<T> = { extends: T, other: pvp_player, own: pvp_player, type?: 0 | 1 | 2, status?: 0 | 1 | 2, time?: number, reward?: number, pvpId?: string };
+export type pvp_result = { other: { curRank: number, rank: number, curScore: number, score: number }, own: { curRank: number, rank: number, curScore: number, score: number } };
+class ch_pvp {
+    private readonly _set_url: string = '/user/setPvpSceneData';
+    private readonly _match_url: string = '/user/startPvpMatch';
+    private readonly _finish_url: string = '/user/pvpFinishAction';
+    private readonly _record_url: string = '/user/pvpChallengeRecord';
+    private static _instance: ch_pvp;
+    public static getInstance(): ch_pvp {
+        if (!this._instance) this._instance = new ch_pvp();
+        return this._instance;
+    }
+    /**
+     * 设置自己的 PVP 擂台数据
+     * 该方法用于更新当前用户在 PVP 擂台上的数据。数据将根据传入的 extend 参数进行更新,  
+     * 并返回一个包含操作状态和可能错误信息的 Promise。
+     * @param extend - 一个pvp擂台数据对象,包含要更新的具体数据。
+     * @returns 一个 Promise,解析为一个包含以下属性的对象:  
+     *   - code: 操作的结果状态。0 表示成功,其他值表示不同的错误类型。  
+     *   - err:  可选字符串,指示错误信息(如果有)。     
+     */
+    public async setSceneData<T extends { [key: string]: any }>(extend: T): Promise<{ code: number, err?: string }> {
+        const check = chsdk.check_req_time('setSceneData');
+        if (check) return check;
+        const body = { extends: ch_util.stringify(extend) };
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._set_url), body);
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+        } else {
+            chsdk.log.log(`PVP擂台数据上报成功`);
+            if (!this._own) {
+                await this.getOwn<T>();
+            } else {
+                this._own.extends = extend;
+            }
+        }
+        return ret;
+    }
+    /**
+    * 开始匹配
+    * @param uid (可选)具体好友的uid,默为空
+    * @returns null  匹配失败
+    * 
+    *          pvp_data<T>
+                   extends PVP 擂台数据
+    *             other   台主信息
+    *             own     自己的信息
+    *                     head:头像, nickName:名字, uid:id, hid:省id, province:省名, pvp:pvp排位分值, rank:pvp排行榜名次  
+    */
+    public async startMatch<T extends { [key: string]: any }>(uid?: number | null): Promise<pvp_data<T> | null> {
+        const check = chsdk.check_req_time('startMatch');
+        if (check) return null;
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._match_url), { uid: uid ? uid.toString() : '' });
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+            return null;
+        } else {
+            const data = ret.data.data;
+            data.other.uid = Number.parseInt(data.other.uid);
+            data.other.hid = Number.parseInt(data.other.hid);
+            data.other.province = chsdk.provinceCode2Name(data.other.hid);
+            data.own.uid = Number.parseInt(data.own.uid);
+            data.own.hid = Number.parseInt(data.own.hid);
+            data.own.province = chsdk.provinceCode2Name(data.own.hid);
+            return { extends: ch_util.parse(data.extends) as T, other: data.other, own: data.own }
+        }
+    }
+    /**从一条记录开始匹配,用于复仇*/
+    public async startMatchRecord<T extends { [key: string]: any }>(pd: pvp_data<T>): Promise<pvp_data<T> | null> {
+        const check = chsdk.check_req_time('startMatch');
+        if (check) return null;
+        let pp = await this.startMatch<T>(pd.other.uid);
+        if (!pp) return null;
+        pp.extends = pd.extends;
+        pp.pvpId = pd.pvpId;
+        return pp;
+    }
+    /**
+     * 完成pvp上报
+     * @param otherUid 对方uid
+     * @param status 0:挑战失败 1:挑战胜利
+     * @param ext PVP 擂台数据
+     * @param serverData 服务器处理排行数据,订阅消息
+     * ..ownValue 增减自己排位分值
+     * ..otherValue 增减对方排位分值
+     * ..msg 自定义的订阅消息
+     * @param pvpId 复仇id
+     * @returns 对战结果
+     *          other 对方的排名和分数变化
+     *          own  自己的排名和分数变化
+     *               curRank 当前排名(变动后)
+                     curScore 当前分数(变动后)
+                     rank  名数变动
+                     score 分数变动
+     */
+    public async finish<T extends { [key: string]: any }>(otherUid: number, status: 0 | 1, ext: T,
+        serverData: { ownValue?: number, otherValue?: number, msg?: string }
+        , pvpId?: string
+    ): Promise<pvp_result> {
+        const check = chsdk.check_req_time('finish');
+        if (check) return null;
+        const body = { otherUid: otherUid.toString(), status: status, extends: ch_util.stringify(ext), serverData: ch_util.stringify(serverData), pvpId: pvpId };
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._finish_url), body);
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+            return null;
+        } else {
+            const data = ret.data.data;
+            chsdk.log.log(`pvp结果数据上报成功,结算:`, data);
+            return data;
+        }
+    }
+    private _own: any;
+    /**获取自己的pvp擂台数据*/
+    public async getOwn<T extends { [key: string]: any }>(): Promise<pvp_own_data<T> | null> {
+        if (!this._own) await this.getRecords<T>(3, false);
+        return this._own;
+    }
+    //
+    private _cache_records: any | null = null;
+    private _cache_time: number;
+    private get_cache_records(): any | null {
+        if (chsdk.date.now() - this._cache_time > 6e4) return null;
+        return this._cache_records;
+    }
+    /**
+    * 获取pvp对战记录
+    * @param type  0:全部纪录 1:被挑战的记录信息(默认) 2:挑战别人的记录 3:不需要纪录
+    * @param cache 默认为true,优先缓存拿数据,缓存存在一分钟
+    * @returns own:自己的PVP  擂台数据
+    *          list:交互纪录
+    *            extends PVP  擂台数据
+    *            reward	奖励惩罚排位分值,由于serverData中得来
+    *            type	  0:自己的数据 1:被挑战的记录信息 2:挑战别人的记录
+    *            status   0:挑战失败 1:挑战胜利 2:复仇成功
+    *            time     时间
+    *            pvpId   pvp复仇id
+    *            other   对方信息
+    *            own     自己信息
+    */
+    public async getRecords<T extends { [key: string]: any }>(typeId: 0 | 1 | 2 | 3 = 1, cache: boolean = true): Promise<{
+        own: pvp_own_data<T>,
+        list: pvp_data<T>[],
+    }
+    > {
+        if (cache) {
+            const data = this.get_cache_records();
+            if (data) return data;
+        }
+        const check = chsdk.check_req_time('getRecords');
+        if (check) return null;
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._record_url), { typeId: typeId });
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+            return null;
+        } else {
+            let data = ret.data.data;
+            if (data.own) {
+                const d = data.own;
+                d.extends = ch_util.parse(d.extends) as T;
+                d.own.uid = Number.parseInt(d.own.uid);
+                d.own.hid = Number.parseInt(d.own.hid);
+                d.own.province = chsdk.provinceCode2Name(d.own.hid);
+            } else {
+                data.own = null;
+            }
+            data.list ||= [];
+            for (let i = 0; i < data.list.length; i++) {
+                const d = data.list[i];
+                d.extends = ch_util.parse(d.extends) as T;
+                d.own.uid = Number.parseInt(d.own.uid);
+                d.own.hid = Number.parseInt(d.own.hid);
+                d.own.province = chsdk.provinceCode2Name(d.own.hid);
+
+                d.other.uid = Number.parseInt(d.other.uid);
+                d.other.hid = Number.parseInt(d.other.hid);
+                d.other.province = chsdk.provinceCode2Name(d.other.hid);
+            }
+            this._own = data.own;
+            this._cache_records = data;
+            this._cache_time = chsdk.date.now();
+            return data;
+        }
+    }
+    /**
+     * 获取pvp排位分值榜单
+     * @returns
+     */
+    public async getRank(): Promise<{
+        list: {
+            head: string;
+            nickName: string;
+            rank: number;
+            score: number;
+            userId: number;
+            [key: string]: any;
+        }[], ower: {
+            head: string;
+            nickName: string;
+            rank: number;
+            score: number;
+            userId: number;
+            [key: string]: any;
+        }
+    }> {
+        const d = await chsdk.loadRankData('pvp', 1, 100, true);
+        return { list: d.data.list, ower: d.data.own };
+    }
+    /**
+     * 分享pvp数据,邀请对战
+     * @param player 玩家自己信息
+     * @param extend (可选)pvp关卡等数据
+     * @param title 分享显示标题
+     * @param imageUrl 分享显示图片
+     */
+    public async sharePvp<T extends { [key: string]: any }>(player: pvp_player, extend?: T, title?: string, imageUrl?: string) {
+        chsdk.shareAppMessage(title, '', imageUrl, ch_util.stringify({ type: 'pvp', player: player, extends: extend }));
+    }
+    /**
+    *获取从好友分享的pvp数据进入游戏的数据
+    */
+    public getSharePvpExtends<T extends { [key: string]: any }>(): { player: pvp_player, extend?: T } {
+        const query = chsdk.getQuery();
+        if (!query) return null;
+        const message = query.message;
+        if (!message) return null;
+        const data = ch_util.parse(message);
+        if (!data) return null;
+        if (data.type != 'pvp') return null;
+        query.message = null;
+        return { player: data.player, extend: data.extends as T };
+    }
+}
+//
+export default ch_pvp.getInstance();

+ 1 - 0
llk/assets/ch/pvp/ch_pvp.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"8b65873a-524d-49ab-81e4-1f1d5cb47d8f","files":[],"subMetas":{},"userData":{}}

+ 147 - 0
llk/assets/ch/sign/sign.ts

@@ -0,0 +1,147 @@
+
+export default class ch_sign{
+    private static _instance:ch_sign;
+    public static getInstance():ch_sign{
+        if(!this._instance) this._instance = new ch_sign();
+        return this._instance;
+    }
+    private _sign_data:Set<number>=new Set();
+    private _max:number = 7;
+    private _creat_date:number=0;
+    private _sign_count:number=0;
+    private _mode:number=1;
+    /**设定的签到天数*/
+    public get max_day():number{return this._max;}
+    private getCreatDayCount():number{
+        if(this._creat_date==0){
+            chsdk.log.warn("签到模块没有初始化");
+            return 0;
+        }
+        return chsdk.date.getDiffDayNum(this._creat_date,chsdk.date.now())+1;
+    }
+    /**
+     * 签到初始化
+     * @param mode =1可以补签 =2不可补签
+     * @param data 签到存档数据,可以为空为第一天
+     * @param max_day 签到最大天数
+     */
+    public init(mode:1|2,data:{sign_list:number[],creat_date:number}|{sign_count:number,last_date:number}|null,max_day:number=7):void{
+        this._mode=mode;
+        if(this._mode==1){
+            if(data){
+                data =data as {sign_list:number[],creat_date:number};
+                const sign_list=data.sign_list ?? [];
+                const creat_date=data.creat_date ?? chsdk.date.now();
+                for(let i=0;i<sign_list.length;i++)this._sign_data.add(sign_list[i]);
+                this._sign_count=this._sign_data.size;
+                this._creat_date=creat_date;
+            }else{
+                this._sign_count=0;
+                this._sign_data.clear();
+                this._creat_date=chsdk.date.now();
+            }
+        }else{
+            if(data){
+                data = data as {sign_count:number,last_date:number};
+                this._sign_count=data.sign_count ?? 0;
+                this._creat_date=data.last_date ?? 0;
+            }else{
+                this._sign_count=0;
+                this._creat_date=0; 
+            }
+        }
+        this._max=max_day;
+    }
+    /**已签到的数据,用于存档*/
+    public getSignData():{sign_list:number[],creat_date:number}|{sign_count:number,last_date:number}{
+        if(this._mode==1){
+           return {sign_list:Array.from(this._sign_data.keys()),creat_date:this._creat_date};  
+        }else if(this._mode==2){
+           return {sign_count:this._sign_count,last_date:this._creat_date};  
+        }
+    }
+    /** 返回某一天的状态
+     *  0 等待签到 
+     *  1 已经签到 
+     *  2 失效等待补签
+     * */
+    public checkSigineState(day:number):number{
+        if(this._mode==1){
+            if(day<1 || day>this._max)return 0;
+            if(this._sign_data.has(day))return 1;
+            const count = this.getCreatDayCount();
+            if(count <=0) return 0;
+            if(day <count) return 2;
+        }else if(this._mode==2){
+            if(day<=this._sign_count) return 1;
+        }
+        return 0;
+    }
+    /**今天是否可以签到*/
+    public checkSigin():boolean{
+        if(this._mode==1){
+           const count = this.getCreatDayCount();
+           if(count<=0 || count>this._max)return false;
+           return !this._sign_data.has(count);
+        }else if(this._mode==2){
+           const count = this._sign_count;
+           if(count>=this._max)return false;
+           return !chsdk.date.isSameDate(this._creat_date,chsdk.date.now());
+        }
+        return false;
+    }
+    /**签到是否全部完成*/
+    public checkSiginComplete():boolean{
+        const count = this._sign_count;
+        return count>=this._max;
+    }
+    /**
+     * 是否能补签
+     * 不能返回0,
+     * 可以补签返回可以补签的那一天*/
+    public checkReSigin():number{
+        if(this._mode!=1) return 0;
+        let count = this.getCreatDayCount()-1;
+        if(count<=0)return 0;
+        if(count>this._max)count=this._max;
+        for(let i=1;i<=count;i++){
+            if(!this._sign_data.has(i)) return i;
+        }
+        return 0;
+    }
+    /**签到
+     * 失败返回 0
+     * 成功返回签到当天
+    */
+    public  signIn(): number {  
+        if (!this.checkSigin()) {  
+            return 0;
+        }
+        if(this._mode==1){
+           const day = this.getCreatDayCount();  
+           this._sign_data.add(day); 
+           this._sign_count=this._sign_data.size;
+           return day;
+        }else  if(this._mode==2){
+            this._sign_count++;
+            this._creat_date=chsdk.date.now();
+            return this._sign_count;
+        }
+        return 0;
+    }
+    /**补签
+    * 失败返回 0
+    * 成功返回补签那天
+    */
+    public reSignIn():number{
+        if(this._mode!=1)return 0;
+        const index = this.checkReSigin();
+        if(index<=0 || index>this._max){
+            return 0;
+        }
+        this._sign_data.add(index);
+        this._sign_count=this._sign_data.size;
+        return index;
+    }
+    //
+}

+ 9 - 0
llk/assets/ch/sign/sign.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6d031cab-c8a4-4b61-a88e-1da5ca87802f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/ch/start.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "5cd73f25-b578-4dce-822c-905430aa8cc0",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 161 - 0
llk/assets/ch/start/ch_start_pack.ts

@@ -0,0 +1,161 @@
+import { _decorator, Component, Enum, Prefab } from 'cc';
+import { ch } from '../ch';
+import { loadType } from '../audio/audio';
+const { ccclass, property } = _decorator;
+/**包id*/
+export enum pack {
+    id0 = 0,
+    id1 = 1,
+    id2 = 2,
+    id3 = 3,
+    id4 = 4,
+    id5 = 5,
+    id6 = 6,
+    id7 = 7,
+    id8 = 8,
+    id9 = 9,
+}
+@ccclass('ch_start_pack')
+export class ch_start_pack extends Component {
+    @property({ visible: false })
+    private _gname: string[] = [];
+    @property({ visible: false })
+    private _gid: string[] = [];
+    @property({ visible: false })
+    private _is_local: boolean[] = [];
+    @property({ visible: false })
+    private _server: chsdk.serverType[] = [chsdk.serverType.dev];
+    @property({ visible: false })
+    private _report: chsdk.reportType[] = [chsdk.reportType.off];
+    @property({ visible: false })
+    private _log: chsdk.loglevel[] = [];
+    @property({ visible: false })
+    private _ttad: string[] = [];
+    @property({ visible: false })
+    private _ttiad: string[] = [];
+    @property({ visible: false })
+    private _tttmpid: string[] = [];
+    @property({ visible: false })
+    private _wxad: string[] = [];
+    @property({ visible: false })
+    private _wxiad: string[] = [];
+    @property({ visible: false })
+    private _wxtmpid: string[] = [];
+    @property({ visible: false })
+    private _serverIP: string[] = [];
+    @property({ visible: false })
+    private _pid: pack = pack.id0;
+    @property({ type: Enum(pack), displayName: '包id', group: { name: '选包', id: 'pack', displayOrder: 0 } })
+    get pid() { return this._pid; }
+    set pid(val) {
+        this._pid = val;
+        for (let i = 0; i <= this._pid; i++) {
+            this._gname[i] ??= '';
+            this._is_local[i] ??= this._is_local[i - 1] ?? true;
+            this._gid[i] ??= this._gid[i - 1] ?? '0';
+            this._server[i] ??= this._server[i - 1] ?? chsdk.serverType.online;
+            this._report[i] ??= this._report[i - 1] ?? chsdk.reportType.off;
+            this._log[i] ??= this._log[i - 1] ?? chsdk.loglevel.DEBUG;
+            this._ttad[i] ??= '';
+            this._ttiad[i] ??= '';
+            this._tttmpid[i] ??= '';
+            this._wxad[i] ??= '';
+            this._wxiad[i] ??= '';
+            this._wxtmpid[i] ??= '';
+            this._serverIP[i] ??= 'http://192.168.1.120:8787/v1';
+        }
+    }
+    @property({ displayName: '游戏名', group: { name: '选包', id: 'pack', displayOrder: 0 } })
+    get gname() { return this._gname[this._pid] ?? ''; }
+    set gname(val) { this._gname[this._pid] = val; }
+    @property({ displayName: '游戏id', group: { name: '选包', id: 'pack', displayOrder: 0 } })
+    get gid() { return this._gid[this._pid]; }
+    set gid(val) { this._gid[this._pid] = val; }
+    //
+    @property({ displayName: '是否单机', group: { name: 'ch.sdk模块', id: 'sdk', displayOrder: 0 } })
+    get is_local() { return this._is_local[this._pid] }
+    set is_local(val) { this._is_local[this._pid] = val; }
+    //
+    @property({ type: Enum(chsdk.serverType), displayName: '服务器地址', visible: function () { return !this.is_local; }, group: { name: 'ch.sdk模块', id: 'sdk', displayOrder: 0 }, tooltip: "local 局域网 \n dev 测试服 \n online 正式服" })
+    get server() { return this._server[this._pid]; }
+    set server(val) { this._server[this._pid] = val; }
+    @property({ displayName: '本地服测试ip', visible: function () { return this.server == chsdk.serverType.test }, group: { name: 'ch.sdk模块', id: 'sdk', displayOrder: 0 } })
+    get serverIP() { return this._serverIP[this._pid]; }
+    set serverIP(val) { this._serverIP[this._pid] = val; }
+    @property({ type: Enum(chsdk.reportType), displayName: '上报类型', group: { name: '基本参数', id: 'sdk', displayOrder: 0 }, tooltip: "off 不上报 \n ch 使用ch服务器(需要在ch后台配置事件) \n platform 使用微信/抖音平台(需要在平台后台设置事件)\nch__platflorm所有平台自定义事件都上报" })
+    get report() { return this._report[this._pid]; }
+    set report(val) { this._report[this._pid] = val; }
+    @property({ type: Enum(chsdk.loglevel), displayName: '日志等级', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get log() { return this._log[this._pid]; }
+    set log(val) { this._log[this._pid] = val; }
+    //
+    @property({ displayName: '抖音奖励广告id', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get ttad() { return this._ttad[this._pid]; }
+    set ttad(val) { this._ttad[this._pid] = val; }
+    @property({ displayName: '抖音插屏广告id', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get ttiad() { return this._ttiad[this._pid]; }
+    set ttiad(val) { this._ttiad[this._pid] = val; }
+    @property({ displayName: '抖音订阅id(,隔开)', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get tttmpid() { return this._tttmpid[this._pid]; }
+    set tttmpid(val) { this._tttmpid[this._pid] = val; }
+
+    @property({ displayName: '微信奖励广告id', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get wxad() { return this._wxad[this._pid]; }
+    set wxad(val) { this._wxad[this._pid] = val; }
+    @property({ displayName: '微信插屏广告id', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get wxiad() { return this._wxiad[this._pid]; }
+    set wxiad(val) { this._wxiad[this._pid] = val; }
+    @property({ displayName: '微信订阅id(,隔开)', group: { name: '基本参数', id: 'sdk', displayOrder: 0 } })
+    get wxtmpid() { return this._wxtmpid[this._pid]; }
+    set wxtmpid(val) { this._wxtmpid[this._pid] = val; }
+    //
+    @property({ visible: false })
+    private _use_ch_audio: boolean = false;
+    @property({ type: Enum(loadType), visible: false })
+    private _ch_audio_type: loadType = loadType.bundle;
+    @property({ displayName: '是否使用ch.audio模块', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 1 } })
+    get use_ch_audio() { return this._use_ch_audio; }
+    set use_ch_audio(val) { this._use_ch_audio = val; }
+    @property({ type: Enum(loadType), displayName: '音频资源加载模式', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 1 }, visible: function () { return this._use_ch_audio; } })
+    get sount_load_type() { return this._ch_audio_type; }
+    set sount_load_type(val) { this._ch_audio_type = val; }
+    @property({ displayName: '音频资源默认bundle名', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 1 }, visible: function () { return this._use_ch_audio && this._ch_audio_type == loadType.bundle } })
+    private sound_bundle: string = 'res';
+    @property({ displayName: '音频资源远程地址', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 1 }, visible: function () { return this._use_ch_audio && this._ch_audio_type == loadType.remote } })
+    private sound_url: string = '';
+    protected onLoad(): void {
+        console.log('包Id:' + this.pid, '游戏名:' + this.gname, '游戏Id:' + this.gid, '单机:' + this.is_local, '服务器:' + chsdk.serverType[this.server]);
+    }
+    /**初始化*/
+    async init(): Promise<boolean> {
+        chsdk.setConf(chsdk.pf.tt, { adUnitId: this.ttad, multiton: false, inster_unitId: this.ttiad, tmplIds: this.tttmpid ? this.tttmpid.split(',') : null });
+        chsdk.setConf(chsdk.pf.wx, { adUnitId: this.wxad, multiton: false, inster_unitId: this.wxiad, tmplIds: this.wxtmpid ? this.wxtmpid.split(',') : null });
+        let ret = await chsdk.init_inside(this.gid, this.log, this.server == chsdk.serverType.test ? this.serverIP : this.server, this.is_local, this.report);
+        if (ret.code == chsdk.code.success) {
+            if (this.use_ch_audio) {
+                ch.audio.init(this.sount_load_type, this.sound_bundle, this.sound_url);
+            }
+            return true;
+        } else {
+            chsdk.log.error(ret.err);
+            return false;
+        }
+    }
+    /**初化始并在遇到问题时以单机模式进入
+    * @param try_count 重试次数
+    * @param try_interval 重试间隔时间(MS)
+    */
+    async try_init(try_count: number = 5, try_interval: number = 2000): Promise<void> {
+        let ret = await this.init();
+        if (ret) return;
+        try_count--;
+        if (try_count < 0) {
+            this.is_local = true;
+            await this.init();
+            return;
+        } else {
+            await new Promise(resolve => setTimeout(resolve, try_interval));
+            return this.try_init(try_count, try_interval);
+        }
+    }
+}

+ 1 - 0
llk/assets/ch/start/ch_start_pack.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"4aa6336e-6ee2-49a8-b6a4-7011475e21d5","files":[],"subMetas":{},"userData":{}}

+ 9 - 0
llk/assets/ch_ui_module.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "aa2343c6-d30f-4e05-8330-63756bd318c8",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/core.meta

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

+ 9 - 0
llk/assets/core/sdk.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "5e338b79-2198-44e3-9c16-75dca600c1f6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/core/ui.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "e6a9b4aa-6d44-413d-9cde-6cdbc4383184",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 279 - 0
llk/assets/core/ui/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": 375,
+      "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": 0,
+    "_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": 750,
+      "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
llk/assets/core/ui/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"
+  }
+}

+ 320 - 0
llk/assets/core/ui/ui.ts

@@ -0,0 +1,320 @@
+import { _decorator, Component, director, error, instantiate, Node, Prefab, UITransform, Widget, assetManager, AssetManager, tween, v3, UIOpacity, Vec3, ProgressBar, Sprite, Tween } from "cc";
+import ui_base from "./ui_base";
+import { ResolutionAutoFit } from "./ui_ResolutionAutoFit";
+const { ccclass, property } = _decorator;
+@ccclass('ui_updater')
+class ui_updater extends Component {
+    update(dt: number) {
+        ui_base.updateAll(dt);
+    }
+}
+export enum GameUILayers {
+    GAME,
+    JOY_STICK,
+    HUD,
+    POPUP,
+    ALERT,
+    NOTICE,
+    LOADING,
+    OVERLAY
+}
+type Constructor<T extends ui_base> = new () => T;
+/**ui管理模块*/
+class ui {
+    private static _instance: ui;
+    public static getInstance(): ui {
+        if (!this._instance) this._instance = new ui();
+        return this._instance;
+    }
+    public delay(ms: number): Promise<void> {
+        return new Promise(resolve => setTimeout(resolve, ms));
+    }
+    public delay_second(s: number): Promise<void> {
+        return new Promise(resolve => setTimeout(resolve, s * 1000));
+    }
+    public progressBar_anim(bar: ProgressBar, progress: number, duration: number = 0.2): Promise<void> {
+        if (bar.progress >= progress) {
+            bar.progress = progress;
+            return;
+        } else {
+            return new Promise(resolve => tween(bar).to(duration, { progress: progress }).call(() => { resolve() }).start());
+        }
+    }
+    public fillRange_anim(sp: Sprite, progress: number, duration: number = 0.2, limt: -1 | 0 | 1 = 0): Promise<void> {
+        if ((limt > 0 && sp.fillRange >= progress) && (limt < 0 && sp.fillRange <= progress)) {
+            sp.fillRange = progress;
+            return;
+        } else {
+            return new Promise(resolve => tween(sp).to(duration, { fillRange: progress }).call(() => { resolve() }).start());
+        }
+    }
+    public scale_anim(node: Node, duration: number = 0.2, start: number = 0.5, end: number = 1,): Promise<void> {
+        node.setScale(start, start, 1);
+        return new Promise(resolve => tween(node).to(duration, { scale: v3(end, end, 1) }).call(() => { resolve() }).start());
+    }
+    public scale_elasticOut_anim(node: Node, duration: number = 0.2, start: number = 0.5, end: number = 1,): Promise<void> {
+        node.setScale(start, start, 1);
+        return new Promise(resolve => tween(node).to(duration, { scale: v3(end, end, 1) }, { easing: 'elasticOut' }).call(() => { resolve() }).start());
+    }
+    public fade_anim(node: Node, duration: number = 0.2, startOpacity: number = 0, targetOpacity: number = 255): Promise<void> {
+        const opacity = node.getComponent(UIOpacity) ?? node.addComponent(UIOpacity);
+        opacity.opacity = startOpacity;
+        return new Promise((resolve) => { tween(opacity).to(duration, { opacity: targetOpacity }).call(() => { resolve() }).start(); });
+    }
+    public shake_anim(node: Node, duration: number = 0.2, intensity: number = 5, shakeCount: number = 7): Promise<void> {
+        return new Promise((resolve) => {
+            const originalPosition = node.position.clone(); // 保存原始位置  
+            const shakeDuration = duration / (shakeCount * 2); // 每次抖动的持续时间  
+            const shakeTween = tween(node);
+            for (let i = 0; i < shakeCount; i++) {
+                const offsetX = (i % 2 === 0 ? intensity : -intensity); // 交替偏移  
+                shakeTween.to(shakeDuration, { position: v3(originalPosition.x + offsetX, originalPosition.y, originalPosition.z) })
+                    .to(shakeDuration, { position: originalPosition }); // 回到原位  
+            }
+            shakeTween.call(() => { resolve() }).start(); // 开始抖动动画  
+        });
+    }
+    public rotate_shake_anim(node: Node, duration: number = 0.3, intensity: number = 5, shakeCount: number = 5): Promise<void> {
+        return new Promise((resolve) => {
+            const originalAngle = node.angle; // 保存原始角度  
+            const shakeDuration = duration / (shakeCount * 2); // 每次晃动的持续时间  
+            const shakeTween = tween(node);
+            for (let i = 0; i < shakeCount; i++) {
+                const offsetAngle = (i % 2 === 0 ? intensity : -intensity); // 交替旋转  
+                shakeTween.to(shakeDuration, { angle: originalAngle + offsetAngle })
+                    .to(shakeDuration, { angle: originalAngle }); // 回到原位  
+            }
+            shakeTween.call(() => { resolve() }).start(); // 开始角度晃动动画  
+        });
+    }
+    public scale_shake_anim(node: Node, duration: number = 0.3, intensity: number = 0.1, shakeCount: number = 10, reset: number | null = null): Promise<void> {
+        if (reset) {
+            Tween.stopAllByTarget(node);
+            node.setScale(reset, reset, reset);
+        }
+        return new Promise((resolve) => {
+            const originalScale = node.scale.clone(); // 保存原始缩放  
+            const shakeDuration = duration / (shakeCount * 2); // 每次震动的持续时间  
+            const shakeTween = tween(node);
+            for (let i = 0; i < shakeCount; i++) {
+                const offsetScale = (i % 2 === 0 ? intensity : -intensity); // 交替缩放  
+                shakeTween.to(shakeDuration, { scale: v3(originalScale.x + offsetScale, originalScale.y + offsetScale, originalScale.z) })
+                    .to(shakeDuration, { scale: originalScale }); // 回到原位  
+            }
+            shakeTween.call(() => { resolve() }).start(); // 开始缩放震动动画  
+        });
+    }
+    public opacity_shake_anim(node: Node, duration: number = 0.8, intensity: number = 80, shakeCount: number = 5): Promise<void> {
+        const opacity = node.getComponent(UIOpacity) ?? node.addComponent(UIOpacity);
+        const originalOpacity = opacity.opacity;
+        const shakeDuration = duration / (shakeCount * 2);
+        return new Promise((resolve) => {
+            const shakeTween = tween(opacity);
+            for (let i = 0; i < shakeCount; i++) {
+                const offsetOpacity = (i % 2 === 0 ? intensity : -intensity);
+                shakeTween.to(shakeDuration, { opacity: Math.min(Math.max(originalOpacity + offsetOpacity, 0), 255) })
+                    .to(shakeDuration, { opacity: originalOpacity });
+            }
+            shakeTween.call(() => { resolve() }).start(); // 开始透明度震动动画  
+        });
+    }
+    /**处理弹跳动画*/
+    public bounce_anim(node: Node, height: number = 100, duration: number = 0.5): Promise<void> {
+        return new Promise((resolve) => {
+            tween(node)
+                .to(duration, { position: node.position.clone().add(new Vec3(0, height, 0)) }, { easing: 'bounceOut' }) // 向上弹跳  
+                .to(duration, { position: node.position }, { easing: 'bounceIn' }) // 回到原位  
+                .call(() => { resolve() }) //完成动画  
+                .start(); //开始动画
+        });
+    }
+    /**
+     * 奖励物飞行动画
+     * @param node 奖励物节点
+     * @param startPos 起始爆开点
+     * @param targetPos 最终目标点
+     * @param explosionDistance 爆开距离
+     * @param explosionDuration 爆开时间
+     * @param stayDuration 停留时间
+     * @param moveDuration 移动到目标的时间
+     * @returns 
+     */
+    public reward_fly_anim(node: Node, startPos: Vec3, targetPos: Vec3, explosionDistance: number = 100, explosionDuration: number = 0.3, stayDuration: number = 0.5, moveDuration: number = 0.3): Promise<void> {
+        node.setPosition(startPos);
+        return new Promise((resolve) => {
+            //随机方向
+            const randomDirection = new Vec3(Math.random() * 2 - 1, Math.random() * 2 - 1, 0).normalize();
+            const explosionEndPos = startPos.add(randomDirection.multiplyScalar(explosionDistance)); // 爆炸效果的位置  
+            tween(node)
+                .to(explosionDuration, { worldPosition: explosionEndPos }) // 爆炸动画  
+                .delay(stayDuration) //停留时间  
+                .to(moveDuration, { worldPosition: targetPos }, { easing: 'cubicIn' })
+                .call(() => { resolve() }).start();
+        });
+    }
+    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;
+    }
+    private getLayers(): GameUILayers[] {
+        return Object.values(GameUILayers).filter(value => typeof value === 'number') as GameUILayers[];
+    }
+
+    /**
+     * @en init,`don't call more than once`.
+     * @zh 初始化UIMgr,`不要多次调用`
+     *  */
+    public init(uiCanvas: Node | Prefab) {
+        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 = '__ui_canvas__';
+        director.addPersistRootNode(this._uiCanvas);
+        if (!this._uiCanvas.getComponent(ui_updater)) {
+            this._uiCanvas.addComponent(ui_updater);
+        }
+        let canvas = this._uiCanvas.getComponent(UITransform);
+        this._uiCanvas.addComponent(ResolutionAutoFit);
+        this._uiRoot = this.createFullScreenNode();
+        this._uiRoot.name = 'root'
+        canvas.node.addChild(this._uiRoot);
+        const layers = this.getLayers();
+        //create layers
+        for (let i = 0; i < layers.length; ++i) {
+            let layerNode = this.createFullScreenNode();
+            layerNode.name = 'layer_' + GameUILayers[layers[i]];
+            this._uiRoot.addChild(layerNode);
+        }
+    }
+    /**获取层级节点*/
+    public getLayerNode(layerIndex: number): Node {
+        return this._uiRoot.children[layerIndex] || this._uiRoot;
+    }
+    /**关闭所有界面*/
+    public closeAll() {
+        ui_base.closeAll();
+    }
+    /**关闭和释放所有界面 */
+    public closeAndReleaseAll() {
+        ui_base.closeAndReleaseAll();
+    }
+    /**关闭某个界面*/
+    public close<T extends ui_base>(uiCls: Constructor<T>): void {
+        this.get(uiCls)?.close();
+    }
+    /**获取界面*/
+    public get<T extends ui_base>(uiCls: Constructor<T>): T {
+        let all = (ui_base as any)._uis;
+        for (let i = 0; i < all.length; ++i) {
+            let c = all[i];
+            if (c instanceof uiCls) {
+                return c;
+            }
+        }
+        return null;
+    }
+    /**某个界面是否显示中*/
+    public isShowing<T extends ui_base>(uiCls: Constructor<T>): boolean {
+        return (ui_base as any)._hasCls(uiCls);
+    }
+    private _clss_loading: Set<any> = new Set();
+    /**是否有正在加载中的界面*/
+    public isLoading<T extends ui_base>(uiCls: Constructor<T> | null): boolean {
+        if (!uiCls) return this._clss_loading.size > 0;
+        return this._clss_loading.has(uiCls);
+    }
+    /***
+     * @en show ui by the given parameters.
+     * @zh 显示UI
+     * @param uiCls the class, must inherits from the class `UIController`.
+     * @returns the instance of `uiCls`
+     *  */
+    public async show<T extends ui_base>(uiCls: Constructor<T>, ...data: any[]): Promise<T | null> {
+        if (this.isLoading(uiCls)) return null;
+        if (this.isShowing(uiCls)) return null;
+        let ui = new uiCls();
+        this._clss_loading.add(uiCls);
+        let bundleName = ui.bundle;
+        if (bundleName) {
+            let bundle = assetManager.getBundle(bundleName);
+            if (!bundle) {
+                try {
+                    const loadedBundle = await this.loadBundleAsync(bundleName);
+                    return await this._create(loadedBundle, ui, uiCls, ...data);
+                } catch (err) {
+                    console.error(err);
+                    this._clss_loading.delete(uiCls);
+                }
+            } else {
+                return await this._create(bundle, ui, uiCls, ...data);
+            }
+        } else {
+            this._clss_loading.delete(uiCls);
+            console.error("ui no bundle name");
+            return null;
+        }
+    }
+    private async _create<T extends ui_base>(bundle: AssetManager.Bundle, ui: T, uiCls: Constructor<T>, ...p: any[]): Promise<T> {
+        try {
+            const data: Prefab = await this.loadPrefabAsync(bundle, ui.prefab);
+            let node: Node = instantiate(data);
+            let parent = this.getLayerNode(ui.layer);
+            parent.addChild(node);
+            (ui as any)._setup(uiCls, node, ...p);
+            this._clss_loading.delete(uiCls);
+            return ui;
+        } catch (err) {
+            console.error(err);
+            this._clss_loading.delete(uiCls);
+            return;
+        }
+    }
+    private loadBundleAsync(bundleName: string): Promise<AssetManager.Bundle> {
+        return new Promise((resolve, reject) => {
+            assetManager.loadBundle(bundleName, null, (err, loadedBundle) => {
+                if (err) {
+                    reject(err);
+                } else {
+                    resolve(loadedBundle);
+                }
+            });
+        });
+    }
+    private loadPrefabAsync(bundle: AssetManager.Bundle, prefabName: string): Promise<Prefab> {
+        return new Promise((resolve, reject) => {
+            bundle.load(prefabName, (err, data: Prefab) => {
+                if (err) {
+                    reject(err);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+}
+export const gui = ui.getInstance();
+export { ui_base };

+ 1 - 0
llk/assets/core/ui/ui.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"81e48c31-8c60-4c3e-9dab-40e2439634c6","files":[],"subMetas":{},"userData":{}}

+ 41 - 0
llk/assets/core/ui/ui_ResolutionAutoFit.ts

@@ -0,0 +1,41 @@
+import { _decorator, Component, Node, size, Size, view,screen, ResolutionPolicy} from 'cc';
+const { ccclass, property } = _decorator;
+
+const CHECK_INTERVAL = 0.1;
+@ccclass('ch.ResolutionAutoFit')
+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
llk/assets/core/ui/ui_ResolutionAutoFit.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "c7d72489-a84b-4886-92e2-028dc77602d1",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 358 - 0
llk/assets/core/ui/ui_base.ts

@@ -0,0 +1,358 @@
+import { _decorator, assetManager, Button, Component, EventHandler, EventTouch, find, isValid, Node, Prefab, Toggle, ToggleContainer } from 'cc';
+const { ccclass, property } = _decorator;
+/***
+ * @en internal class, used for handling node event.
+ * @zh 内部类,用于节点事件监听
+ * 
+ *  */
+@ccclass('tgxNodeEventAgent')
+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]);
+            }
+        }
+    }
+}
+let _id:number=0;
+export default class ui_base  {
+    private static _clss:Set<any>=new Set();
+    private static _uis: ui_base[] = [];
+    /***
+    * @en hide and destroy all ui panel.
+    * @zh 隐藏并销毁所有UI面板
+    * */
+    public static closeAll() {
+        while (this._uis.length) {
+            this._uis[0].close();
+        }
+        this._clss.clear();
+    }
+    //
+    public static closeAndReleaseAll(){
+        while (this._uis.length) {
+            this._uis[0].closeAndRelease();
+        }
+        this._clss.clear();
+    }
+    //update all ui, called by UI.
+    public static updateAll(dt:number) {
+        for (let i = 0; i < this._uis.length; ++i) {
+            let ctrl = this._uis[i];
+            if (ctrl.node && isValid(ctrl.node)) {
+                this._uis[i].onUpdate(dt);
+            }
+        }
+    }
+    private static _addCls(cls:any):void{
+        if(!cls) return;this._clss.add(cls);
+    }
+    private static _removeCls(cls:any):void{
+        if(!cls) return;this._clss.delete(cls);
+    }
+    private static _hasCls(cls:any):boolean{
+        return this._clss.has(cls);
+    }
+    private _id: number = 0;
+    private _bundle:string;
+    private _prefab: string;
+    private _layer: number;
+    private _layout: any;
+    private _cls:any;
+    protected node: Node;
+    private _destroyed:boolean = false;
+    /***
+     * @en the instance id to indicate an unique ui panel.
+     * @zh 实例ID,用于标记一个唯一面板实例
+     *  */
+    public get id(): number {return this._id;}
+    /***
+    * @en url of the prefab used by this ui panel.
+    * @zh 本UI使用prefab路径
+    *  */
+    public get prefab(): string  {return this._prefab;}
+    public get bundle():string{return this._bundle;}
+    /***
+     * @en layer of this ui panel.
+     * @zh 本UI所在的UI层级
+     *  */
+    public get layer(): number {return this._layer;}
+    /***
+     * @en layout of this ui panel.
+     * @zh 本UI组件
+     *  */
+    public get layout(): Component { return this._layout;}
+    public getLayout<T extends Component>():T{ return this._layout as T;}
+    constructor(bundle:string,prefab: string, layer: number, layoutCls: any) {
+        this._cls = null;
+        this._bundle=bundle;
+        this._prefab = prefab;
+        this._layer = layer;
+        this._layout = layoutCls;
+        this._id = _id++;
+    }
+    //setup this ui,called by UIMgr.
+    private _setup(cls:any,node: Node,...data: any[]) {
+        ui_base._uis.push(this);
+        this._cls=cls;
+        (ui_base as any)._addCls(this._cls);
+        this.node = node;
+        if (this._layout) this._layout = this.node.getComponent(this._layout);
+        //notify sub class to handle something.
+        //节点创建完毕,调用子类的处理函数。
+        this.onCreated(...data);
+        //check whether it has been destroyed, if has, hide it.
+        //检查是否为已销毁,如果已销毁,则走销毁流程
+        if(this._destroyed) this.close();
+    }
+    /**
+     * @en hide and destroy this ui panel.
+     * @zh 隐藏并销毁此UI面板
+     *  */
+    public close(){
+        this._resolve_close?.();
+        this._resolve_close=null;
+        this._destroyed = true;
+        if(!this.node) return;
+        this.node.removeFromParent();
+        for (let i = 0; i < ui_base._uis.length; ++i) {
+            if (ui_base._uis[i] == this) {
+                ui_base._uis.splice(i, 1);
+                break;
+            }
+        }
+        this.onDispose();
+        this.node.destroy();
+        this.node = null;
+        (ui_base as any)._removeCls(this._cls);
+        this._cls=null;
+    }
+    public closeAndRelease(){
+        this.close();
+        assetManager.getBundle(this._bundle)?.release(this._prefab);
+    }
+    private _resolve_close: (() => void) | null = null;
+    /**等待此ui关闭*/
+    public wait_close():Promise<void>{
+        if(this._resolve_close) return;
+        return new Promise((resolve) => {this._resolve_close = resolve;});
+    }
+    /**
+     * @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 = '' + _id++;
+
+        //附加额外信息 供事件转发使用
+        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 = '' + _id++;
+
+        //附加额外信息 供事件转发使用
+        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;
+            }
+        }
+    }
+    //子类的所有操作,需要在这个函数之后。
+    protected onCreated(...data: any[]){}
+    //当界面销毁时调用
+    protected onDispose(){}
+    //
+    protected onUpdate(dt?:number) { }
+}
+
+

+ 9 - 0
llk/assets/core/ui/ui_base.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "a34ebbf0-9efe-44d8-b1b6-27c447c17536",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/core/util.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "3e9ea6dc-0e45-4763-812d-7a1c1d325a49",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 209 - 0
llk/assets/core/util/ArrayUtil.ts

@@ -0,0 +1,209 @@
+export default class ArrayUtil {
+    /**随机打乱一个数组 */
+    public static  shuffleArray<T>(array: T[]): T[] {
+        let currentIndex = array.length;
+        let temporaryValue;
+        let randomIndex;
+      
+        // While there remain elements to shuffle...
+        while (currentIndex !== 0) {
+          // Pick a remaining element...
+          randomIndex = Math.floor(Math.random() * currentIndex);
+          currentIndex--;
+      
+          // And swap it with the current element.
+          temporaryValue = array[currentIndex];
+          array[currentIndex] = array[randomIndex];
+          array[randomIndex] = temporaryValue;
+        }
+        return array;
+    }
+    // Fisher-Yates 洗牌算法
+    //const originalArray = [1, 2, 3, 4, 5];  
+    //const shuffledArray = shuffleArray([...originalArray]);
+    public static shuffleArray2<T>(array: T[]): T[] {  
+        for (let i = array.length - 1; i > 0; i--) {  
+            // 生成一个随机索引  
+            const j = Math.floor(Math.random() * (i + 1));  
+            // 交换元素  
+            [array[i], array[j]] = [array[j], array[i]];  
+        }  
+        return array;  
+    }  
+    /**合并两个数组元素,移除相同的 */
+    public static MergeAndRemoveDuplicates<T>(arr1: T[], arr2: T[]): T[] {
+        const merged = [...arr1, ...arr2]; // 合并两个数组
+        const uniqueValues = Array.from(new Set(merged)); // 去重
+        return uniqueValues;
+    }
+    /**排除数组1在数组2里有的元素,返回新数组*/
+    public static ExcludeElements<T>(array1:T[],array2:T[]):T[]{
+        const set = new Set(array2); // 将 array2 转换为 Set
+        return array1.filter(item => !set.has(item));
+    }
+    /**检测数组元素是否能满足顺序的组合*/
+    public static checkSequentialCombination(arr: number[]): number[] {
+        const sortedArr = arr.slice().sort((a, b) => a - b);
+        for (let i = 0; i < sortedArr.length - 1; i++) {
+            if (sortedArr[i] + 1 !== sortedArr[i + 1]) {
+                return [];
+            }
+        }
+        return sortedArr;
+    }
+    /**从一个数组中找出是否有连续的组合*/
+    public static findSequentialCombinations(arr: number[]): number[][] {
+        const result: number[][] = [];
+        arr.sort((a, b) => a - b);
+        let currentSequence: number[] = [];
+        for (let i = 0; i < arr.length; i++) {
+            if (currentSequence.length === 0 || arr[i] === currentSequence[currentSequence.length - 1] + 1) {
+                currentSequence.push(arr[i]);
+            } else {
+                if (currentSequence.length > 1) {
+                    result.push(currentSequence);
+                }
+                currentSequence = [arr[i]];
+            }
+        }
+        if (currentSequence.length > 1) {
+            result.push(currentSequence);
+        }
+        return result;
+    }
+    /*eg:const arr = [2, 4,2, 5, 3, 6, 9,9,10,11,];
+    const combinations = this.findSequentialCombinations(arr);
+    
+    if (combinations.length > 0) {
+        console.log("所有可能的连续顺序组合为:");
+        combinations.forEach(combination => {
+            console.log(combination);
+        });
+    } else {
+        console.log("无法找到连续顺序组合");
+    }*/
+
+    /**数组中删除一个合条件的*/
+    public static DeleteOneItem<T>(list:Array<T>,check:(item:T)=>boolean):T|null{
+        let length=list.length;
+        for(let i:number=0;i<length;i++){
+            if(check(list[i])){
+                return list.splice(i,1)[0];
+                //i--;
+                //length--;
+            }
+        }
+        return null;
+    }
+
+    /**插入排序(适用于差异不会很大,相对有序的数据)Array.sort() 快速排序 算法更适用于混乱无章的数据 */
+    public static InsertionSort(array:any[]):void{
+            const count = array.length;
+            if(count<=1)return;
+            let t1:any;
+            let t2:any;
+            for(let i=1;i<count;i++){
+                t1 = array[i];
+                let j:number;
+                for(j=i;j>0 && t1.sortValue>(t2=array[j-1]).sortValue;j--){
+                    array[j]=t2;
+                }
+                array[j]=t1;
+            }
+    }
+    // 元素排序
+    /*示例
+     负数在前
+    const checkMinusNumber = function (val: number) {
+            return val > 0;
+    };
+    const arr = [2, 4, 5, 6, 7, -8, -10 - 12, -2];
+    adjustArrayOrder.reorder(arr, checkMinusNumber);
+    console.log(arr);
+    */
+    public static reorder(arr: Array<number>, checkFun: (checkVal: number) => boolean): void {
+         let end = arr.length - 1;
+         let begin =0;
+         while (begin < end) {
+               // 向后移动begin
+               while (begin < end && !checkFun(arr[begin])) {
+                     begin++;
+               }
+               // 向前移动end
+               while (begin < end && checkFun(arr[end])) {
+                   end--;
+               }
+               // begin与end都指向了正确的位置
+               if (begin < end) {
+                    // 交换两个元素的顺序
+                    [arr[begin], arr[end]] = [arr[end], arr[begin]];
+               }
+          }
+    }
+    //冒泡排序
+    /// <summary>
+	/// 各种类型冒泡排序比
+	 /// </summary>/// 例子:
+                      /// CommonSort<int>(new int[]{2,3,1,45,123,4},compare)
+                      /// bool compare(int a,int b){
+                      ///    	 if(a>b)return true;
+                      ///   	 reutn false;
+                      /// }
+	/// <param name="sortArray"></param>
+	/// <param name="compareMethod"></param>
+	/// <typeparam name="T"></typeparam>
+	static  CommonSort<T>(sortArray:Array<T>, compareMethod:(a:T,b:T)=>boolean):void
+	{
+		let swapped = true;
+		do
+		{
+			swapped = false;
+			for (let i = 0; i < sortArray.length - 1; i++)
+			{
+				if (compareMethod(sortArray[i], sortArray[i + 1]))
+				{
+					let temp = sortArray[i];
+					sortArray[i] = sortArray[i + 1];
+					sortArray[i + 1] = temp;
+					swapped = true;
+				}
+			}
+		} while (swapped);
+    }
+    /**归并排序 */
+    /*var arr: number[] = [];
+	for (var i: number = 0; i < 1000; i++) {
+	    arr.push(Math.floor(1000 - i));//生成降序的数组
+	}
+	MergeSort(arr, 0, arr.length);//调用归并排序算法*/
+    //拆分数组  分
+    static MergeSort(arr: number[], lo: number, hi: number): void {
+        if (hi - lo < 2) return;//单个元素无需考虑
+        var mi: number = (lo + hi) >> 1;//已中点为界 或者改成Math.floor((lo + hi) / 2)
+        this.MergeSort(arr, lo, mi);//对左边排序
+        this.MergeSort(arr, mi, hi);//对右边排序
+        this.merge(arr, lo, mi, hi);//归并
+    }
+    //归并算法实现 合
+    private static  merge(arr: number[], lo: number, mi: number, hi: number): void {
+        var A: number[] = arr.slice(lo, hi);//A[lo, hi)=arr[lo, hi)
+        var lb: number = mi - lo;
+        var B: number[] = new Array(lb);
+        for (var i = 0; i < lb; B[i] = A[i++]);//复制左子向量B[0, lb) = arr[lo, mi)
+        var lc: number = hi - mi;
+        var C: number[] = arr.slice(mi, hi);//后子向量C[0,lc) = arr[mi, hi)
+        for (var i = 0, j = 0, k = 0; j < lb;) {//反复冲B和C中取出更小者
+            if (k < lc && C[k] < B[j]) {//将其归入A中
+                A[i++] = C[k++];
+            }
+            if (lc <= k || B[j] <= C[k]) {
+                A[i++] = B[j++];
+            }
+        }
+        for (var i = 0; i < A.length; arr[lo + i] = A[i++]);//把A中的值赋给原来的数组
+        B.length = 0;
+        C.length = 0;
+        A.length = 0;
+    }
+}
+

+ 9 - 0
llk/assets/core/util/ArrayUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "8334108e-7a2b-4f44-81dc-c35f154bb837",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 420 - 0
llk/assets/core/util/DataTimeUtil.ts

@@ -0,0 +1,420 @@
+export default class  DataTimeUtil {
+        /**时间偏移*/
+        public static offset: number = 0;
+        /**
+         * 当前时间戳(毫秒)
+         * @returns 
+         */
+        public static currentTimeMillis (): number {
+            return new Date().getTime() + this.offset;
+        }
+    
+        /**
+         * 当前时间戳(秒)
+         * @returns
+         */
+        public static currentTimeSeconds (): number {
+            return Math.floor((new Date().getTime() + this.offset) / 1000);
+        }
+        /**当前服务器日期*/
+        public static currentDate():Date{
+            return new Date(this.currentTimeMillis ());
+        }
+        /**将毫秒时间戳转到当天0点时的毫秒时间戳*/
+        public static timestampToStartOfDay(timestamp:number):number {
+            let date = new Date(timestamp); // 将时间戳转换为日期对象
+            date.setHours(0, 0, 0, 0); // 将时、分、秒、毫秒部分设置为0
+            return date.getTime(); // 将日期转换回时间戳
+        }
+        /**将秒时间戳转到当天0点时的秒时间戳*/
+        public static timestampSecondsToStartOfDay(timestamp:number):number {
+            let date = new Date(timestamp*1000); // 将时间戳转换为日期对象
+            date.setHours(0, 0, 0, 0); // 将时、分、秒、毫秒部分设置为0
+            return Math.floor(date.getTime()/1000); // 将日期转换回时间戳
+        }
+        /**获取服务器到明天0点时长 */
+        public static longToTomorrow():number{
+            // 获取当前时间
+           const now = this.currentDate();
+           // 获取明天的日期
+           const tomorrow = new Date(now);
+           tomorrow.setDate(now.getDate() + 1);
+           tomorrow.setHours(0, 0, 0, 0);
+           // 计算时间差
+           return tomorrow.getTime() - now.getTime();
+        }
+        /**当前服务器月份*/
+        public static currentMonth():number{
+              return this.currentDate().getMonth() + 1; // 月份从0开始,所以需要加1
+        }
+        /**当前服务器日期月有多少天 */
+        public static daysInCurrentMonth():number{
+              const currentYear = this.currentDate().getFullYear();
+              const daysInCurrentMonth = new Date(currentYear, this.currentMonth(), 0).getDate();
+              return daysInCurrentMonth;
+        }
+        public static timeMillisToDate(ms:number):Date{
+            return new Date(ms);
+        }
+        public static stringToStamp(dateString:string):number{
+            const timestamp: number = Date.parse(dateString) - new Date().getTimezoneOffset() * 60 * 1000;
+            return timestamp;
+        }
+        public static stringToDate(dateString:string):Date{
+            return new Date(dateString);
+        }
+        public static dateToString(date:Date):string{
+             const year: number = date.getFullYear();
+             const month: number = date.getMonth() + 1;
+             const day: number = date.getDate();
+             const hours: number = date.getHours();
+             const minutes: number = date.getMinutes();
+             const seconds: number = date.getSeconds();
+             // 自定义格式化日期和时间
+             const formattedDateTime: string = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+             return formattedDateTime;
+        }
+        /**本周一0点的时间戳*/
+        public static mondayTimestamp():number{
+            const currentDate=this.currentDate();
+            // 获取当前日期的星期几(星期天为0,星期一为1,以此类推)
+            const currentDayOfWeek = currentDate.getDay();
+            // 计算当前日期距离周一的天数差值
+            const daysUntilMonday = (currentDayOfWeek === 0) ? 6 : currentDayOfWeek - 1;
+            return currentDate.getTime() - (daysUntilMonday * 24 * 60 * 60 * 1000);
+        }
+
+
+        /*private _day_s = 864e5;
+        private _diff:number=0;
+        private updateServerTime(s:number):void{ this._diff = s - (new Date).getTime()}
+        public now():number{return this.getTime()}
+        public getTime():number { return (new Date).getTime() + this._diff;} 
+        public getDayStartTime(s:number):number {return new Date(s).setHours(0, 0, 0, 0)}
+        public getDayEndTime(e:number):number { return new Date(e).setHours(23, 59, 59, 999)}
+        public getWeekEndTime (e:number):number {
+              var t = new Date(e).getDay();
+              return this.getDayEndTime(e) + (0 === t ? 0 : (7 - t) * this._day_s)
+        }
+        public getMonthEndTime(e:number):number {
+              var t = new Date(e);
+              return 11 === t.getMonth() ? t.setFullYear(t.getFullYear() + 1, 0, 0) : t.setMonth(t.getMonth() + 1, 0), t.setHours(23, 59, 59, 999)
+        }
+        public getDiffDayNum (e:number, t:number) {
+              var r = this.getDayStartTime(e),o = this.getDayStartTime(t);
+              return Math.ceil(Math.abs(r - o) / this._day_s)
+        }*/
+
+
+
+
+        /**获取时间戳*/
+        private static getTickCount() {
+            if(window && window.performance){
+                return window.performance.now();
+            }
+            return (new Date()).getTime();
+        }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    //-----------------------------------------------------------------------------------------------------------------------------
+    /**
+     * 根据秒数格式化字符串
+     * @param second 秒数
+     * @param type 1:00:00:00   2:yyyy-mm-dd h:m:s    3:00:00(分:秒)   4:xx天前,xx小时前,xx分钟前    6:00:00(时:分)  
+     * @return
+     *
+     */
+    public static getFormatBySecond(second: number, type: number = 1): string {
+        var str: string = "";
+        switch (type) {
+            case 1:
+                str = this.getFormatBySecond1(second);
+                break;
+            case 2:
+                str = this.getFormatBySecond2(second);
+                break;
+            case 3:
+                str = this.getFormatBySecond3(second);
+                break;
+            case 4:
+                str = this.getFormatBySecond4(second);
+                break;
+            case 5:
+                str = this.getFormatBySecond5(second);
+                break;
+            case 6:
+                str = this.getFormatBySecond6(second);
+                break;
+        }
+        return str;
+    }
+
+    //1: 00:00:00
+    private static getFormatBySecond1(t: number = 0): string {
+        var hourst: number = Math.floor(t / 3600);
+        var hours: string;
+        if (hourst == 0) {
+            hours = "00";
+        } else {
+            if (hourst < 10)
+                hours = "0" + hourst;
+            else
+                hours = "" + hourst;
+        }
+        var minst: number = Math.floor((t - hourst * 3600) / 60);
+        var secondt: number = Math.floor((t - hourst * 3600) % 60);
+        var mins: string;
+        var sens: string;
+        if (minst == 0) {
+            mins = "00";
+        } else if (minst < 10) {
+            mins = "0" + minst;
+        } else {
+            mins = "" + minst;
+        }
+        if (secondt == 0) {
+            sens = "00";
+        } else if (secondt < 10) {
+            sens = "0" + secondt;
+        } else {
+            sens = "" + secondt;
+        }
+        return hours + ":" + mins + ":" + sens;
+    }
+
+    //3:00:00(分:秒)
+    private static getFormatBySecond3(t: number = 0): string {
+        var hourst: number = Math.floor(t / 3600);
+        var minst: number = Math.floor((t - hourst * 3600) / 60);
+        var secondt: number = Math.floor((t - hourst * 3600) % 60);
+        var mins: string;
+        var sens: string;
+        if (minst == 0) {
+            mins = "00";
+        } else if (minst < 10) {
+            mins = "0" + minst;
+        } else {
+            mins = "" + minst;
+        }
+        if (secondt == 0) {
+            sens = "00";
+        } else if (secondt < 10) {
+            sens = "0" + secondt;
+        } else {
+            sens = "" + secondt;
+        }
+        return mins + ":" + sens;
+    }
+
+    //2:yyyy-mm-dd h:m:s
+    private static getFormatBySecond2(time: number): string {
+        var date: Date = new Date(time*1000);
+        var year: number = date.getFullYear();
+        var month: number = date.getMonth() + 1; 	//返回的月份从0-11;
+        var day: number = date.getDate();
+        var hours: number = date.getHours();
+        var minute: number = date.getMinutes();
+        var second: number = date.getSeconds();
+        return year + "-" + month + "-" + day + " " + hours + ":" + minute + ":" + second;
+
+    }
+
+    //4:xx天前,xx小时前,xx分钟前
+    private static getFormatBySecond4(time: number): string {
+        var t = Math.floor(time / 3600);
+        if (t > 0) {
+            if (t > 24) {
+                return Math.floor(t / 24) + "天前";
+            }
+            else {
+                return t + "小时前";
+            }
+        }
+        else {
+            return Math.floor(time / 60) + "分钟前";
+        }
+    }
+
+    private static getFormatBySecond5(time: number): string {
+        //每个时间单位所对应的秒数
+        var oneDay: number = 3600 * 24;
+        var oneHourst: number = 3600;
+        var oneMinst: number = 60;
+
+        var days = Math.floor(time / oneDay);
+        var hourst: number = Math.floor(time % oneDay / oneHourst)
+        var minst: number = Math.floor((time - hourst * oneHourst) / oneMinst)  //Math.floor(time % oneDay % oneHourst / oneMinst);
+        var secondt: number = Math.floor((time - hourst * oneHourst) % oneMinst) //time;
+
+        var dayss: string = "";
+        var hourss: string = ""
+        var minss: string = "";
+        var secss: string = ""
+        if (time > 0) {
+            //天
+            if (days == 0) {
+                dayss = "";
+                //小时
+                if (hourst == 0) {
+                    hourss = "";
+                    //分
+                    if (minst == 0) {
+                        minss = "";
+                        if (secondt == 0) {
+                            secss = "";
+                        } else if (secondt < 10) {
+                            secss = "0" + secondt + "秒";
+                        } else {
+                            secss = "" + secondt + "秒";
+                        }
+
+                        return secss;
+                    }
+                    else {
+                        minss = "" + minst + "分";
+                        if (secondt == 0) {
+                            secss = "";
+                        } else if (secondt < 10) {
+                            secss = "0" + secondt + "秒";
+                        } else {
+                            secss = "" + secondt + "秒";
+                        }
+
+                    }
+
+                    return minss + secss;
+                }
+                else {
+                    hourss = hourst + "小时";
+                    if (minst == 0) {
+                        minss = "";
+                        if (secondt == 0) {
+                            secss = "";
+                        } else if (secondt < 10) {
+                            secss = "0" + secondt + "秒";
+                        } else {
+                            secss = "" + secondt + "秒";
+                        }
+
+                        return secss
+
+                    } else if (minst < 10) {
+                        minss = "0" + minst + "分";
+                    } else {
+                        minss = "" + minst + "分";
+                    }
+
+                    return hourss + minss;
+
+                }
+            }
+            else {
+                dayss = days + "天";
+                if (hourst == 0) {
+                    hourss = "";
+                } else {
+                    if (hourst < 10)
+                        hourss = "0" + hourst + "小时";
+                    else
+                        hourss = "" + hourst + "小时";
+                    ;
+                }
+                return dayss + hourss;
+            }
+        }
+        return "";
+    }
+
+    //6:00:00(时:分) 
+    private static getFormatBySecond6(t: number = 0): string {
+        var hourst: number = Math.floor(t / 3600);
+        var minst: number = Math.floor((t - hourst * 3600) / 60);
+        var houers: string;
+        var mins: string;
+        if (hourst == 0) {
+            houers = "00";
+        } else if (hourst < 10) {
+            houers = "0" + hourst;
+        } else {
+            houers = "" + hourst;
+        }
+        if (minst == 0) {
+            mins = "00";
+        } else if (minst < 10) {
+            mins = "0" + minst;
+        } else {
+            mins = "" + minst;
+        }
+        return houers + ":" + mins;
+    }
+
+
+    /**
+     * 获取当前是周几
+     * ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"]
+     */
+    public static getDay(timestamp: number): number {
+        let date = new Date(timestamp);
+        return date.getDay();
+    }
+
+    /**
+     * 判定两个时间是否是同一天
+     */
+    public static isSameDate(timestamp1: number, timestamp2: number): boolean {
+        let date1 = new Date(timestamp1);
+        let date2 = new Date(timestamp2);
+        return date1.getFullYear() == date2.getFullYear()
+            && date1.getMonth() == date2.getMonth()
+            && date1.getDate() == date2.getDate();
+    }
+
+    /**
+     * 日期格式化
+     */
+    public static format(d: Date, fmt: string = "yyyy-MM-dd hh:mm:ss"): string {
+        let o = {
+            "M+": d.getMonth() + 1, //month
+            "d+": d.getDate(),    //day
+            "h+": d.getHours(),   //hour
+            "m+": d.getMinutes(), //minute
+            "s+": d.getSeconds(), //second
+            "q+": Math.floor((d.getMonth() + 3) / 3),  //quarter
+            "S": d.getMilliseconds() //millisecond
+        }
+        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1,
+            (d.getFullYear() + "").substr(4 - RegExp.$1.length));
+        for (var k in o) if (new RegExp("(" + k + ")").test(fmt))
+            fmt = fmt.replace(RegExp.$1,
+                RegExp.$1.length == 1 ? o[k] :
+                    ("00" + o[k]).substr(("" + o[k]).length));
+        return fmt;
+    }
+    /**
+     * 计算两个时间(秒)相差天数(不满一天算0天)
+     */
+    public static getDayCountSeconds(timestamp1: number, timestamp2: number): number {
+        const d_value: number = Math.abs(timestamp2 - timestamp1);
+        return Math.floor(d_value / (24 * 60 * 60));
+    }
+    /**
+     * 计算两个时间相差天数
+     */
+    public static dateDifference(timestamp1: number, timestamp2: number): number {
+        const d_value: number = Math.abs(timestamp2 - timestamp1);
+        return Math.ceil(d_value / (24 * 60 * 60 * 1000));
+    }
+}

+ 9 - 0
llk/assets/core/util/DataTimeUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6aa3e5d2-e8f8-414a-ba3b-5f2c9e97456c",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 172 - 0
llk/assets/core/util/DirectorUtil.ts

@@ -0,0 +1,172 @@
+import { director } from "cc";
+import { TweenSystem } from "cc";
+import { PhysicsSystem } from "cc";
+import { PhysicsSystem2D } from "cc";
+import { AnimationManager } from "cc";
+import { sp } from "cc";
+import { Animation, AnimationState, Node } from "cc";
+export default class DirectorUtil {
+	//没有用到龙骨 dragon_bones_as:dragonBones.ArmatureDisplay[]
+	private static pause_data: { state_b: boolean, physics_2d_b: boolean, physics_3d_b: boolean, scheduler_as: any[], anim_as: AnimationState[], tween_target_as: any[], spine_as: sp.Skeleton[] } = {
+		/**暂停状态 */
+		state_b: false,
+		/**2d物理系统状态 */
+		physics_2d_b: false,
+		/**3d物理系统状态 */
+		physics_3d_b: false,
+		/**定时器对象列表 */
+		scheduler_as: [],
+		/**动画列表 */
+		anim_as: [],
+		/**缓动对象列表 */
+		tween_target_as: [],
+		/**龙骨组件列表 */
+		//dragon_bones_as:[],
+		spine_as: []
+	};
+	/**暂停游戏 */
+	static pause(config_?: {/**排除节点 */exclude_as?: Node[];/**递归排除节点 */recu_exclude_as?: Node[] }): void {
+		if (this.pause_data.state_b) return;
+		// 暂停定时器
+		this.pause_data.scheduler_as = director.getScheduler().pauseAllTargets();
+		// 暂停当前动画
+		{
+			let anim_system = director.getSystem(AnimationManager.ID);
+			this.pause_data.anim_as.splice(0, this.pause_data.anim_as.length, ...anim_system["_anims"].array);
+			this.pause_data.anim_as.forEach(v1 => { v1.pause(); });
+		}
+		// 暂停spine动画
+		{
+			this.pause_data.spine_as = director.getScene().getComponentsInChildren(sp.Skeleton);
+			this.pause_data.spine_as.forEach(v1 => { v1.timeScale = 0; });
+		}
+		// 暂停龙骨动画
+		// {
+		//this.pause_data.dragon_bones_as = director.getScene().getComponentsInChildren(dragonBones.ArmatureDisplay);
+		//this.pause_data.dragon_bones_as.forEach(v1 => {v1.timeScale = 0;});
+		//}
+		// 暂停当前缓动
+		this.pause_data.tween_target_as = TweenSystem.instance.ActionManager.pauseAllRunningActions();
+		// 暂停物理系统
+		{
+			if (PhysicsSystem2D && PhysicsSystem2D.instance.enable) {
+				this.pause_data.physics_2d_b = PhysicsSystem2D.instance.enable;
+				PhysicsSystem2D.instance.enable = false;
+			}
+			if (PhysicsSystem && PhysicsSystem.instance.enable) {
+				this.pause_data.physics_3d_b = PhysicsSystem.instance.enable;
+				PhysicsSystem.instance.enable = false;
+			}
+		}
+		// 恢复排除节点
+		if (config_) {
+			let exclude_as: Node[] = [];
+			exclude_as.push(...config_.exclude_as);
+			config_.recu_exclude_as?.forEach(v1 => {
+				exclude_as.push(...this.recu_node_list(v1));
+			});
+			exclude_as.forEach(v1 => {
+				this.resume_node(v1);
+			});
+		}
+		this.pause_data.state_b = true;
+	}
+	private static recu_node_list(node_: Node, result_as: Node[] = []): Node[] {
+		if (!node_) {
+			return result_as;
+		}
+		result_as.push(node_);
+		node_.children.forEach(v1 => {
+			result_as.push(v1);
+			this.recu_node_list(v1);
+		});
+		return result_as;
+	}
+	/**恢复游戏 */
+	public static resume(): void {
+		// 恢复定时器
+		director.getScheduler().resumeTargets(this.pause_data.scheduler_as);
+		// 恢复动画
+		this.pause_data.anim_as.forEach(v1 => {
+			if (v1.isPlaying && v1.isPaused) {
+				v1.play();
+			}
+		});
+		// 恢复龙骨动画
+		//this.pause_data.dragon_bones_as.forEach(v1 => {
+		//v1.timeScale = 1;
+		//});
+		this.pause_data.spine_as.forEach(v1 => {
+			v1.timeScale = 1;
+		});
+		// 恢复缓动
+		TweenSystem.instance.ActionManager.resumeTargets(this.pause_data.tween_target_as);
+		// 恢复物理系统
+		{
+			if (this.pause_data.physics_2d_b) {
+				PhysicsSystem2D.instance.enable = this.pause_data.physics_2d_b;
+			}
+			if (this.pause_data.physics_3d_b) {
+				PhysicsSystem.instance.enable = this.pause_data.physics_3d_b;
+			}
+		}
+		this.pause_data.state_b = false;
+	}
+	/**暂停节点
+	* -物理系统需手动启用/禁用
+	*/
+	static pause_node(node_: Node): void;
+	static pause_node(node_as_: Node[]): void;
+	static pause_node(args1_: Node | Node[]): void {
+		let node_as: Node[];
+		if (Array.isArray(args1_)) {
+			node_as = args1_;
+		} else {
+			node_as = [args1_];
+		}
+		node_as.forEach(v1 => {
+			// 暂停定时器
+			director.getScheduler().pauseTarget(v1);
+			// 暂停动画
+			v1.getComponent(Animation)?.pause();
+			//暂停spine动画
+			if (v1.getComponent(sp.Skeleton)) {
+				v1.getComponent(sp.Skeleton).timeScale = 0;
+			}
+			// 暂停龙骨
+			//if (v1.getComponent(dragonBones.ArmatureDisplay)) {
+			//v1.getComponent(dragonBones.ArmatureDisplay).timeScale = 0;
+			//}
+			// 暂停缓动
+			TweenSystem.instance.ActionManager.pauseTarget(v1);
+		});
+	}
+	/**恢复节点 */
+	static resume_node(node_: Node): void;
+	static resume_node(node_as_: Node[]): void;
+	static resume_node(args1_: Node | Node[]): void {
+		let node_as: Node[];
+		if (Array.isArray(args1_)) {
+			node_as = args1_;
+		} else {
+			node_as = [args1_];
+		}
+		node_as.forEach(v1 => {
+			// 恢复定时器
+			director.getScheduler().resumeTarget(v1);
+			// 恢复动画
+			v1.getComponent(Animation)?.resume();
+			//恢复spine动画
+			if (v1.getComponent(sp.Skeleton)) {
+				v1.getComponent(sp.Skeleton).timeScale = 1;
+			}
+			//恢复龙骨
+			//if (v1.getComponent(dragonBones.ArmatureDisplay)) {
+			//v1.getComponent(dragonBones.ArmatureDisplay).timeScale = 1;
+			//}
+			// 恢复缓动
+			TweenSystem.instance.ActionManager.resumeTarget(v1);
+		});
+	}
+	//
+}

+ 9 - 0
llk/assets/core/util/DirectorUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0dd04c52-58cd-46d3-b730-6fd73ced38da",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 46 - 0
llk/assets/core/util/Instance.ts

@@ -0,0 +1,46 @@
+/**单例
+ * 加方法型
+ * public static getInstance(): XXX{
+        return Instance.get(XXX);
+   }
+*/
+
+export default class Instance {
+    public static get<T>(clazz:new (...param: any[]) => T, ...param: any[]): T {
+        if (clazz["__Instance__"] == null) {
+            clazz["__Instance__"] = new clazz(...param);
+        }
+        return clazz["__Instance__"];
+    }
+}
+/**单例
+ * 继承型,静止实例化
+ */
+export class Singleton {
+    // 实例
+    private static _instance: Singleton;
+    // 是否是通过getInstance实例化
+    private static _instantiateByGetInstance: boolean = false;
+    /**
+     * 获取实例
+     */
+    public static getInstance<T extends Singleton>(this: (new () => T) | typeof Singleton): T {
+        const _class = this as typeof Singleton;
+        if (!_class._instance) {
+            _class._instantiateByGetInstance = true;
+            _class._instance = new _class();
+            _class._instantiateByGetInstance = false;
+        }
+        return _class._instance as T;
+    }
+    
+    /**
+     * 构造函数
+     * @protected
+     */
+    protected constructor() {
+        if (!(this.constructor as typeof Singleton)._instantiateByGetInstance) {
+            throw new Error("Singleton class can't be instantiated more than once.");
+        }
+    }
+}

+ 9 - 0
llk/assets/core/util/Instance.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "f298abb1-84e6-4870-98ea-2b09f59063fe",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 158 - 0
llk/assets/core/util/LocalStorageUtil.ts

@@ -0,0 +1,158 @@
+import { sys } from "cc"
+export class LocalStorageUtil {
+    private static _inst:LocalStorageUtil;
+    public static getInstance(): LocalStorageUtil{
+        if(!this._inst)this._inst=new LocalStorageUtil();
+        return this._inst;
+    }
+    //缓存
+    temporary: Map<string, any>;
+  
+    constructor() {
+        this.temporary = new Map<string, any>();
+    }
+    private _user_id:string|null;
+    public setUserId(uk:string):void{
+        this._user_id=uk;
+    }
+    /**
+     * 缓存变量存储区别用户
+     * @param {*} key 
+     * @param {*} value 
+     */
+    set(key: string, value: any): any {
+        if (typeof value === 'object') {
+            try {
+                value = JSON.stringify(value);
+            }
+            catch (e) {
+                console.error(`解析失败,str = ${value}`);
+                return;
+            }
+        }
+        else if (typeof value === 'number') {
+            value = value + "";
+        }
+
+        if (this._user_id)
+            sys.localStorage.setItem(this._user_id + "_" + key, value);
+        else
+            sys.localStorage.setItem(key,value);
+        return value;
+    }
+    /**
+     * 获取缓存变量区别用户
+     * @param {*} key 
+     * @returns 
+     */
+    private get(key: string): string { 
+        if (null == key) {
+            console.error("存储的key不能为空");
+            return null!;
+        }
+        let data;
+        if (this._user_id)
+            data = sys.localStorage.getItem(this._user_id + "_" + key);
+        else
+            data = sys.localStorage.getItem(key);
+        if (!data) return undefined;
+        return data;
+       
+    }
+     /** 获取指定关键字的数值 */
+     getNumber(key: string, defaultValue: number = 0): number {
+        var r = this.get(key);
+        if (r == "0") {
+            return Number(r);
+        }
+        return Number(r) || defaultValue;
+    }
+
+    /** 获取指定关键字的布尔值 */
+    getBoolean(key: string): boolean {
+        var r = this.get(key);
+        return Boolean(r) || false;
+    }
+
+    /** 获取指定关键字的JSON对象 */
+    getJson(key: string, defaultValue?: any): any {
+        var r = this.get(key);
+        return (r && JSON.parse(r)) || defaultValue;
+    }
+    /** 获取指定关键字的JSON对象 */
+    getObject(key: string): any {
+        let data= this.get(key);
+        try {
+            const jsonObj = JSON.parse(data)
+            return jsonObj;
+        } catch (error) {
+            return data;
+        }
+    }
+    /**
+     * 删除缓存变量
+     * @param {*} key 
+     */
+    public removeNormalObject(key: string): any {
+        sys.localStorage.removeItem(key);
+    }
+    /**
+     * 缓存变量存储
+     * @param {*} key 
+     * @param {*} value 
+     */
+    setNormalObject(key: string, value: any): any {
+        sys.localStorage.setItem(key, JSON.stringify(value));
+        return value;
+    }
+
+    /**
+     * 获取缓存变量
+     * @param {*} key 
+     * @returns 
+     */
+    getNormalObject(key: string): string {
+        let data;
+        data = sys.localStorage.getItem(key);
+        if (!data) return undefined;
+        try {
+            const jsonObj = JSON.parse(data)
+            return jsonObj;
+        } catch (error) {
+            return data;
+        }
+    }
+
+    /**
+     * 删除缓存变量
+     * @param {*} key 
+     */
+    removeObject(key: string) {
+        sys.localStorage.removeItem(key);
+    }
+
+    /**
+     * 临时变量存储
+     * @param {*} key 
+     * @param {*} value 
+     */
+    setTempObject(key: string, value: any) {
+        this.temporary.set(key, value);
+    }
+    /**
+     * 获取临时变量
+     * @param {*} key 
+     * @returns 
+     */
+    getTempObject(key: string): any {
+        return this.temporary.get(key);
+    }
+    /**
+     * 删除临时变量
+     * @param {*} key 
+     */
+    removeTempObject(key: string) {
+        this.temporary.delete(key);
+    }
+}
+

+ 9 - 0
llk/assets/core/util/LocalStorageUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "d8e8827e-43d4-4fb5-8ddf-15aa29e19ee2",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 496 - 0
llk/assets/core/util/MathUtil.ts

@@ -0,0 +1,496 @@
+import { misc, Rect, Vec2, Vec3 } from "cc";
+
+
+/** 用于计算的临时工 */
+const tempVec3 = new Vec3();
+/** 用于弧度转角度 */
+const rad2Deg = 180 / Math.PI;
+/** 用于角度转弧度 */
+const deg2Rad = Math.PI / 180;
+export class MathUtil {
+    static readonly zore: Vec2 = new Vec2(1, 0);
+    /**
+    * 用于弧度转角度
+    */
+    public static get rad2Deg() {
+        return rad2Deg;
+    }
+
+    /**
+     * 用于角度转弧度
+     */
+    public static get deg2Rad() {
+        return deg2Rad;
+    }
+    /**
+    * 弧度转角度
+    * @param radians 
+    */
+    public static radiansToDegrees(radians: number) {
+        return radians * rad2Deg;
+    }
+
+    /**
+     * 角度转弧度
+     * @param degree 
+     */
+    public static degreesToRadians(degree: number) {
+        return degree * deg2Rad;
+    }
+
+    /**限制数大小 */
+    static Clamp(value: number, min: number, max: number): number {
+        if (value < min) {
+            value = min;
+        } else if (value > max) {
+            value = max;
+        }
+        return value;
+    }
+    static Clamp01(value: number): number {
+        return this.Clamp(value, 0, 1);
+    }
+
+    static PingPong(t: number, length: number): number {
+        t = this.Repeat(t, length * 2)
+        return length - Math.abs(t - length)
+    }
+
+    static Repeat(t: number, length: number): number {
+        return t - (Math.floor(t / length) * length)
+    }
+    static Round(num: number): number {
+        return Math.floor(num + 0.5)
+    }
+    static Sign(num: number): number {
+        if (num > 0) {
+            num = 1
+        } else if (num < 0) {
+            num = -1
+        } else {
+            num = 0;
+        }
+        return num;
+    }
+    static InverseLerp(from: number, to: number, value: number): number {
+        if (from < to) {
+            if (value < from) return 0;
+            if (value > to) return 1;
+            value = value - from
+            value = value / (to - from)
+            return value;
+        }
+        if (from <= to) return 0;
+        if (value < to) return 1;
+        if (value > from) return 0;
+        return 1 - ((value - to) / (from - to));
+    }
+    static Lerp(from: number, to: number, t: number): number {
+        return from + (to - from) * this.Clamp01(t);
+    }
+    static LerpUnclamped(a: number, b: number, t: number): number {
+        return a + (b - a) * t;
+    }
+    static DeltaAngle(current: number, target: number): number {
+        let num = this.Repeat(target - current, 360);
+        if (num > 180) num = num - 360;
+        return num;
+    }
+    static LerpAngle(a: number, b: number, t: number): number {
+        let num = this.Repeat(b - a, 360)
+        if (num > 180) num = num - 360;
+        return a + num * this.Clamp01(t)
+    }
+    /**
+     * 在最小值和最大值之间进行插值,并在极限处进行平滑处理
+     * @param from 
+     * @param to 
+     * @param t 
+     */
+    public static smoothStep(from: number, to: number, t: number) {
+        t = this.Clamp01(t);
+        t = (-2.0 * t * t * t + 3.0 * t * t);
+        return (to * t + from * (1.0 - t));
+    }
+    /**
+    * 平滑控制
+    * @param current 当前值
+    * @param target 目标值
+    * @param currentVelocity 当前速度
+    * @param smoothTime 平滑时间
+    * @param maxSpeed 最大速度
+    * @param deltaTime 时间增量
+    */
+    public static smoothDamp(current: number, target: number, currentVelocity: number, smoothTime: number, deltaTime: number, maxSpeed: number = Number.POSITIVE_INFINITY) {
+        smoothTime = Math.max(0.0001, smoothTime);
+        const num1 = 2 / smoothTime;
+        const num2 = num1 * deltaTime;
+        const num3 = (1 / (1 + num2 + 0.47999998927116394 * num2 * num2 + 0.23499999940395355 * num2 * num2 * num2));
+        const num4 = current - target;
+        const num5 = target;
+        const max = maxSpeed * smoothTime;
+        const num6 = this.Clamp(num4, -max, max);
+        target = current - num6;
+        const num7 = (currentVelocity + num1 * num6) * deltaTime;
+        let velocity = (currentVelocity - num1 * num7) * num3;
+        let num8 = target + (num6 + num7) * num3;
+        if ((num5 - current > 0) === (num8 > num5)) {
+            num8 = num5;
+            velocity = (num8 - num5) / deltaTime;
+        }
+        return {
+            value: num8,
+            velocity: velocity,
+        };
+    }
+    /**
+     * 垂直于原向量V的单位向量
+     * @param v 一个向量
+     * @returns 
+     */
+    static getPerpendicular(v: Vec2): Vec2 {
+        const perpendicular = new Vec2(-v.y, v.x);  //计算垂直向量
+        const normal = perpendicular.normalize();//归一化
+        return normal;
+    }
+    //角度转向量  
+    static angleToVector(angle: number): Vec2 {
+        return this.zore.clone().rotate(-misc.degreesToRadians(angle));
+    }
+    // 向量转角度
+    static vectorToAngle(dir: Vec2): number {
+        return -misc.radiansToDegrees(dir.signAngle(this.zore));
+    }
+    // 角度转向量   
+    static angle_to_vector(angle: number): Vec2 {
+        // tan = sin / cos
+        // 将传入的角度转为弧度
+        let radian = this.degreesToRadians(angle);
+        // 算出cos,sin和tan
+        let cos = Math.cos(radian);// 邻边 / 斜边
+        let sin = Math.sin(radian);// 对边 / 斜边
+        let tan = sin / cos;// 对边 / 邻边
+        // 结合在一起并归一化
+        let vec = new Vec2(cos, sin).normalize();
+        // 返回向量
+        return (vec);
+    }
+    // !!!!!!!!其实使用Math.atan2求出弧度再转角度一样的效果
+    // 向量转角度
+    static vector_to_angle(dir: Vec2): number {
+        // 将传入的向量归一化 一般已经归一化了
+        //dir.normalize();
+        // 计算出目标角度的弧度
+        let radian = dir.signAngle(this.zore);
+        // 把弧度计算成角度
+        let angle = -this.radiansToDegrees(radian);
+        // 返回角度
+        return (angle);
+    }
+    //向量转弧度
+    static vector_to_radian(dir: Vec2): number {
+        return dir.signAngle(this.zore);
+    }
+    /// <summary>
+    /// 旋转向量,使其方向改变,大小不变
+    /// </summary>
+    /// <param name="v">需要旋转的向量</param>
+    /// <param name="angle">旋转的角度</param>
+    /// <returns>旋转后的向量</returns>
+    static RotationMatrix(x: number, y: number, angle: number): Vec2 {
+        let radian = this.degreesToRadians(angle);
+        let sin = Math.sin(radian);
+        var cos = Math.cos(radian);
+        var newX = x * cos + y * sin;
+        var newY = x * -sin + y * cos;
+        //var newX = x * cos - y * sin;
+        //var newY = x * sin + y * cos;
+        return new Vec2(newX, newY);
+    }
+    static RotationDir(dir: Vec2, angle: number): Vec2 {
+        let radian = this.radiansToDegrees(angle);
+        let sin = Math.sin(radian);
+        var cos = Math.cos(radian);
+        var newX = dir.x * cos + dir.y * sin;
+        var newY = dir.x * -sin + dir.y * cos;
+        dir.x = newX;
+        dir.y = newY;
+        return dir;
+    }
+    //扇形范围
+    public static CheckInView(mPos: Vec2, faceDir: Vec2, tPos: Vec2, dis: number, angle: number): boolean {
+        //let t = tPos.subtract(mPos);
+        //let distance = Vec2.lengthSqr(t);
+        //if(distance<dis*dis)
+        let distance = Vec2.distance(mPos, tPos);
+        if (distance < dis) {
+            if (angle >= 360) return true;
+            let dir = tPos.subtract(mPos).normalize();
+            let ang = this.radiansToDegrees(Vec2.angle(faceDir, dir));
+            if (ang <= angle * 0.5) {
+                return true;
+            }
+        }
+        return false;
+    }
+    //检测点是否在一个凸四边形内
+    public static InConvexQuad(point: Vec2, pointA: Vec2, pointB: Vec2, pointC: Vec2, pointD: Vec2): boolean {
+        let vec1: Vec2, vec2: Vec2;
+        vec1.x = pointB.x - pointA.x;
+        vec1.y = pointB.y - pointA.y;
+        vec2.x = point.x - pointA.x;
+        vec2.y = point.y - pointA.y;
+        if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false;
+        vec1.x = pointC.x - pointB.x;
+        vec1.y = pointC.y - pointB.y;
+        vec2.x = point.x - pointB.x;
+        vec2.y = point.y - pointB.y;
+        if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false;
+        vec1.x = pointD.x - pointC.x;
+        vec1.y = pointD.y - pointC.y;
+        vec2.x = point.x - pointC.x;
+        vec2.y = point.y - pointC.y;
+        if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false;
+        vec1.x = pointA.x - pointD.x;
+        vec1.y = pointA.y - pointD.y;
+        vec2.x = point.x - pointD.x;
+        vec2.y = point.y - pointD.y;
+        if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false;
+        return true;
+    }
+    /**等同于Vec2.cross */
+    public static cross2DPoint(x1: number, y1: number, x2: number, y2: number): number {
+        return x1 * y2 - x2 * y1;
+    }
+    /**检测两个2D包围盒(xy为中心点 wh分别为x轴半径宽 y轴半径宽) 是否相交*/
+    public static AABBAABB2D(pointX1: number, pointY1: number, w1: number, h1: number,
+        pointX2: number, pointY2: number, w2: number, h2: number): boolean {
+        if (Math.abs(pointX1 - pointX2) > (w1 + w2)) return false;
+        if (Math.abs(pointY1 - pointY2) > (h1 + h2)) return false;
+        return true;
+    }
+    /**检测两个3D包围盒 是否相交*/
+    public static AABBAABB3D(pointX1: number, pointY1: number, pointZ1: number, w1: number, h1: number, t1: number,
+        pointX2: number, pointY2: number, pointZ2: number, w2: number, h2: number, t2: number): boolean {
+        if (Math.abs(pointX1 - pointX2) > (w1 + w2)) return false;
+        if (Math.abs(pointY1 - pointY2) > (h1 + h2)) return false;
+        if (Math.abs(pointZ1 - pointZ2) > (t1 + t2)) return false;
+        return true;
+    }
+    /**点与点*/
+    public static pointPoint(point1: Vec2, point2: Vec2): boolean {
+        if (point1.x == point2.x && point1.y == point2.y) {
+            return true;
+        }
+        return false;
+    }
+    //判断点是否在线上,在返回1,不在返回0
+    public static onSegement(Q: Vec2, p1: Vec2, p2: Vec2): boolean {
+        let maxx: number, minx: number, maxy: number, miny: number;
+        maxx = p1.x > p2.x ? p1.x : p2.x;    //矩形的右边长
+        minx = p1.x > p2.x ? p2.x : p1.x;     //矩形的左边长
+        maxy = p1.y > p2.y ? p1.y : p2.y;    //矩形的上边长
+        miny = p1.y > p2.y ? p2.y : p1.y;     //矩形的下边长
+        if (((Q.x - p1.x) * (p2.y - p1.y) == (p2.x - p1.x) * (Q.y - p1.y)) && (Q.x >= minx && Q.x <= maxx) && (Q.y >= miny && Q.y <= maxy)) {
+            return true;
+        }
+        return false;
+    }
+    //点与圆的碰撞
+    //通过计算点到圆心的距离与半径比较判断circle(由中心点和半径构成)
+    public static pointInCircle(point: Vec2, center: Vec2, radius: number): boolean {
+        return Vec2.squaredDistance(point, center) <= radius * radius;
+    }
+    //点与矩形的碰撞
+    //通过判断点坐标是否在矩形四个顶点围成的坐标区域内
+    public static pointInRect(point: Vec2, rect: Rect): boolean {
+        if (point.x >= rect.x
+            && point.x <= rect.x + rect.width
+            && point.y >= rect.y
+            && point.y <= rect.y + rect.height) return true;
+        return false
+    }
+    //线与矩形的碰撞
+    public static lineInRect(p1: Vec2, p2: Vec2, rect: Rect): boolean {
+        let height = p1.y - p2.y;
+        let width = p2.x - p1.x;
+        let c = p1.x * p2.y - p2.x - p1.y;//计算叉乘
+        if ((height * rect.xMin + width * rect.yMin + c >= 0 && height * rect.xMax + width * rect.yMax + c <= 0)
+            || (height * rect.xMin + width * rect.yMin + c <= 0 && height * rect.xMax + width * rect.yMax + c >= 0)
+            || (height * rect.xMin + width * rect.yMax + c >= 0 && height * rect.xMax + width * rect.yMin + c <= 0)
+            || (height * rect.xMin + width * rect.yMax + c <= 0 && height * rect.xMax + width * rect.yMin + c >= 0)
+        ) {
+            if ((p1.x < rect.xMin && p2.x < rect.xMin)
+                || (p1.x > rect.xMax && p2.x > rect.xMax)
+                || (p1.y > rect.yMin && p2.y > rect.yMin)
+                || (p1.y < rect.yMax && p2.y < rect.yMax)
+            ) {
+                return false;
+            } else {
+                return true;
+            }
+        } else {
+            return false
+        }
+    }
+    //两个rect是否相交
+    public static collisionRectWithRect(rect1: Rect, rect2: Rect): boolean {
+        //计算相交部分的矩形
+        //左下角坐标:( lx , ly )    //右上角坐标:( rx , ry )
+        let lx = Math.max(rect1.xMin, rect2.xMin);
+        let ly = Math.max(rect1.yMin, rect2.yMin);
+        let rx = Math.min(rect1.xMax, rect2.xMax);
+        let ry = Math.min(rect1.yMax, rect2.yMax);
+        //判断是否能构成小矩形
+        if (lx > rx || ly > ry) return false; //矩形不相交
+        return true;  //发生碰撞
+    }
+    //圆与矩形是否相交
+    public static collisionRectWithCircle(rect: Rect, p: Vec2, r: number): boolean {
+        //获取矩形信息
+        //左下角坐标:( lx , ly )
+        //右上角坐标:( rx , ry )
+        let lx = rect.xMin;
+        let ly = rect.yMin;
+        let rx = rect.xMax;
+        let ry = rect.yMax;
+        //计算圆心到四个顶点的距离
+        let d1 = Vec2.distance(p, new Vec2(lx, ly));
+        let d2 = Vec2.distance(p, new Vec2(lx, ry));
+        let d3 = Vec2.distance(p, new Vec2(rx, ly));
+        let d4 = Vec2.distance(p, new Vec2(rx, ry));
+        //判断是否碰撞//判断距离是否小于半径
+        if (d1 < r || d2 < r || d3 < r || d4 < r) return true;
+        //是否在圆角矩形的,横向矩形内
+        if (p.x > (lx - r) && p.x < (rx + r) && p.y > ly && p.y < ry) return true;
+        //是否在圆角矩形的,纵向矩形内
+        if (p.x > lx && p.x < rx && p.y > (ly - r) && p.y < (ry + r)) return true;
+        //不发生碰撞
+        return false;
+    }
+    /**
+       * 计算向量在指定平面上的投影
+       * @param vector 被投影的向量
+       * @param planeNormal 平面法线
+       */
+    public static projectOnPlane(vector: Vec3, planeNormal: Vec3) {
+        // 也可以直接用 Vec3 自带的平面投影函数
+        // return Vec3.projectOnPlane(new Vec3, targetDir, planeNormal);
+
+        // 使用点乘计算方向矢量在平面法线上的投影长度
+        const projectionLength = Vec3.dot(vector, planeNormal);
+        // 平面法线与长度相乘得到方向矢量在平面法线上的投影矢量
+        const vectorOnPlane = tempVec3.set(planeNormal).multiplyScalar(projectionLength);
+        // 方向矢量减去其在平面法线上的投影矢量即是其在平面上的投影矢量
+        return Vec3.subtract(new Vec3, vector, vectorOnPlane);
+    }
+
+    /**
+     * 计算两个向量基于指定轴的夹角(逆时针方向为正方向,值范围 -180 ~ 180)
+     * @param a 向量 a
+     * @param b 向量 b
+     * @param axis 参照轴向量(请确保是归一化的)
+     */
+    public static signedAngle(a: Vec3, b: Vec3, axis: Vec3) {
+        // 将向量 a 和 b 分别投影到以 axis 为法线的平面上
+        const aOnAxisPlane = this.projectOnPlane(a, axis);
+        const bOnAxisPlane = this.projectOnPlane(b, axis);
+        // 归一化处理
+        const aNormalized = aOnAxisPlane.normalize();
+        const bNormalized = bOnAxisPlane.normalize();
+        // 求出同时垂直于 a 和 b 的法向量
+        const abNormal = Vec3.cross(new Vec3, aNormalized, bNormalized).normalize();
+        // 将法向量到 axis 上的投影长度
+        // 若投影长度为正值(+1)则表示法向量与 axis 同向(向量叉乘的右手法则)
+        const sign = Vec3.dot(abNormal, axis);
+        // 求出向量 a 和 b 的夹角
+        const radian = Math.acos(Vec3.dot(aNormalized, bNormalized));
+        // 混合在一起!
+        return radian * sign * this.rad2Deg;
+    }
+
+    //获取某点到某点的Y轴方向
+    static GetYDir(from: Vec3, to: Vec3) {
+        let dir = to.subtract(from);
+        dir.y = from.y
+        return dir.normalize();
+    }
+    //向量绕Y轴转一个角度
+    static RotateAroundAxisY(dir: Vec3, angle: number): Vec3 {
+        dir = dir.clone();
+        let rad = this.degreesToRadians(angle);
+        let cos = Math.cos(rad);
+        let sin = Math.sin(rad);
+        dir.x = dir.x * cos + dir.z * sin;
+        dir.z = dir.x * (-sin) + dir.z * cos;
+        return dir;
+    }
+    //将一个向量围绕X轴旋转angle个角度
+    static RotateAroundAxisX(dir: Vec3, angle: number): Vec3 {
+        dir = dir.clone();
+        let rad = this.degreesToRadians(angle);
+        let cos = Math.cos(rad);
+        let sin = Math.sin(rad);
+        dir.y = dir.y * cos - dir.z * sin;
+        dir.z = dir.y * sin + dir.z * cos;
+        return dir;
+    }
+    //将一个向量围绕Z轴旋转angle个角度
+    static RotateAroundAxisZ(dir: Vec3, angle: number): Vec3 {
+        dir = dir.clone();
+        let rad = this.degreesToRadians(angle);
+        let cos = Math.cos(rad);
+        let sin = Math.sin(rad);
+        dir.x = dir.x * cos - dir.y * sin;
+        dir.y = dir.x * sin + dir.y * cos;
+        return dir;
+    }
+    //线段是否与矩形相交
+    static LineRectIntersection(lineStartPoint: Vec2, lineEndPoint: Vec2, rectMinX: number, rectMaxX: number, rectMinY: number, rectMaxY: number): boolean {//针对四种不同的情况,进行碰撞检测
+        let minXLinePoint = lineEndPoint;
+        if (lineStartPoint.x <= lineEndPoint.x) {
+            minXLinePoint = lineStartPoint;
+        }
+        let maxXLinePoint = lineStartPoint;
+        if (lineStartPoint.x <= lineEndPoint.x) {
+            maxXLinePoint = lineEndPoint;
+        }
+        let minYLinePoint = lineEndPoint;
+        if (lineStartPoint.y <= lineEndPoint.y) {
+            minYLinePoint = lineStartPoint;
+        }
+        let maxYLinePoint = lineStartPoint;
+        if (lineStartPoint.y <= lineEndPoint.y) {
+            maxYLinePoint = lineEndPoint;
+        }
+        if (minXLinePoint.x <= rectMinX && rectMinX <= maxXLinePoint.x) {
+            let m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x);
+            let intersectionY = ((rectMinX - minXLinePoint.x) * m) + minXLinePoint.y;
+            if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y && rectMinY <= intersectionY && intersectionY <= rectMaxY) {
+                return true;//new Vec2(rectMinX, intersectionY)
+            }
+        }
+        if (minXLinePoint.x <= rectMaxX && rectMaxX <= maxXLinePoint.x) {
+            let m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x);
+            let intersectionY = ((rectMaxX - minXLinePoint.x) * m) + minXLinePoint.y;
+            if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y && rectMinY <= intersectionY && intersectionY <= rectMaxY) {
+                return true;//new Vec2(rectMaxX, intersectionY)
+            }
+        }
+        if (minYLinePoint.y <= rectMaxY && rectMaxY <= maxYLinePoint.y) {
+            let rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y);
+            let intersectionX = ((rectMaxY - minYLinePoint.y) * rm) + minYLinePoint.x;
+            if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x && rectMinX <= intersectionX && intersectionX <= rectMaxX) {
+                return true;//new Vec2(intersectionX, rectMaxY)
+            }
+        }
+        if (minYLinePoint.y <= rectMinY && rectMinY <= maxYLinePoint.y) {
+            let rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y);
+            let intersectionX = ((rectMinY - minYLinePoint.y) * rm) + minYLinePoint.x;
+            if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x && rectMinX <= intersectionX && intersectionX <= rectMaxX) {
+                return true;//new Vec2(intersectionX, rectMinY)
+            }
+        }
+        return false
+    }
+}

+ 9 - 0
llk/assets/core/util/MathUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "5df23a8d-909e-4972-8e1b-ef66d92c925f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 40 - 0
llk/assets/core/util/PathUtil.ts

@@ -0,0 +1,40 @@
+
+export default class PathUtil {
+    /**
+     * 返回 Path 的扩展名
+     * @param path 路径
+     * @returns {string}
+     */
+    public static extname(path: string): string {
+        var temp = /(\.[^\.\/\?\\]*)(\?.*)?$/.exec(path);
+        return temp ? temp[1] : '';
+    }
+
+    /**
+     * 获取文件路径的文件名。
+     * @param path 路径
+     * @param extname 扩展名
+     * @returns {string}
+     */
+    public static basename(path: string, extname?: string): string {
+        let index = path.indexOf("?");
+        if (index > 0) path = path.substring(0, index);
+        let reg = /(\/|\\)([^\/\\]+)$/g;
+        let result = reg.exec(path.replace(/(\/|\\)$/, ""));
+        if (!result) return path;
+        let baseName = result[2];
+        if (extname && path.substring(path.length - extname.length).toLowerCase() === extname.toLowerCase())
+            return baseName.substring(0, baseName.length - extname.length);
+        return baseName;
+    }
+
+    /**
+     * 获取文件路径的目录名
+     * @param path 路径
+     * @returns {string} 
+     */
+    public static dirname(path: string): string {
+        var temp = /((.*)(\/|\\|\\\\))?(.*?\..*$)?/.exec(path);
+        return temp ? temp[2] : '';
+    }
+}

+ 9 - 0
llk/assets/core/util/PathUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "59900f42-6224-4f49-b3fe-e8c2641562d8",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 189 - 0
llk/assets/core/util/ProjectileMathUtil.ts

@@ -0,0 +1,189 @@
+/** 用于弧度转角度 */
+const rad2Deg = 180 / Math.PI;
+/** 用于角度转弧度 */
+const deg2Rad = Math.PI / 180;
+/**
+ * 抛射运动的数学工具
+ */
+export default class ProjectileMathUtil {
+    /**
+       * 计算耗时
+       * @param x 水平位移
+       * @param angle 初始角度
+       * @param velocity 初始速度
+       */
+    public static calculateTotalTime(x: number, angle: number, velocity: number) {
+        // 初始角度(弧度制)
+        const θ = angle * deg2Rad;
+        // 时间
+        // t = x / ( v * cos(θ) )
+        const t = x / (velocity * Math.cos(θ));
+
+        return t;
+    }
+    /**
+    * 计算指定时刻的运动角度
+    * @param angle 初始角度
+    * @param velocity 初始速度
+    * @param time 时间
+    * @param gravity 重力加速度
+    * @param returnInRadians 是否返回弧度制结果
+    */
+    public static calculateAngleAtMoment(angle: number, velocity: number, time: number, gravity: number, returnInRadians: boolean = false) {
+        // 重力加速度(垂直向下)
+        const g = gravity;//
+        // 初始角度(弧度制)
+        const θ = angle * deg2Rad;
+
+        // 水平瞬时速度
+        // vx = v * cos(θ)
+        const vx = velocity * Math.cos(θ);
+
+        // 垂直瞬时速度
+        // vy = v * sin(θ) - g * t
+        const vy = velocity * Math.sin(θ) - g * time;
+
+        // 该时刻的运动角度(弧度制)
+        const θt = Math.atan(vy / vx);
+
+        return (returnInRadians ? θt : θt * rad2Deg);
+    }
+
+    /**
+     * 计算指定时刻的位移距离
+     * @param angle 初始角度
+     * @param velocity 初始速度
+      * @param gravity 重力加速度
+     * @param time 时间点
+     */
+    public static calculateDisplacementAtMoment(angle: number, velocity: number, gravity: number, time: number) {
+        // 重力加速度(垂直向下)
+        const g = gravity;
+        // 初始角度(弧度制)
+        const θ = angle * deg2Rad;
+
+        // 水平位移
+        // x = v * cos(θ) * t
+        const x = velocity * Math.cos(θ) * time;
+
+        // 垂直位移
+        // y = v * sin(θ) * t - 0.5 * g * t^2
+        const y = velocity * Math.sin(θ) * time - 0.5 * g * Math.pow(time, 2);
+
+        return { x, y };
+    }
+
+    /**
+     * 根据初始角度计算初始速度
+     * @param x 水平距离
+     * @param y 垂直距离
+ * @param gravity 重力加速度
+     * @param angle 初始角度(角度制)
+     */
+    public static calculateWithAngle(x: number, y: number, gravity: number, angle: number) {
+        // 重力加速度(垂直向下)
+        const g = Math.abs(gravity);
+        // 初始角度(弧度制)
+        const θ = angle * deg2Rad;
+
+        // 速度公式
+        // v = sqrt( ( x^2 * g ) / ( 2 * x * sin(θ) * cos(θ) - 2 * y * cos(θ)^2 ) )
+
+        // 部分计算结果
+        const p1 = (2 * x * Math.sin(θ) * Math.cos(θ)) - (2 * y * Math.pow(Math.cos(θ), 2));
+        // 负数没有平方根
+        if (p1 < 0) {
+            return NaN;
+        }
+        // 速度
+        const v = Math.sqrt((g * Math.pow(x, 2)) / p1);
+
+        return v;
+    }
+
+    /**
+     * 根据初始速度计算初始角度
+     * @param x 水平距离
+     * @param y 垂直距离
+ * @param gravity 重力加速度
+     * @param velocity 初始速度
+     */
+    public static calculateWithVelocity(x: number, y: number, velocity: number, gravity: number) {
+        // 重力加速度(垂直向下)
+        const g = gravity;
+        // 初始速度
+        const v = velocity;
+
+        // 角度公式
+        // θ = atan( ( -v^2 ± sqrt( v^4 - g * ( g * x^2 + 2 * y * v^2 ) ) / ( -g * x ) ) )
+
+        // 部分计算结果
+        const p1 = Math.pow(v, 2);
+        const p2 = Math.pow(v, 4) - g * (g * Math.pow(x, 2) + 2 * y * p1);
+        // 负数没有平方根
+        if (p2 < 0) {
+            return {
+                angle1: NaN,
+                angle2: NaN,
+            };
+        }
+        // 部分计算结果
+        const p3 = Math.sqrt(p2);
+        // 角度(两个解)
+        const θ1 = Math.atan((-p1 + p3) / (-g * x));
+        const θ2 = Math.atan((-p1 - p3) / (-g * x));
+
+        return {
+            angle1: θ1 * rad2Deg,
+            angle2: θ2 * rad2Deg,
+        };
+    }
+
+    /**
+     * 根据最大高度计算速度和角度
+     * @param x 水平距离
+     * @param y 垂直距离
+     * @param gravity 重力加速度
+     * @param maxHeight 最大高度
+     */
+    public static calculateWithMaxHeight(x: number, y: number, maxHeight: number, gravity: number) {
+        // 重力加速度(垂直向下)
+        const g = gravity;
+        // 最大高度
+        const h = maxHeight;
+
+        // 最大高度不能小于 0,也不能够小于垂直距离
+        if (h < 0 || (h - y) < 0) {
+            return {
+                angle: NaN,
+                velocity: NaN,
+                time: NaN,
+            };
+        }
+
+        // 部分计算结果
+        const p1 = Math.sqrt(2 * g * h);
+        const p2 = Math.sqrt(2 * g * (h - y));
+
+        // 时间公式
+        // t = ( -sqrt( 2 * g * h ) ± sqrt( 2 * g * ( h - y ) ) ) / -g
+        const t1 = (-p1 + p2) / -g;
+        const t2 = (-p1 - p2) / -g;
+        // 始终使用较大的解
+        const t = Math.max(t1, t2);
+
+        // 角度公式
+        // θ = atan( ( sqrt( 2 * g * h ) * t ) / x )
+        const θ = Math.atan(p1 * t / x);
+
+        // 速度公式
+        // v = sqrt( 2 * g * h ) / sin(θ)
+        const v = p1 / Math.sin(θ);
+
+        return {
+            angle: θ * rad2Deg,
+            velocity: v,
+            time: t,
+        };
+    }
+}

+ 1 - 0
llk/assets/core/util/ProjectileMathUtil.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"16ac3b52-38ce-45c2-965e-f0214a854d86","files":[],"subMetas":{},"userData":{}}

+ 190 - 0
llk/assets/core/util/ResUtil.ts

@@ -0,0 +1,190 @@
+/**
+ *
+ * @file ResUtil.ts
+ * @author
+ * @description Cocos方法整合,如果Cocos版本升级,造成API修改,仅需修改此处
+ */
+import { Asset, assetManager, AssetManager, director, ImageAsset, resources, SceneAsset, Texture2D } from "cc";
+//export type Constructor<T extends Asset> = new () => T;
+export const headImgExt = ".head";
+export module ResUtil {
+    /**
+    * @description 原生加载资源
+    * @param object {url: 远程地址 option: 参数类型}
+    * @returns 
+    */
+    export function loadRemote<T extends Asset>(url:string, option?: any):Promise<T>{
+        return new Promise((resolve, reject) => {
+            assetManager.loadRemote<T>(url, option, (err: Error | null, asset: T) => {
+                resolve && resolve(err ? null : asset);
+            });
+        });
+    }
+    /**
+     * 加载bundle
+     * @param bundleName 
+     * @returns 
+     */
+    export function loadBundle(bundleName: string):Promise<AssetManager.Bundle> {
+        return new Promise((resolve, reject) => {
+            assetManager.loadBundle(bundleName, (err, bundle) => {
+                resolve && resolve(err ? null : bundle);
+            });
+        });
+    }
+    /**获取已加载的bundle*/
+    export function getBundle(bundleName?: string):AssetManager.Bundle|null{
+        if (null == bundleName || '' === bundleName) {
+            return resources;
+        } else {
+            return assetManager.getBundle(bundleName)!;
+        }
+    }
+    /**
+     * 加载资源
+     * @param  path  资源名
+     * @param  bundle 所属包名或 包
+     * @type 资源类型
+     * @returns 
+    */
+    export async function loadAsset<T extends Asset>(path: string, bundle?: string | AssetManager.Bundle):Promise<T>{
+        let bd:AssetManager.Bundle;
+        if(!bundle) bundle='';
+        if(typeof bundle === 'string'){
+           bd = getBundle(bundle);
+           if(!bd) bd = await loadBundle(bundle);
+        } else if (bundle instanceof AssetManager.Bundle) {  
+           bd = bundle;
+        }
+        const asset = bd.get<T>(path);
+        if (null != asset) {
+            return Promise.resolve(asset);
+        }
+        return new Promise((resolve, reject) => {
+            bd.load<T>(path,(err, asset: T) => {
+                resolve(err ? null : asset);
+            });
+        });
+    }
+    export async function loadDir<T extends Asset>(path: string, bundle?: string | AssetManager.Bundle,progressCallback?: (completed: number, total: number) => void):Promise<T[]>{
+        let bd:AssetManager.Bundle;
+        if(!bundle) bundle='';
+        if(typeof bundle === 'string'){
+           bd = getBundle(bundle);
+           if(!bd) bd = await loadBundle(bundle);
+        } else if (bundle instanceof AssetManager.Bundle) {  
+           bd = bundle;
+        }
+        return new Promise((resolve, reject) => {
+            bd.loadDir<T>(path,(finished:number,total:number,item)=>{
+                progressCallback?.(finished,total);
+            }, (err, assets:T[]) => {    
+                resolve(err ? null : assets);
+            });
+        });
+    }
+    export async function loadAssets<T extends Asset>(list: string[], bundle?: string | AssetManager.Bundle,progressCallback?: (completed: number, total: number) => void):Promise<T[]>{
+        let bd:AssetManager.Bundle;
+        if(!bundle) bundle='';
+        if(typeof bundle === 'string'){
+           bd = getBundle(bundle);
+           if(!bd) bd = await loadBundle(bundle);
+        } else if (bundle instanceof AssetManager.Bundle) {  
+           bd = bundle;
+        }
+        return new Promise((resolve, reject) => {
+            bd.load<T>(list,(finished: number, total: number, item:any) => {
+                progressCallback?.(finished,total);
+            }, (err: Error | null, data) => {
+                resolve(err ? null : data);
+            });
+        });
+    }
+
+    export async function loadScene(scene_name:string, bundle?: string | AssetManager.Bundle,show:Boolean=false):Promise<SceneAsset>{
+        let bd:AssetManager.Bundle;
+        if(!bundle) bundle='';
+        if(typeof bundle === 'string'){
+           bd = getBundle(bundle);
+           if(!bd) bd = await loadBundle(bundle);
+        } else if (bundle instanceof AssetManager.Bundle) {  
+           bd = bundle;
+        }
+        return new Promise((resolve, reject) => {
+            bd.loadScene(scene_name, (err, asset:SceneAsset) => {    
+                resolve(err ? null : asset);
+                if(show)director.loadScene(scene_name);
+            });
+        });
+    }
+    export function releaseAll():void{
+        assetManager.releaseAll();
+    }
+    export function releaseAsset(asset:Asset){
+        assetManager.releaseAsset(asset);
+    }
+    export function release(path:string|null,bundle?: string | AssetManager.Bundle){
+        let bd:AssetManager.Bundle;
+        if(!bundle) bundle='';
+        if(typeof bundle === 'string'){
+           bd = getBundle(bundle);
+        } else if (bundle instanceof AssetManager.Bundle) {  
+           bd = bundle;
+        }
+        if(path && path!=''){
+            bd?.release(path); 
+        }else{
+            bd?.releaseAll();
+        }
+    }
+    /**
+    * 自定义头像加载流程
+    * 加载头像使用 ResUtil.loadRemote({url, option:{ext:headImgExt}})
+    */
+    export function registerHeadImgLoader() {
+        assetManager.downloader.register(headImgExt, (content, options, onComplete) => {
+            onComplete(null, content);
+        });
+        assetManager.parser.register(headImgExt, downloadDomImage);
+        assetManager.factory.register(headImgExt, createTexture);
+    }
+    function createTexture(id: string, data: any, options: any, onComplete: Function) {
+        let out: Texture2D | null = null;
+        let err: Error | null = null;
+        try {
+            out = new Texture2D();
+            const imageAsset = new ImageAsset(data);
+            out.image = imageAsset;
+        } catch (e) {
+            err = e as any as Error;
+        }
+        onComplete && onComplete(err, out);
+    }
+
+    function downloadDomImage(url: string, options: any, onComplete: Function) {
+        const img = new Image();
+        if (window.location.protocol !== 'file:') {
+            img.crossOrigin = 'anonymous';
+        }
+        function loadCallback() {
+            img.removeEventListener('load', loadCallback);
+            img.removeEventListener('error', errorCallback);
+            if (onComplete) {
+                onComplete(null, img);
+            }
+        }
+
+        function errorCallback() {
+            img.removeEventListener('load', loadCallback);
+            img.removeEventListener('error', errorCallback);
+            if (onComplete) {
+                onComplete(new Error(url));
+            }
+        }
+
+        img.addEventListener('load', loadCallback);
+        img.addEventListener('error', errorCallback);
+        img.src = url;
+        return img;
+    }
+}

+ 9 - 0
llk/assets/core/util/ResUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "fdca46ee-3efc-42b6-a776-d0c4afa42ed3",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 99 - 0
llk/assets/core/util/StringUtil.ts

@@ -0,0 +1,99 @@
+export class StringUtil {
+    
+    /**小数转成保留几位小数的字符串*/
+    public static ToFixed(s:number,count:number):string{
+        return s.toFixed(count).toString();
+    }
+    
+    /**秒转成 00:00时间格式*/
+    public static ToTimeString(s:number):string{
+         s = Math.floor(s);
+         let m = Math.floor(s/60);
+         s=s-m*60;  
+         let mm=m.toString();
+         if(mm.length<=1){
+            mm = "0".concat(mm);
+         }
+         if(s<10){
+            return  `${mm}:${"0".concat(s.toString())}`;
+         }
+         return  `${mm}:${s}`;
+    }
+    /**秒*/
+    public static ToSce(s:number,u:string="s"):string{
+        s = Math.floor(s);
+        return  `${s}${u}`;
+   }
+    /**货币值转字符 */
+    public static ToMoneyString(n:number):string{
+        if(n>1000000){
+            let m=Math.floor(n*0.000001);
+            n-=m*1000000;
+            if(n>1000){
+               return `${m}M${Math.floor(n*0.001)}K`;
+            }
+            return `${m}M`;
+        }else if(n>1000){
+            let m=Math.floor(n*0.001);
+            n-=m*1000;
+            if(n>100){
+               n=Math.floor(n*0.01);
+               return `${m}K${n}`; 
+            }
+            return `${m}K`; 
+        }
+        return n.toString();
+    }
+    /**毫秒转秒显示*/
+    public static MS2S(ms:number):string{
+        return (ms*0.001).toFixed(1);
+    }
+    /**
+     * 去掉前后空格
+     * @param str
+     * @returns {string}
+     */
+    public trimSpace(str: string): string {
+        return str.replace(/^\s*(.*?)[\s\n]*$/g, '$1');
+    }
+
+    /**
+     * 获取字符串长度,中文为2
+     * @param str
+     */
+    public getStringLength(str: string): number {
+        var strArr = str.split("");
+        var length = 0;
+        for (var i = 0; i < strArr.length; i++) {
+            var s = strArr[i];
+            if (this.isChinese(s)) {
+                length += 2;
+            } else {
+                length += 1;
+            }
+        }
+        return length;
+    }
+
+    /**
+     * 判断一个字符串是否包含中文
+     * @param str
+     * @returns {boolean}
+     */
+    public isChinese(str: string): boolean {
+        var reg = /^.*[\u4E00-\u9FA5]+.*$/;
+        return reg.test(str);
+    }
+
+    /**
+     * 格式化字符串 "{0},{1}.format("text0","text1")
+     */
+    public format(val: string, ...param: any[]): string {
+        for (let i = 0, len = param.length; i < len; i++) {
+            let reg = new RegExp("({)" + i + "(})", "g");
+            val = val.replace(reg, param[i]);
+        }
+        return val;
+    }
+}
+

+ 9 - 0
llk/assets/core/util/StringUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "77722058-8f23-4726-9278-0ee300084ad9",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 23 - 0
llk/assets/core/util/TableLoadUtil.ts

@@ -0,0 +1,23 @@
+import { _decorator, assetManager,JsonAsset} from 'cc';
+export default class TableLoadUtil {
+    public static  preloadAll(bundleName:string,path:string,callback: Function,util_cb:(string,any)=>void): void {
+        assetManager.loadBundle(bundleName, (err, bundle) => {
+                if (err) {
+                    console.log(err);
+                    return;
+                }
+                bundle.loadDir(path,JsonAsset, function (err, assets:JsonAsset[]) {
+                    if (err) {
+                        console.log(err);
+                        return;
+                    }
+                    for (let i = 0; i < assets.length; i++) {
+                        util_cb?.(assets[i].name,assets[i].json);
+                    }
+                    callback(assets);
+                }.bind(this));
+        });
+    }
+
+}
+

+ 9 - 0
llk/assets/core/util/TableLoadUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0d2ab41a-5985-4b3e-9b57-88553af5262f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 53 - 0
llk/assets/core/util/UrlUtil.ts

@@ -0,0 +1,53 @@
+export default class UrlUtil {
+    /**
+     * 获取URL参数(字符串)
+     * @param url 地址
+     * @returns {string}
+     */
+    private static getParamString(url?: string): string {
+        url = url || window.location?.href;
+        if (url != void 0) {
+            let index = url.indexOf('?');
+            if (index != -1) {
+                return url.substring(index + 1);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 获取URL参数
+     * @param url 地址
+     * @returns {JSON}
+     */
+    public static getParam(url?: string): { [key: string]: string } {
+        let param = {};
+        let paramString = this.getParamString(url);
+        if (paramString) {
+            paramString.split("&").forEach((value: string) => {
+                let values = value.split("=");
+                if (values.length == 2) {
+                    param[values[0]] = values[1];
+                }
+            });
+        }
+        return param;
+    }
+
+    /**
+     * 根据key获取URL参数
+     * @param key key
+     * @param url 地址
+     * @returns {string}
+     */
+    public static getParamValue(key: string, url?: string): string {
+        let paramString = this.getParamString(url);
+        if (paramString) {
+            let values = paramString.match(`(^|&)${key}=([^&]*)(&|$)`);
+            if (values) {
+                return values[2];
+            }
+        }
+        return null;
+    }
+}

+ 9 - 0
llk/assets/core/util/UrlUtil.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "27d0798c-6b21-4105-9ac1-9dcc54c92213",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 261 - 0
llk/assets/core/util/Utils.ts

@@ -0,0 +1,261 @@
+export class Utils {
+     
+    /**
+     * 随机数 (包含min,不包含max)
+     * @param min
+     * @param max
+     * @param isInt
+     * @return {*}
+     */
+    public static getRandom(min: number = 0, max: number = 1, isInt: boolean = false): number {
+        if (min == null) min = 0;
+        if (max == null) max = 1;
+        if (isInt == null) isInt = false;
+        if (min === max) return min;
+        let value = min + (Math.random() * (max - min));
+        if (isInt) {
+            value = Math.floor(value);
+        }
+        return value;
+    }
+    /**
+     * 随机整数-包含最大值,最小值
+     * @param min
+     * @param max
+     * @return {*}
+     */
+    public static getRandomIntInclusive(min: number, max: number) {
+        min = Math.ceil(min);
+        max = Math.floor(max);
+        if (min == max)
+            return min;
+        return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
+    }
+    /**
+     * 随机整数-不包含最大值,最小值
+     * @param min
+     * @param max
+     * @return {*}
+     */
+    public static getRandomInt(min: number, max: number):number {
+        min = Math.ceil(min);  max = Math.ceil(max);
+        return Math.floor(Math.random() * (max - min)) + min;
+    }
+    /** */
+    public static getRandomNumberInRange(min:number, max:number):number {
+        return Math.random() * (max - min) + min;
+    }
+    /**生成随机整数 -1 或 1*/
+    public static getRandomDir():number {
+       return Math.floor(Math.random() * 2) === 0 ? -1 : 1;//
+    }
+    /**
+     * 在指定数组中随机取出N个不重复的数据
+     * @param resArr
+     * @param ranNum
+     * @returns {Array}
+     */
+    public static getRandomDiffValueFromArr<T>(resArr: Array<T>, ranNum: number): Array<T> {
+        let arr = new Array<T>();
+        let result = new Array<T>();
+        if (!resArr || resArr.length <= 0 || ranNum <= 0) {
+            return result;
+        }
+        for (let i = 0; i < resArr.length; i++) {
+            arr.push(resArr[i]);
+        }
+        if (ranNum >= arr.length) {
+            return arr;
+        }
+        ranNum = Math.min(ranNum, arr.length - 1);
+        for (let i = 0; i < ranNum; i++) {
+            let ran = Utils.getRandomIntInclusive(0, arr.length - 1);// Math.floor(Math.random() * arr.length);
+            result.push(arr.splice(ran, 1)[0]);
+        }
+        return result;
+    }
+    /**
+     * 随机一个字符串
+     * @param length 
+     * @returns 
+     */
+    public static randomStr(length: number): string {
+        let result: string = '';
+        const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+        const charactersLength = characters.length;
+        for (let i = 0; i < length; i++) {
+            result += characters.charAt(Math.floor(Math.random() * charactersLength));
+        }
+        return result;
+    }
+    /**
+     * 时间转换 毫秒=》2019-03-28 19:55:34
+     * @param milliseconds
+     * @return {string}
+     */
+    public static time2str (milliseconds: number): string {
+        let time = new Date();
+        time.setTime(milliseconds);
+        let year = time.getFullYear();
+        let hours = time.getHours();
+        let min = time.getMinutes();
+        let sec = time.getSeconds();
+        let month = time.getMonth()+1;
+        let date = time.getDate();
+        return year  +"-"+ month + "-" + date + " " + (hours > 9 ? hours : "0" + hours) + ":" + (min > 9 ? min : "0" + min) + ":" + (sec > 9 ? sec : "0" + sec);
+    }
+
+    /**
+     * 将毫秒转换为时间格式 00:00:00
+     * @param delay
+     * @param detail
+     * @return
+     */
+    public static second2time (delay: number, detail: number = 2): string {
+        // if (detail == undefined) detail = 2;
+        //天
+        let d = Math.floor(delay / 86400000);
+        delay = delay % 86400000;
+        // 小时
+        let h = Math.floor(delay / 3600000);
+        delay = delay % 3600000;
+        // 分钟
+        let m = Math.floor(delay / 60000);
+        delay = delay % 60000;
+        // 秒
+        let s = Math.floor(delay / 1000);
+        delay = delay % 1000;
+        // 毫秒
+        let ml = delay;
+
+        let result: string = "";
+        if (d > 0 && detail > 0) {
+            result += (d > 9 ? d : "0" + d) + ":";// "天";
+            detail--;
+        }
+        if (h > 0 && detail > 0) {
+            result += (h > 9 ? h : "0" + h) + ":";// "小时";
+            detail--;
+        }
+        if (m > 0 && detail > 0) {
+            result += (m > 9 ? m : "0" + m) + ":";// "分钟";
+            detail--;
+        }
+        if (detail > 0) {
+            result += (s > 9 ? s : "0" + s);// "秒";
+            detail--;
+        }
+        if (ml > 0 && detail > 0) {
+            detail--;
+        }
+
+        return result;
+    }
+
+    public static  formatMilliseconds(milliseconds: number): string {
+        const seconds = Math.floor((milliseconds / 1000) % 60);
+        const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
+        const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
+        //const formattedSeconds = seconds < 10 ? "0" + seconds : seconds;
+        //const formattedMinutes = minutes < 10 ? "0" + minutes : minutes;
+        //const formattedHours = hours < 10 ? "0" + hours : hours;
+        //`${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
+        return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2,"0")}:${seconds.toString().padStart(2,"0")}`;
+    }
+    
+     
+    /**
+     * 取小数位
+     * @param decimal 小数
+     * @param places 位数
+     * @return {number}
+     */
+    public static getDecimal (decimal: number, places: number): number {
+        let round: number = Math.pow(10, places);
+        return Math.round(decimal * round) / round;
+    }
+
+    /**
+     * 本年第几周
+     * @param timestamp 毫秒 
+     * @returns 
+     */
+    public static getYearWeek(timestamp: number): number {
+        // let nowDate: Date = new Date(date);
+        // let firstDay: Date = new Date(date);
+        // firstDay.setMonth(0);//设置1月
+        // firstDay.setDate(1);//设置1号
+        // let diffDays = Math.ceil((nowDate.valueOf() - firstDay.valueOf())/(24*60*60*1000));
+        // return Math.ceil((diffDays + (firstDay.getDay() + 1 - 1)) / 7);
+
+        let currentDate: Date = new Date(timestamp);
+        let year: Date =  new Date(currentDate.getFullYear(), 0, 1);
+        let days: number =  Math.floor((currentDate.valueOf() - year.valueOf()) / (24 * 60 * 60 * 1000));
+        let week: number = Math.ceil(( currentDate.getDay() + 1 + days) / 7);
+        return week;
+    }
+
+    /**
+     * 是否是同一天
+     * @param timeStampA
+     * @param timeStampB
+     * @return {boolean}
+     */
+    public static isSameDay(timeStampA: number, timeStampB: number): boolean {
+        let dateA = new Date(timeStampA);
+        dateA.setHours(0, 0, 0, 0);
+        let dateB = new Date(timeStampB);
+        dateB.setHours(0, 0, 0, 0);
+        return (dateA.getTime() == dateB.getTime()) ? true : false;
+    }
+    //
+    public static checkAndAdd<t1>(map:Map<t1,number>,k:t1,v:number){
+           if(map.has(k)){
+                 map.set(k,map.get(k)+v);
+           }else{
+                 map.set(k,v);
+           }
+    }
+    //
+    /**
+     * 随机权重下标
+     * @param weights 权重数组
+     */
+    static randomWeights(weights: number[]) {
+        // 总权重值
+        const totalWeight = weights.reduce((a, b) => a + b, 0);
+        // 随机的权重值
+        const randomWeight = this.getRandom(0, totalWeight);
+
+        for (let index = 0, weight = 0; index < weights.length; index++) {
+            weight += weights[index];
+            if (weight >= randomWeight) {
+                return index;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * 随机数组下标
+     * @param array 数组
+     */
+    static randomArray<T>(array: T[]): number;
+    /**
+     * 根据权重随机数组下标
+     * @param array    数组
+     * @param weights  权重数组
+     */
+    static randomArray<T>(array: T[], weights: number[]): number;
+    static randomArray(array: any[], weights?: number[]) {
+        if (!weights) {
+            const index = this.getRandom(0, array.length,true);
+            return index;
+        }
+
+        return this.randomWeights(weights.slice(0, array.length));
+    }
+
+    
+}

+ 9 - 0
llk/assets/core/util/Utils.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "7ea66af4-000a-4815-b8ee-4c58d72485a1",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
llk/assets/core/util_class.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "2da3b1c5-df08-4785-86e8-e350736d3f48",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 95 - 0
llk/assets/core/util_class/Action.ts

@@ -0,0 +1,95 @@
+type Action = { check: () => boolean, entry: () => void, do: (dt: number) => void, dispose: () => void };
+export type ActionCreat = (...args: any) => Action;
+export default function get_new_action_center(): ActionCenter { return new ActionCenter(); }
+/**行为机*/
+class ActionCenter {
+   private _list: Action[] = [];
+   private _current: Action;
+   /**加入一个创建行为的闭包的函数
+    * ActionCreat = (...args: any) => Action;    
+    * Action ={check:() => boolean,entry:() => void,do:(dt:number)=>void}
+    */
+   public add(actionCreat: ActionCreat, ...args: any): void {
+      this._list.push(actionCreat(...args));
+   }
+   /**检测和进入行为*/
+   public check_entry(): void {
+      if (this._current?.check()) return;
+      for (let i = 0; i < this._list.length; i++) {
+         if (this._current != this._list[i] && this._list[i].check()) {
+            this._current = this._list[i];
+            this._current.entry();
+            return;
+         }
+      }
+      this._current = null;
+   }
+   /**是否有正在执行的行为 */
+   public get isAction(): boolean {
+      return this._current != null;
+   }
+   /**执行行为*/
+   public do(dt: number): void {
+      if (this._current) this._current.do(dt);
+   }
+   /**清除所有行为*/
+   public clean(): void {
+      for (let i = 0; i < this._list.length; i++) {
+         this._list[i].dispose();
+      }
+      this._list.length = 0;
+      this._current = null;
+   }
+   /*例子
+   public test():void{
+     const who={state:0};
+     this.add((who:{state:number})=>{
+          const w = who;
+          let time:number=0;
+          return{
+             check:()=>{
+                 if(w.state==0){
+                   return true;
+                 }
+                 return false;
+             },
+             entry:()=>{
+                time=0;
+                console.log("entry 行为1",time);
+             },
+             do:(dt:number)=>{
+                 time+=dt;
+                 if(time>=2){
+                    console.log("do",time);
+                    w.state=1;
+                 };
+             }
+         }
+     },who);
+     this.add((who:{state:number})=>{
+      const w = who;
+      let time:number=0;
+      return{
+         check:()=>{
+             if(w.state==1){
+               return true;
+             }
+             return false;
+         },
+         entry:()=>{
+            time=0;
+            console.log("entry 行为2",time);
+         },
+         do:(dt:number)=>{
+             time+=dt;
+             if(time>=2){
+                console.log("do",time);
+                w.state=0;
+             };
+         }
+      }
+     },who);
+   }*/
+}
+
+

+ 9 - 0
llk/assets/core/util_class/Action.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "24a1f6e4-bd6f-4040-9f9c-7397f678c9e5",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 75 - 0
llk/assets/core/util_class/Container.ts

@@ -0,0 +1,75 @@
+export class Container {
+    protected _itemList: Array<{ type: number, count: number }> = null;
+    constructor() {
+        this._itemList = new Array<{ type: number, count: number }>();
+    }
+    public addCount(type: number, count: number): { type: number, count: number } {
+        return this.addItem({ type: type, count: count });
+    }
+    public useCount(type: number, count: number): { type: number, count: number } {
+        for (let i = 0; i < this._itemList.length; i++) {
+            if (this._itemList[i].type == type) {
+                if (this._itemList[i].count >= count) {
+                    this._itemList[i].count -= count;
+                    return this._itemList[i];
+                }
+            }
+        }
+        return null;
+    }
+    /**
+     * 添加物品
+     * @param item
+     * @returns
+     */
+    public addItem(item: { type: number, count: number }): { type: number, count: number } {
+        for (let i = 0; i < this._itemList.length; i++) {
+            if (this._itemList[i].type == item.type) {
+                this._itemList[i].count += item.count;
+                return this._itemList[i];
+            }
+        }
+        this._itemList.push(item);
+        return item;
+    }
+    public getCount(type: number): number {
+        for (let i = 0; i < this._itemList.length; i++) {
+            if (this._itemList[i].type == type) {
+                return this._itemList[i].count;
+            }
+        }
+        return 0;
+    }
+    get itemList(): Array<{ type: number, count: number }> {
+        return this._itemList;
+    }
+    set itemList(arr: Array<{ type: number, count: number }>) {
+        this._itemList = arr;
+    }
+    /**
+    * 序列化(数据库存储)
+    */
+    public serialize(): any {
+        let list = [];
+        for (let i: number = 0; i < this._itemList.length; i++) {
+            let item = this._itemList[i];
+            list.push({ type: item.type, count: item.count });
+        }
+        let data = {
+            "list": list
+        };
+        return data;
+    }
+    /**
+     * 反序列化(数据库读取)
+     * @param data
+     */
+    public unserialize(data: any): void {
+        if (!data) return;
+        let list = data.list;
+        this._itemList = [];
+        for (let i: number = 0; i < list.length; i++) {
+            this._itemList.push({ type: list[i].type, count: list[i].count });
+        }
+    }
+}

+ 9 - 0
llk/assets/core/util_class/Container.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "7b36736c-e438-4809-a64b-99cdb2a1b59d",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 48 - 0
llk/assets/core/util_class/Delay.ts

@@ -0,0 +1,48 @@
+class Delay {
+    private delays: { duration: number; elapsed: number; resolve: (() => void) | null; }[] = [];
+    private paused: boolean = false;
+    // 创建一个新的延迟  
+    public start(seconds: number): Promise<void> {
+        const duration = seconds;
+        return new Promise((resolve) => {
+            this.delays.push({
+                duration: duration,
+                elapsed: 0,
+                resolve: resolve
+            });
+        });
+    }
+    public pause(): void {
+        this.paused = true;
+    }
+    public resume(): void {
+        this.paused = false;
+    }
+    //更新所有延迟的状态,传入更新时间间隔(秒)  
+    public update(deltaTime: number): void {
+        if (this.paused) return;
+        for (let i = this.delays.length - 1; i >= 0; i--) {
+            const delay = this.delays[i];
+            delay.elapsed += deltaTime; // 累加已过时间  
+            if (delay.elapsed >= delay.duration) {
+                if (delay.resolve) {
+                    delay.resolve(); // 解析 Promise  
+                    delay.resolve = null; // 清空引用  
+                }
+                this.delays.splice(i, 1); // 从数组中移除已完成的延迟对象  
+            }
+        }
+    }
+    //取消所有延迟  
+    public cancelAll(): void {
+        this.delays.forEach(delay => { delay.resolve = null; });
+        this.delays = []; //清空所有延迟  
+    }
+    //检查是否有活动的延迟  
+    public isActive(): boolean {
+        return this.delays.length > 0;
+    }
+}
+export default function get_new_delay(): Delay {
+    return new Delay();
+}

+ 9 - 0
llk/assets/core/util_class/Delay.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "8e7b9582-bab2-4fbb-87e3-357f4f65b458",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 44 - 0
llk/assets/core/util_class/FMS.ts

@@ -0,0 +1,44 @@
+/**注册一个继承IFMS的状态类型 */
+export function FMSRegister(name: string) {
+    return function (target:any) {
+           const instance = new target() as IFMS;
+           instance.type=name;
+           FMS.IFMS.set(name, instance);
+    }
+}
+/**状态接口*/
+export interface IFMS{
+    type: string;
+    onEntry: () => void;
+    onExit: () => void;
+}
+/**全局状态机,游戏流程 (私有有行为机更灵活)*/
+class FMS{
+    static IFMS: Map<string,IFMS> = new Map();
+    private _current:IFMS;
+    /**当前状态*/
+    get Current():IFMS{return this._current};
+    get CurrentTaype():string{return this._current ? null:this._current.type};
+    /**初始1个状态*/
+    public Init(type:string):void{
+        this.Change(type);
+    }
+    /**切换状态*/
+    public Change(type:string):void{
+        if(this._current)this._current.onExit();
+        this._current = FMS.IFMS.get(type);
+        if(this._current)this._current.onEntry();
+    }
+}
+/**例子:
+@FMSRegister("Init")
+export class FMSInit implements IFMS{
+    type: string;
+    onExit: () => void;
+    onEntry():void{
+         console.log(this.type,"---------------------");
+    }
+}
+*/
+export default function get_new_fms():FMS { return new FMS;}
+

+ 9 - 0
llk/assets/core/util_class/FMS.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "bbc2506c-b74f-4f9b-b444-286abe5a7d89",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 230 - 0
llk/assets/core/util_class/GameData.ts

@@ -0,0 +1,230 @@
+import { ch } from "../../ch/ch";
+import PeriodData, { PeriodDataUpdate } from "./PeriodData";
+export interface data_interface {
+   serialize(): any;
+   unserialize(data: any): void;
+}
+/**游戏数据
+ * 有额外自定义数据可以继承此类
+*/
+export default class GameData<T extends string | number | symbol, DT extends string | number | symbol, WT extends string | number | symbol, MT extends string | number | symbol> {
+   private _key: string;
+   private _uid: string;
+   private _gid: string;
+   private _save_time: number;
+   private _ver: string;
+   private _data: PeriodData<T>;
+   private _day_data: PeriodData<DT>;
+   private _week_data: PeriodData<WT>;
+   private _month_data: PeriodData<MT>;
+   constructor(gid: string, uid: string, name: string,
+      limt?: Map<T, { min?: number | null, max?: number | null }>,
+      day_limt?: Map<DT, { min?: number | null, max?: number | null }>,
+      week_limt?: Map<WT, { min?: number | null, max?: number | null }>,
+      month_limt?: Map<MT, { min?: number | null, max?: number | null }>,
+   ) {
+      this._key = name;
+      this._gid = gid;
+      this._uid = uid;
+      this._data = new PeriodData<T>(PeriodDataUpdate.none, limt);
+      this._day_data = new PeriodData<DT>(PeriodDataUpdate.day, day_limt);
+      this._week_data = new PeriodData<WT>(PeriodDataUpdate.week, week_limt);
+      this._month_data = new PeriodData<MT>(PeriodDataUpdate.month, month_limt);
+   }
+   //
+   public get data(): PeriodData<T> { return this._data };
+   public get day_data(): PeriodData<DT> { return this._day_data };
+   public get week_data(): PeriodData<WT> { return this._week_data };
+   public get month_data(): PeriodData<MT> { return this._month_data };
+   public get uid(): string { return this._uid };
+   //
+   private _dirty: boolean = false;
+   public checkNewDay(): void {
+      let now = ch.date.now();
+      this._day_data?.check(now);
+      this._week_data?.check(now);
+      this._month_data?.check(now);
+   }
+   //
+   private compareVersion(version1: string, version2: string): number {
+      // 移除前面的 'v' 字符  
+      const v1 = version1.replace(/^V/, '');
+      const v2 = version2.replace(/^V/, '');
+      // 分割版本号  
+      const parts1 = v1.split('.').map(Number); // 将版本号分割并转为数字  
+      const parts2 = v2.split('.').map(Number);
+      // 比较每一部分  
+      for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
+         const num1 = parts1[i] || 0; // 如果没有该部分,默认为 0  
+         const num2 = parts2[i] || 0;
+         if (num1 < num2) {
+            return -1; // version1 < version2  
+         }
+         if (num1 > num2) {
+            return 1; // version1 > version2  
+         }
+      }
+      return 0; // 两个版本号相等  
+   }
+   //
+   private async loadGameDataWithRetry(maxRetries: number = 3, delayMs: number = 1000): Promise<any | null> {
+      let attempt = 0;
+      while (attempt < maxRetries) {
+         const data = await this.load_data();
+         if (data != null) {
+            return data ?? null;
+         } else {
+            attempt++;
+            await new Promise(resolve => setTimeout(resolve, delayMs));
+         }
+      }
+      return null;
+   }
+   //
+   private async saveGameDataWithRetry(save_data: { [key: string]: any }, maxRetries: number = 3, delayMs: number = 1000): Promise<boolean> {
+      let attempt = 0;
+
+      while (attempt < maxRetries) {
+         const ret = await this.save_data(save_data);
+         if (ret) {
+            return true;
+         } else {
+            attempt++;
+            await new Promise(resolve => setTimeout(resolve, delayMs));
+         }
+      }
+      return false;
+   }
+   /**加载数据*/
+   public async load(ver?: string | null): Promise<void> {
+      this.on_load(0);
+      let load_data = ch.storage.getObject(this._key, this._gid);
+      if (load_data) {
+         if (ch.sdk.get_inited() && load_data.uid != this._uid) load_data = null;
+      }
+      //
+      if (!load_data) {
+         const remote_data = await this.loadGameDataWithRetry();
+         load_data = remote_data;
+      }
+      // } else if (remote_data && this.on_check(load_data, remote_data)) {
+      //    load_data = remote_data;
+      // }
+      //
+      if (!load_data) {
+         this.on_init();
+      } else {
+         this.unserialize(load_data);
+      }
+      this.checkVer(ver);
+
+      this.checkNewDay();
+      this.on_load(1);
+   }
+   /**远程调用保存,如果不是强制远程保存,必并先设置脏数据*/
+   public async save(force: boolean = false): Promise<boolean> {
+      this.on_save(0);
+      if (force) {
+         this.setDirty();
+      } else if (!this._dirty) {
+         this.on_save(1);
+         return false;
+      }
+      let ret = await this.saveGameDataWithRetry(this.serialize());
+      this.on_save(1);
+      if (ret) this._dirty = false;
+      return ret;
+   }
+   /**设置脏数据后保存到本地*/
+   public setDirty() {
+      this._dirty = true;
+      ch.storage.set(this._key, this.serialize(), this._gid);
+   }
+   private checkVer(new_v: string | null): void {
+      if (!new_v) return;
+      if (!this._ver) {
+         this.on_ver(true, this._ver, new_v);
+      }
+      let k = this.compareVersion(this._ver, new_v);
+      this.on_ver(k < 0, this._ver, new_v);
+      this._ver = new_v;
+   }
+   /**
+   * 序列化(数据库存储)
+   */
+   private serialize(): { [key: string]: number | string | any } {
+      const save_data: { [key: string]: number | string } = {};
+      if (this._data) save_data.data = this._data.serialize();
+      if (this._day_data) save_data.day_data = this._day_data.serialize();
+      if (this._week_data) save_data.week_data = this._week_data.serialize();
+      if (this._month_data) save_data.month_data = this._month_data.serialize();
+      save_data.uid = this._uid;
+      save_data.gid = this._gid;
+      this._save_time = ch.date.now();
+      save_data.save_time = this._save_time;
+      if (this._ver) save_data.ver = this._ver;
+      this.on_serialize(save_data);
+      return save_data;
+   }
+   /**
+   * 反序列化
+   */
+   private unserialize(load_data: { [key: string]: number | string | any }): void {
+      if (!load_data) return;
+      this._data?.unserialize(load_data.data);
+      this._day_data?.unserialize(load_data.day_data);
+      this._week_data?.unserialize(load_data.week_data);
+      this._month_data?.unserialize(load_data.month_data);
+      this._uid = load_data.uid ?? this._uid;
+      this._gid = load_data.gid ?? this._gid;
+      this._save_time = load_data.save_time as number ?? ch.date.now();
+      if (load_data.ver) this._ver = load_data.ver as string;
+      this.on_unserialize(load_data);
+   }
+   /**重写此方法初始自定义数据*/
+   protected on_init(): void {
+   }
+   /**重写此方法检测是否使用远程数据*/
+   protected on_check(local: any, remote: any): boolean {
+      return true;
+   }
+   /**重写 版本检测数据处理*/
+   protected on_ver(is_new: boolean, old_v: string, new_v: string): void {
+   }
+   /**重写序列化加入自定义的数据*/
+   protected on_serialize(data: { [key: string]: number | string | any }): void {
+
+   }
+   /**重写反序列化*/
+   protected on_unserialize(data: { [key: string]: number | string | any }): void {
+   }
+   /**保存数据(0开始 1结束)*/
+   protected on_save(step: 0 | 1): void {
+
+   }
+   /**加载数据(0开始 1结束)*/
+   protected on_load(step: 0 | 1): void {
+
+   }
+   
+   /**重写成其它加载方式*/
+   protected async load_data(): Promise<{ [key: string]: any; }> {
+      const ret = await ch.sdk.loadGameData(this._key);
+      if (ret.code === 0) {
+         return ret.data;
+      } else {
+         ch.log.warn(`尝试加载数据失败,错误代码: ${ret.code}  ${ret.err}`);
+      }
+      return null;
+   }
+   /**重写成其它保存方式*/
+   protected async save_data(save_data: { [key: string]: any; }): Promise<boolean> {
+      const ret = await ch.sdk.saveGameData(this._key, save_data);
+      if (ret.code == 0) {
+         return true;
+      } else {
+         ch.log.warn(`尝试保存数据失败,错误代码: ${ret.code}  ${ret.err}`);
+         return false;
+      }
+   }
+}

+ 9 - 0
llk/assets/core/util_class/GameData.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "c88bde51-0f1c-4827-b614-4b0bfa575726",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 36 - 0
llk/assets/core/util_class/HeadIcon.ts

@@ -0,0 +1,36 @@
+import { _decorator, Sprite, SpriteFrame} from 'cc';
+import { ch } from '../../ch/ch';
+class HeadIcon {
+    private _map:Map<string,SpriteFrame>=new Map();
+    private _map_uid:Map<Sprite,string>=new Map();
+    constructor(){
+    }
+    public async showIcon(uid:string,remoteUrl:string,sp:Sprite=null,def?:SpriteFrame):Promise<SpriteFrame>{
+        if(!remoteUrl || remoteUrl=="image"){
+            if(sp)sp.spriteFrame=def;
+            return;
+        }
+        if(this._map.has(uid)){
+            const spr=this._map.get(uid);
+            if(sp)sp.spriteFrame=spr;
+            return spr;
+        }else{
+            const map=this._map;
+            const map_uid=this._map_uid;
+            if(sp && sp.isValid ) map_uid.set(sp,uid);
+            const spriteFrame = await ch.util.loadImage(remoteUrl);
+            if(sp && sp.isValid )if(map_uid.get(sp)==uid)sp.spriteFrame=spriteFrame;
+            map.set(uid,spriteFrame);
+            return spriteFrame;
+        }
+    }
+    public clean():void{
+        this._map.clear();
+        this._map_uid.clear();
+    }
+}
+export default function get_new_head_icon():HeadIcon { return new HeadIcon();}
+
+
+
+

+ 9 - 0
llk/assets/core/util_class/HeadIcon.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "e8ef16c6-dcb8-4183-bfcf-be5a3252b0c3",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 78 - 0
llk/assets/core/util_class/Line2DMove.ts

@@ -0,0 +1,78 @@
+import { Vec2 } from "cc";
+//直线移动
+export class Line2DMove {
+   private _s: Vec2;//起始点
+   private _e: Vec2;//终点
+   private _percentSpeed: number;
+   private _percentAddSpeed: number;
+   private _percent: number;//进度
+   private _current: Vec2;
+   private _currentDir: Vec2;
+   /**
+   * @param start_x 起始点
+   * @param start_y 
+   * @param end_x 终点
+   * @param end_y 
+   * @param speed 速度
+   * @param addSpeed 加速度
+   * 起点终点一致时没有速度将不起作用
+   */
+   constructor(start_x: number, start_y: number, end_x: number, end_y: number, speed: number, addSpeed: number) {
+      this._s = new Vec2(start_x, start_y);
+      this._e = new Vec2(end_x, end_y);
+      this._current = new Vec2(start_x, start_y);
+      this._currentDir = new Vec2(this._e.x - this._s.x, this._e.y - this._s.y).normalize();
+      let dis = (new Vec2(end_x, end_y).subtract2f(start_x, start_y)).length();
+      if (dis <= 0) {
+         this._percent = 1;
+         this._percentSpeed = 0;
+         this._percentAddSpeed = 0;
+      } else {
+         this._percent = 0;
+         this._percentSpeed = speed / dis;
+         this._percentAddSpeed = addSpeed / dis;
+      }
+   }
+   public get target_pos_x(): number {
+      return this._e.x;
+   }
+   public get target_pos_y(): number {
+      return this._e.y;
+   }
+   public get current_dir(): Vec2 {
+      return this._currentDir;
+   }
+   public get isEnd(): boolean {
+      return this._percent >= 1;
+   }
+   public get percent(): number {
+      return this._percent;
+   }
+   /**
+    * 向目标运动
+    * @param dt 帧时间
+    * @param mb 目标点,没有的话为初始定义的点
+    * @returns 
+    */
+   public MoveTo(dt: number, mb: Vec2 = null): Vec2 {
+      if (this._percentSpeed == 0 && this._percentAddSpeed == 0) return this._current;
+      this._percentSpeed += this._percentAddSpeed * dt;
+      this._percent += this._percentSpeed * dt;
+      let nextpos = this.getPos(this._percent, this._s, mb == null ? this._e : mb);
+      this._current.x = nextpos.x;
+      this._current.y = nextpos.y;
+      return this._current;
+   }
+   /**
+     * 获取进度位置
+     * @param t 当前时间进度 0-1
+     * @param a 起点
+     * @param b 控制点
+     * @param c 终点
+   */
+   private _ab = new Vec2();
+   private getPos(t: number, a: Vec2, b: Vec2): Vec2 {
+      Vec2.lerp(this._ab, a, b, t);
+      return this._ab;
+   }
+}

+ 9 - 0
llk/assets/core/util_class/Line2DMove.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "24592c8c-7c3f-4eea-ab15-95d8430f73db",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 198 - 0
llk/assets/core/util_class/PeriodData.ts

@@ -0,0 +1,198 @@
+export enum PeriodDataUpdate {
+   none = 0,
+   day = 1,
+   week = 2,
+   month = 3,
+}
+/**周期数据
+ * @param T 为一个枚举类型
+ * @param update PeriodDataUpdate 更新周期
+ * @param limt 限制值最大,最小
+ * 调用check检测更新
+ * @exampl
+定义数据枚举  字段名和字段key需要一致
+export enum day_type{
+   score ="score",
+   level ="level",
+   relive="relive",
+}
+初始所有定义数据
+day_data = new PeriodData<day_type>(PeriodDataUpdate.day,
+         Object.keys(day_type) as day_type[],
+         new Map([
+               [day_type.relive,{min:0,max:3}],//限制每日复活3次
+         ])
+      );
+day_data.get(day_type.score)...
+*/
+export default class PeriodData<T extends string | number | symbol> {
+   private _data: Map<T, { v: number, min: number | null, max: number | null }> = new Map();
+   private _last: number = 0;
+   private _update: PeriodDataUpdate;
+   constructor(update: PeriodDataUpdate, limt?: Map<T, { min?: number | null, max?: number | null }>) {
+      this._update = update;
+      if (!limt) return;
+      //遍历所有键进行初始化
+      limt.forEach((v, key) => {
+         this._data.set(key, { v: v.min ?? 0, min: v.min, max: v.max });
+      }, this);
+   }
+   /**
+   * 序列化(数据库存储)
+   */
+   public serialize(): any {
+      const obj: { [key: string]: number } = {};
+      this._data.forEach((value, key) => { obj[String(key)] = value.v; });
+      const data = {
+         "last": this._last,
+         "data": obj,
+      };
+      return data;
+   }
+   /**
+   * 反序列化
+   */
+   public unserialize(data: any): void {
+      if (!data) return;
+      this._last = data.last ?? 0;
+      const obj: { [key: string]: number } = data.data;
+      if (obj) {
+         for (const key in obj) {
+            const value = obj[key];
+            this._get(key as T).v = value;
+         }
+      }
+   }
+   private _get(key: T): { v: number, min: number, max: number } {
+      let data = this._data.get(key);
+      if (!data) {
+         data = { v: 0, min: 0, max: null };
+         this._data.set(key, data);
+      }
+      return data;
+   }
+   /**如果有必要,重新设置数据的min,max*/
+   set(key: T, value: number, min?: number | null, max?: number | null) {
+      const d = this._get(key);
+      d.v = value;
+      d.min = min;
+      d.max = max;
+   }
+   /**获取数据*/
+   get(key: T): number {
+      return this._get(key).v;
+   }
+   /**有大最值的话,获取数据为剩余可用次数*/
+   get_available(key: T): number {
+      const d = this._get(key);
+      if (d.max) return d.max - d.v;
+      return d.v;
+   }
+   isEnble(key: T): boolean {
+      const d = this._get(key);
+      return d.v > 0;
+   }
+   enble(key: T): void {
+      this._get(key).v = 1;
+   }
+   disEnble(key: T): void {
+      this._get(key).v = 0;
+   }
+   /**增加数据的值*/
+   add(key: T, n: number = 1): number | null {
+      return this.change(key, n);
+   }
+   /**减小数据的值*/
+   sub(key: T, n: number = 1): number | null {
+      return this.change(key, -n);
+   }
+   /**改变数据 n>0增加 小于0减少*/
+   change(key: T, n: number): number | null {
+      const d = this._get(key);
+      if (!d) return null;
+      d.v += n;
+      if (d.min && d.v < d.min) d.v = d.min;
+      if (d.max && d.v > d.max) d.v = d.max;
+      return d.v;
+   }
+   /**改变数据,比原本值大直接替换,没有改变返回null*/
+   change_big(key: T, n: number): number | null {
+      const d = this._get(key);
+      if (!d) return null;
+      if (n > d.v) {
+         d.v = n;
+         if (d.min && d.v < d.min) d.v = d.min;
+         if (d.max && d.v > d.max) d.v = d.max;
+         return d.v;
+      }
+      return null;
+   }
+   /**改变数据,比原本值小直接替换,没有改变返回null*/
+   change_sm(key: T, n: number): number | null {
+      const d = this._get(key);
+      if (!d) return null;
+      if (n < d.v) {
+         d.v = n;
+         if (d.min && d.v < d.min) d.v = d.min;
+         if (d.max && d.v > d.max) d.v = d.max;
+         return d.v;
+      }
+      return null;
+   }
+   /**检测更新now当前时间戳(ms)*/
+   check(now: number): boolean {
+      if (this._update == PeriodDataUpdate.none) return false;
+      if (this._update == PeriodDataUpdate.day) {
+         if (!this.isSameDate(now, this._last)) {
+            this._last = now;
+            this.reset();
+            return true;
+         }
+      }
+      if (this._update == PeriodDataUpdate.week) {
+         if (!this.areDatesInSameWeek(now, this._last)) {
+            this._last = now;
+            this.reset();
+            return true;
+         }
+      }
+      if (this._update == PeriodDataUpdate.month) {
+         if (!this.areDatesInSameMonth(now, this._last)) {
+            this._last = now;
+            this.reset();
+            return true;
+         }
+      }
+      return false;
+   }
+   reset(): void {
+      for (const key of this._data.keys()) {
+         const data = this._get(key);
+         data.v = data.min ?? 0;
+      }
+   }
+   /**判定两个时间是否是同一天*/
+   private isSameDate(timestamp1: number, timestamp2: number): boolean {
+      let date1 = new Date(timestamp1); let date2 = new Date(timestamp2);
+      return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate();
+   }
+   /**判定两个时间是否是同一周*/
+   private areDatesInSameWeek(date1: number, date2: number): boolean {
+      const startOfWeek1 = this.getStartOfWeek(new Date(date1));
+      const startOfWeek2 = this.getStartOfWeek(new Date(date2));
+      return startOfWeek1.getTime() === startOfWeek2.getTime();
+   }
+   private getStartOfWeek(date: Date): Date {
+      const startOfWeek = new Date(date);
+      const day = startOfWeek.getDay(); // 星期日是 0,星期一是 1,依此类推  
+      const diff = startOfWeek.getDate() - day; // 计算当前日期所在周的第一天  
+      startOfWeek.setDate(diff); // 设置为该周的第一天  
+      startOfWeek.setHours(0, 0, 0, 0); // 设置时间为 00:00:00  
+      return startOfWeek;
+   }
+   private areDatesInSameMonth(date1: number, date2: number): boolean {
+      const d1 = new Date(date1);
+      const d2 = new Date(date2);
+      return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth();
+   }
+}

+ 9 - 0
llk/assets/core/util_class/PeriodData.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6e0401e0-71cd-42da-be28-5dd0d09f0bf2",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 61 - 0
llk/assets/core/util_class/PrefabPool.ts

@@ -0,0 +1,61 @@
+import { Node,NodePool, Prefab, instantiate} from "cc";
+/** node对象池 */
+class PrefabPool {
+     private  _pools:Map<string,NodePool>= new Map();
+     private  _node:Node;
+     constructor(){
+        if(this._node==null){
+             this._node=new Node();
+             this._node.active=false;
+          }
+     }
+     private getPool(path:string):NodePool{
+        let pool= this._pools.get(path);
+        if(pool==null){
+            pool= new NodePool();
+            this._pools.set(path,pool);
+        }
+        return pool;
+    }
+    public GetNode(p:Prefab):Node{
+            let pool= this.getPool(p.name);
+            let node = pool.get();
+            if (!node){ 
+                node = instantiate (p);
+                node.name=p.name;
+            }
+            return node;
+     }
+     public RecNode(node:Node):void{
+          node.parent=this._node;
+          this.getPool(node.name).put(node);
+     }
+     public InitPool(p:Prefab,n:number=10):void{
+          let pool= this.getPool(p.name);
+          for(let i=0;i<n;i++){
+              let node = instantiate (p);
+              node.name=p.name;
+              node.parent=this._node;
+              pool.put(node);
+          }
+    }
+    public  Clear():void{
+        if(!this._node)return;
+        this._node.removeAllChildren();
+        this._pools.forEach((v,k)=>{
+            v.clear();
+        })
+        this._pools.clear();
+   }
+    public getAllPoolNames(): string[] {
+       return Object.keys(this._pools);
+    }
+    public getPoolSize(poolName: string): number {
+        const pool = this._pools[poolName];
+        if (pool) {
+           return pool.size();
+        }
+        return 0;
+    }
+}
+export default function get_new_prefab_pool():PrefabPool { return new PrefabPool();}

+ 9 - 0
llk/assets/core/util_class/PrefabPool.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "12f30917-fa5b-4ee0-9665-191b9edf8804",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 182 - 0
llk/assets/core/util_class/Queue.ts

@@ -0,0 +1,182 @@
+export default class Queue<T> {
+  private count: number;
+  private lowestCount: number;
+  private items: { [key: number]: T };
+  constructor() {
+    this.count = 0;
+    this.lowestCount = 0;
+    this.items = {};
+  }
+  /**加入队列*/
+  public enqueue(item: T): void {
+    // 队列的末尾添加元素: 将队列的大小作为key
+    this.items[this.count] = item;
+    this.count++;
+  }
+  /**拿出队首*/
+  public dequeue(): T {
+    if (this.isEmpty()) {
+      return undefined;
+    }
+    const result = this.items[this.lowestCount];
+    // 删除队首元素
+    delete this.items[this.lowestCount];
+    // 队首元素自增
+    this.lowestCount++;
+    return result;
+  }
+  /**是否为空队列*/
+  public isEmpty(): boolean {
+    return this.count - this.lowestCount === 0;
+  }
+  /**查看下一个出队元素 */
+  public peek(): T {
+    if (this.isEmpty()) {
+      return undefined;
+    }
+    return this.items[this.lowestCount];
+  }
+  /**队列个数*/
+  public size(): number {
+    return this.count - this.lowestCount;
+  }
+  /**清空队列*/
+  public clear(): void {
+    this.count = 0;
+    this.lowestCount = 0;
+    this.items = {};
+  }
+  public toString(): string {
+    if (this.isEmpty()) {
+      return "";
+    }
+    let objString = `${this.items[this.lowestCount]}`;
+    for (let i = this.lowestCount + 1; i < this.count; i++) {
+      objString = `${objString},${this.items[i]}`;
+    }
+    return objString;
+  }
+}
+
+/**
+ * 双端队列
+ */
+export class Deque<T> {
+  private items: {}
+  private lowestCount: number
+  private count: number
+
+  constructor() {
+    this.items = {}
+    this.lowestCount = 0
+    this.count = 0
+  }
+
+  /**
+   * 向队列的尾端添加元素
+   * @param element
+   * @returns size
+   */
+  addTail(element: T): number {
+    this.items[this.count++] = element
+    return this.size()
+  }
+
+  /**
+   * 向队列头部添加元素
+   * @param element
+   * @returns size
+   */
+  addHead(element: T): number {
+    if (this.count === 0) {
+      this.addTail(element)
+    } else if (this.lowestCount > 0) {
+      this.items[--this.lowestCount] = element
+    } else {
+      for (let i = this.count; i > this.lowestCount; i--) {
+        this.items[i] = this.items[i - 1]
+      }
+      this.count++
+      this.items[0] = element
+    }
+    return this.size()
+  }
+
+  /**
+   * 返回队列尾部的元素
+   * @returns T
+   */
+  getTail(): T {
+    if (this.isEmpty()) return undefined
+    this.count--
+    const res = this.items[this.count]
+    delete this.items[this.count]
+    return res
+  }
+
+  /**
+   * 返回头部元素
+   * @returns T
+   */
+  getHead(): T {
+    if (this.isEmpty()) return undefined
+    const res = this.items[this.lowestCount]
+    delete this.items[this.lowestCount]
+    this.lowestCount++
+    return res
+  }
+
+  /**
+   * 看一下队列首部的元素
+   * @returns T
+   */
+  peekHead(): T {
+    if (this.isEmpty()) return undefined
+    return this.items[this.lowestCount]
+  }
+
+  /**
+   * 看一下队列尾部的元素
+   * @return T
+   */
+  peekTail(): T {
+    if (this.isEmpty()) return undefined
+    return this.items[this.count - 1]
+  }
+
+  /**
+   * 返回元素的个数
+   * @returns number
+   */
+  size(): number {
+    return this.count - this.lowestCount
+  }
+
+  /**
+   * 判断队列是否为空
+   */
+  isEmpty(): boolean {
+    return this.size() === 0
+  }
+
+  /**
+   * 清空队列
+   */
+  clear(): void {
+    this.items = {}
+    this.count = this.lowestCount = 0
+  }
+
+  toString(): string {
+    if (this.isEmpty()) {
+      return ''
+    }
+    let res = this.items[this.lowestCount].toString()
+    for (let i = this.lowestCount + 1; i < this.count; i++) {
+      res = `${res}, ${this.items[i].toString()}`
+    }
+    return res
+  }
+}
+
+

+ 9 - 0
llk/assets/core/util_class/Queue.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "169009a6-3f39-4f91-98ae-3b9ecfc6f46c",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 107 - 0
llk/assets/core/util_class/Random.ts

@@ -0,0 +1,107 @@
+/**设种一个随机种子保证随机结果一致
+ * 后面需要可以引入 seedrandom 库
+*/
+class Random {
+    private _seed: number;
+    private _start_seed: number;
+    /**当前种子*/
+    public get seed(): number {
+        return this._seed;
+    }
+    /**原始种子*/
+    public get start_seed(): number {
+        return this._start_seed;
+    }
+    /**随机种子*/
+    constructor(seed: number) {
+        this.set_seed(seed);
+    }
+    /**设置当前开始种子*/
+    public set_seed(seed: number) {
+        this._seed = seed;
+        this._start_seed = seed;
+    }
+    private _nextSeed(): void {
+        this._seed = (this._seed * 9301 + 49297) % 233280;
+    }
+    /**0.0(含)-1.0(不含)*/
+    private _nextFloat(): number {
+        this._nextSeed();
+        return this._seed / 233280;
+    }
+    /**0(含)-max(不含) */
+    private _nextInt(max: number): number {
+        max = Math.floor(max);
+        this._nextSeed();
+        return this._seed % max;
+    }
+    /**
+     * 生成指定范围的随机浮点数
+     * @param min   最小值(含)
+     * @param max   最大值(不含)
+    */
+    public float(min: number = 0, max: number = 1): number {
+        return this._nextFloat() * (max - min) + min;
+    }
+    /**
+     * 生成指定范围的随机整数
+     * @param min 最小值(含)
+     * @param max 最大值(不含)
+     * @returns
+     */
+    public int(min: number = 0, max: number = 100): number {
+        return this._nextInt(max - min) + Math.ceil(min);
+    }
+    /** 生成随机整数 -1 或 1*/
+    public dir(): -1 | 1 {
+        return Math.floor(this.float() * 2) === 0 ? -1 : 1;
+    }
+    /**
+    * 在指定数组中随机取出N个不重复的数据
+    * @param resArr
+    * @param ranNum
+    * @returns {Array}
+    */
+    public getDiffValueFromArr<T>(resArr: Array<T>, ranNum: number): Array<T> {
+        let arr = new Array<T>();
+        let result = new Array<T>();
+        if (!resArr || resArr.length <= 0 || ranNum <= 0) {
+            return result;
+        }
+        for (let i = 0; i < resArr.length; i++) {
+            arr.push(resArr[i]);
+        }
+        if (ranNum >= arr.length) return arr;
+        ranNum = Math.min(ranNum, arr.length - 1);
+        for (let i = 0; i < ranNum; i++) {
+            let ran = this.int(0, arr.length - 1);
+            result.push(arr.splice(ran, 1)[0]);
+        }
+        return result;
+    }
+    /**
+    * 生成指定范围的随机整数
+     * @param min   最小值
+     * @param max   最大值
+     * @param type  类型
+     * @example
+     //type=1 [min,max) 得到一个两数之间的随机整数,这个值不小于min(如果min不是整数的话,得到一个向上取整的 min),并且小于(但不等于)max  
+     //type=2 [min,max] 得到一个两数之间的随机整数,包括两个数在内,这个值比min大(如果min不是整数,那就不小于比min大的整数),但小于(但不等于)max
+     //type=3 (min,max) 得到一个两数之间的随机整数
+    */
+    public int_type(min: number, max: number, type: number): number {
+        min = Math.ceil(min);
+        max = Math.floor(max);
+        switch (type) {
+            case 1: // [min,max) 得到一个两数之间的随机整数,这个值不小于min(如果min不是整数的话,得到一个向上取整的 min),并且小于(但不等于)max  
+                return Math.floor(this._nextFloat() * (max - min)) + min;
+            case 2: // [min,max] 得到一个两数之间的随机整数,包括两个数在内,这个值比min大(如果min不是整数,那就不小于比min大的整数),但小于(但不等于)max
+                return Math.floor(this._nextFloat() * (max - min + 1)) + min;
+            case 3: // (min,max) 得到一个两数之间的随机整数
+                return Math.floor(this._nextFloat() * (max - min - 1)) + min + 1;
+        }
+        return 0;
+    }
+}
+/**创建一个设定好种子的随机数实例*/
+export default function get_new_random(seed: number): Random { return new Random(seed); }

+ 9 - 0
llk/assets/core/util_class/Random.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0df91f5a-1b94-4b0d-bd92-5330a4bdb89f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 74 - 0
llk/assets/core/util_class/SafeNumber.ts

@@ -0,0 +1,74 @@
+/**
+* 防内存调试变量数字类型
+*/
+class SafeVarNumber {
+    private cache: number = 0;
+    private scale: number = 2;
+    private map: Object = {};
+    private idx: number = 0;
+    constructor(num: number = 0) {
+        this.value = num;
+    }
+    /**数值 */
+    get value() {
+        if (Math.abs(this.map[this.idx] * this.scale - this.cache) > 0.000001) {
+            this.map[this.idx] = this.cache / this.scale;
+        }
+        return this.map[this.idx];
+    }
+    set value(num: number) {
+        delete this.map[this.idx];
+        this.idx = Math.floor(Math.random() * 256);
+        this.map[this.idx] = num;
+        this.scale = Math.floor(Math.random() * 2) + 2;
+        this.cache = num * this.scale;
+    }
+    valueOf() {
+        return this.value;
+    }
+    toString() {
+        return this.value.toString();
+    }
+    clone() {
+        return new SafeVarNumber(this.value);
+    }
+}
+/**
+* 防内存调试常量数字类型
+*/
+class SafeConstNumber {
+    private cache: number = 0;
+    private scale: number = 2;
+    private map: Object = {};
+    private idx: number = 0;
+    constructor(num: number = 0) {
+        this.idx = Math.floor(Math.random() * 256);
+        this.map[this.idx] = num;
+        this.scale = Math.floor(Math.random() * 2) + 2;
+        this.cache = num * this.scale;
+    }
+    /**数值 */
+    get value() {
+        let num = this.map[this.idx];
+        if (Math.abs(num * this.scale - this.cache) > 0.000001) {
+            num = this.cache / this.scale;
+        }
+
+        delete this.map[this.idx];
+        this.idx = Math.floor(Math.random() * 256);
+        this.map[this.idx] = num;
+
+        return this.map[this.idx];
+    }
+    valueOf() {
+        return this.value;
+    }
+    toString() {
+        return this.value.toString();
+    }
+    clone() {
+        return new SafeConstNumber(this.value);
+    }
+}
+export function get_new_safe_number(num:number):SafeVarNumber { return new SafeVarNumber(num);}
+export function get_new_safe_const_number(num:number):SafeConstNumber { return new SafeConstNumber(num);}

+ 9 - 0
llk/assets/core/util_class/SafeNumber.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "7e0d8920-e850-4be3-ac0d-55f3622bb784",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 50 - 0
llk/assets/core/util_class/Shake.ts

@@ -0,0 +1,50 @@
+import { Vec3 } from "cc";
+
+class Shake {
+    private intensity:number;
+    private duration:number;
+    private shakePos:Vec3=new Vec3(0,0,0);
+    constructor(public startPos:Vec3=new Vec3(0,0,0)) {
+        this.duration=0;
+        this.intensity = 0;
+    }
+    /**改变初起点*/
+    setStartPos(x:number,y:number):void{
+        this.startPos.x=x;
+        this.startPos.y=y;
+    }
+    /**开始抖动
+    * duration 时间
+    * intensity 强度
+    */
+    start(duration:number=0.3,intensity:number=3.8):void{
+        this.duration=duration;
+        this.intensity = intensity;
+    }
+    /**停止抖动*/
+    stop():Vec3 {
+        this.shakePos.x = this.startPos.x;
+        this.shakePos.y = this.startPos.y;
+        this.duration=0;
+        return this.shakePos;
+    }
+    getShakePos():Vec3{
+        return this.shakePos;
+    }
+    action(dt: number):Vec3 {
+        if (this.duration>0) {
+            this.duration-=dt;
+            if(this.duration<=0){
+                return this.stop();
+            }else{
+                const randomX = Math.random() * this.intensity - this.intensity* 0.5;
+                const randomY = Math.random() * this.intensity - this.intensity *0.5;
+                this.shakePos.x = this.startPos.x+randomX;
+                this.shakePos.y = this.startPos.y+randomY;
+                return this.shakePos;
+            }
+        }
+        return null;
+    }
+}
+export default function get_new_shake(startPos:Vec3=new Vec3(0,0,0)):Shake { return new Shake(startPos);}

+ 9 - 0
llk/assets/core/util_class/Shake.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "88ada871-02bb-483c-a86d-5e7101dbdfa8",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 68 - 0
llk/assets/core/util_class/State.ts

@@ -0,0 +1,68 @@
+/*enum states{
+    Read = 1,  //0001
+    Write = 2, //0010
+    Create = 4,//0100
+    Delete = 8 //1000
+}/**状态类型不算合并的不超过32种
+export enum StateType {
+    Unmatched = 1<<1,//无敌
+    Swoon = 1<<2,//晕眩
+}*/
+/**状态对像*/
+class State {
+    private _state: number = 0;//无状态
+    public Init(): void {
+        this._state = 0;
+    }
+    /**是否满足传入的状态*/
+    public Meet(state: number): boolean {
+        return State.Has(this._state, state);
+    }
+    /**传入的状态是组合型时只要满足其一 */
+    public MeetAny(state: number): boolean {
+        let a = this._state & state;
+        if (a === state) return true;
+        if (a == 0) return false;
+        return State.Has(state, a);
+    }
+    /**是否满足所有传入的状态*/
+    public MeetAll(...params: number[]): boolean {
+        let result = 0;
+        for (let i = 0; i < params.length; i++) {
+            result |= params[i];
+        }
+        return this.Meet(State.CombieState(result))
+    }
+    /**是否满足其中传入的状态*/
+    public MeetAnyOne(...params: number[]): boolean {
+        for (let i = 0; i < params.length; i++) {
+            if (this.Meet(params[i])) return true;
+        }
+        return false;
+    }
+    /**加入状态*/
+    public Add(state: number): void {
+        this._state |= state;
+    }
+    /**删除状态*/
+    public Delete(state: number): void {
+        if (!this.Meet(state)) return;
+        this._state ^= state;
+    }
+    /**转成状态*/
+    public static ToState(Id: number): number {
+        return 1 << Id;
+    }
+    /**合并状态*/
+    public static CombieState(...params: number[]): number {
+        let result = 0;
+        for (let i = 0; i < params.length; i++) {
+            result |= params[i];
+        }
+        return result;
+    }
+    public static Has(statss: number, state: number): boolean {
+        return (statss & state) === state;
+    }
+}
+export default function get_new_state(): State { return new State(); }

+ 9 - 0
llk/assets/core/util_class/State.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "d9db0143-8c08-4db8-b03d-3154d254082f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 198 - 0
llk/assets/core/util_class/TimeJobCenter.ts

@@ -0,0 +1,198 @@
+/**任务*/
+class  TimeTask
+{
+    public  tid:number;
+    public  destTime:number;//延时执行时间
+    public  callback:Function;//执行方法
+    public  count:number;//执行次数
+    public  delay:number;//间隔
+    public  doDelete:boolean=false;
+    constructor( tid:number,callback:Function, destTime:number,delay:number,count:number)
+    {
+        this.tid = tid;
+        this.callback = callback;
+        this.destTime = destTime;
+        this.delay = delay;
+        this.count = count;
+    }
+}
+/**时间单位*/
+export  enum TimeUnit
+{
+    Millisecond,
+    Second,
+    Hour,
+    Day
+}
+/**
+ * 时间定时任务
+ * 更好管理某一模块层级的时间任务,暂停,取消等
+ */
+class  TimeJobCenter {
+    private tid:number = 0;
+    private tempList:Array<TimeTask>  = new Array<TimeTask>();
+    private taskList:Array<TimeTask> = new Array<TimeTask>();
+    private nowTime:number=0;
+    public Dispose():void
+    {
+        this.taskList.length=0;
+        this.tempList.length=0;
+        this.nowTime=0;
+    }
+    private getTid():number
+    {
+        //安全代码以防过了
+        if (this.tid==Number.MAX_VALUE)
+        {
+            let id = 0;
+            while (true)
+            {
+                let used = false;
+                for (let index = 0; index < this.taskList.length; index++)
+                {
+                    if (this.taskList[index].tid == id)
+                    {
+                        used = true;
+                        break;
+                    }
+                }
+                if (!used)
+                {
+                    break;
+                }
+                else
+                {
+                    id++;
+                }
+            }
+            return id;
+        }else{
+            this.tid += 1; 
+            return this.tid;
+        }
+    }
+    /**延迟(ms)*/
+    public delay(ms: number): Promise<void> {  
+        return new Promise(resolve => this.AddTimeTask(resolve, ms,1,TimeUnit.Millisecond));  
+    }
+    /**延迟(s)*/
+    public delay_second(ms: number): Promise<void> {  
+        return new Promise(resolve => this.AddTimeTask(resolve, ms,1,TimeUnit.Second));  
+    } 
+    /**
+     * 增加时间任务
+     * @param callback 执行方法
+     * @param delay 延迟时间
+     * @param count 次数
+     * @param timeUnit 时间单位 默认为 秒 TimeUnit.Second
+     * @returns 时间任务id
+     */
+    public  AddTimeTask(callback:Function, delay:number,count:number = 1, timeUnit:TimeUnit = TimeUnit.Second):number
+    {
+            let tid = this.getTid();
+            this.tempList.push(this.CreatNewTimeTask(tid, callback, delay, count, timeUnit));//增加一个任务到缓存
+            return tid;
+    }
+    //
+    private CreatNewTimeTask(tid:number, callback:Function, delay:number,count:number = 1, timeUnit:TimeUnit = TimeUnit.Second):TimeTask
+    {
+            if (timeUnit != TimeUnit.Second)
+            {
+                //如果单位不是秒,就全换算成秒做为单位
+                switch (timeUnit)
+                {
+                    case TimeUnit.Millisecond:
+                        delay *=0.001;
+                        break;
+                    case TimeUnit.Hour:
+                        delay *= 360;
+                        break;
+                    case TimeUnit.Day:
+                        delay *= 360 * 24;
+                        break;
+                    default:
+                        console.error("Add Task TimeUnit Type Error...");
+                        break;
+                }
+            }
+            return new TimeTask(tid, callback,this.nowTime + delay, delay, count);
+    }
+    /**
+     * 删除一个时间任务
+     * @param tid 时间任务id
+     * @returns 是否删除成功
+     */
+    public  DeleteTimeTask(tid:number):boolean
+    {
+            let exist = false;
+            let tt: TimeTask;
+            for (let i = 0; i < this.tempList.length; i++)
+            {
+                tt = this.tempList[i];
+                if (tt.tid == tid)
+                {
+                    this.tempList[i].doDelete=true;
+                    exist = true;
+                    break;
+                }
+            }
+            if (!exist)
+            {
+                for (let i = 0; i < this.taskList.length; i++)
+                {
+                    tt = this.taskList[i];
+                    if (tt.tid == tid)
+                    {
+                        this.taskList[i].doDelete=true;
+                        exist = true;
+                        break;
+                    }
+                }
+            }
+            return exist;
+      }
+      //
+      private _index:number;
+      private _count:number;
+      private _tt:TimeTask;
+      //运行调用
+      public Run(dt:number):void
+      {
+          this.nowTime +=dt;
+          this._count=this.tempList.length;
+          if (this._count> 0)
+          {
+              for (this._index = 0;this._index < this._count; this._index++)
+              {
+                   if(this.tempList[this._index].doDelete)continue;
+                   this.taskList.push(this.tempList[this._index]);
+              }
+              this.tempList.length=0;
+          }
+          this._count=this.taskList.length;
+          if(this._count>0){
+             for (this._index = 0; this._index <this._count; this._index++)
+             {
+                this._tt = this.taskList[this._index];
+                if (this.nowTime < this._tt.destTime) continue;
+                if (this._tt.doDelete) continue;
+                this._tt.callback();
+                if (this._tt.count == 1)
+                {
+                    this._tt.doDelete=true;
+                }
+                else if (this._tt.count > 0)
+                {
+                    this._tt.count--;
+                    this._tt.destTime += this._tt.delay;//下次执行时间
+                }else{
+                    this._tt.destTime += this._tt.delay;
+                }
+             }
+          }
+          this.taskList = this.taskList.filter(task => !task.doDelete);//把标记为删除的真正删除
+       }
+       //
+}
+export default function get_new_job():TimeJobCenter { return new TimeJobCenter();}
+

+ 9 - 0
llk/assets/core/util_class/TimeJobCenter.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "29a95e83-4861-4b5b-a1bc-dfdf61028cfc",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 60 - 0
llk/assets/core/util_class/Timer.ts

@@ -0,0 +1,60 @@
+class Timer {
+    private _elapsedTime: number = 0;
+    get elapsedTime(): number {
+        return this._elapsedTime;
+    }
+    private _step: number = -1;
+    /** 触发间隔时间(秒) */
+    get step(): number {
+        return this._step;
+    }
+    set step(step: number) {
+        this._step = step;                     // 每次修改时间
+        this._elapsedTime = 0;                 // 逝去时间
+    }
+    get coundown(): number {
+        return this._step - this._elapsedTime;
+    }
+    get progress(): number {
+        return this._elapsedTime / this._step;
+    }
+    constructor(step: number = 0) {
+        this.step = step;
+    }
+    /**
+    * 序列化(数据库存储)
+    */
+    public serialize(): any {
+        let data = {
+            "step": this._step,
+            "elapsed": this._elapsedTime,
+        };
+        return data;
+    }
+    /**
+     * 反序列化
+     */
+    public unserialize(data: any): void {
+        if (!data) return;
+        this._step = data.step;
+        this._elapsedTime = data.elapsed;
+    }
+    update(dt: number): boolean {
+        if (this.step <= 0) return false;
+        this._elapsedTime += dt;
+        if (this._elapsedTime >= this._step) {
+            this._elapsedTime -= this._step;
+            return true;
+        }
+        return false;
+    }
+    reset() {
+        this._elapsedTime = 0;
+    }
+    stop() {
+        this._elapsedTime = 0;
+        this.step = -1;
+    }
+}
+
+export default function get_new_timer(step: number): Timer { return new Timer(step); }

+ 9 - 0
llk/assets/core/util_class/Timer.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "89024f12-7734-48b1-ae9d-3c720ef90c72",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

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