diff --git a/assets/core/Oops.ts b/assets/core/Oops.ts index cc53a346fd2a655e0076699c8ea46431f7b9c9aa..1e9dda97950d8aca0857f3ca4b114cb4be7e8423 100644 --- a/assets/core/Oops.ts +++ b/assets/core/Oops.ts @@ -10,8 +10,6 @@ import { ecs } from "../libs/ecs/ECS"; import { ECSRootSystem } from "../libs/ecs/ECSSystem"; import { LanguageManager } from "../libs/gui/language/Language"; import { VM } from "../libs/model-view/ViewModel"; -import { HttpRequest } from "../libs/network/HttpRequest"; -import { NetManager } from "../libs/network/NetManager"; import { Config } from "../module/config/Config"; import { AudioManager } from "./common/audio/AudioManager"; import { MessageManager } from "./common/event/MessageManager"; @@ -55,10 +53,6 @@ export class oops { /** 多语言模块 */ static language: LanguageManager = new LanguageManager(); - /** HTTP */ - static http: HttpRequest = new HttpRequest(); // 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容(https://store.cocos.com/app/detail/5877) - /** WebSocket */ - static tcp: NetManager = new NetManager(); // 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容(https://store.cocos.com/app/detail/5877) /** ECS */ static ecs: ECSRootSystem = new ecs.RootSystem(); /** MVVM */ diff --git a/assets/core/common/audio/AudioEffectPool.ts b/assets/core/common/audio/AudioEffectPool.ts index 5c740e716df620a6965a84ed5070088181b6afe1..34e1a3217b2f400d7f83db6c3266a6c1cd5dce17 100644 --- a/assets/core/common/audio/AudioEffectPool.ts +++ b/assets/core/common/audio/AudioEffectPool.ts @@ -2,6 +2,7 @@ import { AudioClip, Node, NodePool } from "cc"; import { oops } from "../../Oops"; import { resLoader } from "../loader/ResLoader"; import { AudioEffect } from "./AudioEffect"; +import { IAudioParams } from "./IAudio"; const AE_ID_MAX = 30000; @@ -9,10 +10,10 @@ const AE_ID_MAX = 30000; export class AudioEffectPool { private _switch: boolean = true; /** 音效开关 */ - public get switch(): boolean { + get switch(): boolean { return this._switch; } - public set switch(value: boolean) { + set switch(value: boolean) { this._switch = value; if (value) this.stop(); } @@ -52,10 +53,21 @@ export class AudioEffectPool { * @param onPlayComplete 播放完成回调 * @returns */ - async load(url: string | AudioClip, bundleName: string = resLoader.defaultBundleName, onPlayComplete?: Function): Promise { + async loadAndPlay(url: string | AudioClip, params?: IAudioParams): Promise { return new Promise(async (resolve, reject) => { if (!this.switch) return resolve(-1); + let bundleName = resLoader.defaultBundleName; + let loop = false; + let volume = this.volume; + let onPlayComplete: Function = null!; + if (params) { + if (params.bundle != null) bundleName = params.bundle; + if (params.loop != null) loop = params.loop; + if (params.volume != null) volume = params.volume; + if (params.onPlayComplete != null) onPlayComplete = params.onPlayComplete; + } + // 创建音效资源 let clip: AudioClip; if (url instanceof AudioClip) { @@ -63,6 +75,8 @@ export class AudioEffectPool { } else { clip = resLoader.get(url, AudioClip, bundleName)!; + + // 加载音效资源 if (clip == null) { let urls = this.res.get(bundleName); if (urls == null) { @@ -101,22 +115,24 @@ export class AudioEffectPool { node.name = "AudioEffect"; node.parent = oops.audio.node; ae = node.addComponent(AudioEffect)!; + ae.onComplete = () => { + this.put(aeid, url, bundleName); // 播放完回收对象 + onPlayComplete && onPlayComplete(aeid, url, bundleName); + // console.log(`【音效】回收,池中剩余音效播放器【${this.pool.size()}】`); + }; } else { node = this.pool.get()!; ae = node.getComponent(AudioEffect)!; } - ae.onComplete = () => { - this.put(aeid, url, bundleName); // 播放完回收对象 - onPlayComplete && onPlayComplete(); - // console.log(`【音效】回收,池中剩余音效播放器【${this.pool.size()}】`); - }; // 记录正在播放的音效播放器 this.effects.set(key, ae); - ae.volume = this.volume; + ae.loop = loop; ae.clip = clip; + ae.volume = volume; + ae.currentTime = 0; ae.play(); resolve(aeid); @@ -147,23 +163,6 @@ export class AudioEffectPool { } } - /** 释放所有音效资源与对象池中播放器 */ - release() { - // 释放正在播放的音效 - this.effects.forEach(ae => { - ae.node.destroy(); - }); - this.effects.clear(); - - // 释放音效资源 - this.res.forEach((urls: string[], bundleName: string) => { - urls.forEach(url => resLoader.release(url, bundleName)); - }); - - // 释放池中播放器 - this.pool.clear(); - } - /** 停止播放所有音效 */ stop() { this.effects.forEach(ae => { @@ -188,4 +187,21 @@ export class AudioEffectPool { ae.pause(); }); } + + /** 释放所有音效资源与对象池中播放器 */ + release() { + // 释放正在播放的音效 + this.effects.forEach(ae => { + ae.node.destroy(); + }); + this.effects.clear(); + + // 释放音效资源 + this.res.forEach((urls: string[], bundleName: string) => { + urls.forEach(url => resLoader.release(url, bundleName)); + }); + + // 释放池中播放器 + this.pool.clear(); + } } \ No newline at end of file diff --git a/assets/core/common/audio/AudioManager.ts b/assets/core/common/audio/AudioManager.ts index 670fa3c86e1aa2804f9e38cd04ffc30fff4dc8d4..3eee4646b8473c114f3e59f3e25ed4978c556610 100644 --- a/assets/core/common/audio/AudioManager.ts +++ b/assets/core/common/audio/AudioManager.ts @@ -2,6 +2,7 @@ import { AudioClip, Component } from "cc"; import { oops } from "../../Oops"; import { AudioEffectPool } from "./AudioEffectPool"; import { AudioMusic } from "./AudioMusic"; +import { IAudioParams } from "./IAudio"; const LOCAL_STORE_KEY = "game_audio"; @@ -19,100 +20,15 @@ export class AudioManager extends Component { effect: AudioEffectPool = new AudioEffectPool(); /** 音乐管理状态数据 */ - private local_data: any = {}; - - /** - * 设置背景音乐播放完成回调 - * @param callback 背景音乐播放完成回调 - */ - setMusicComplete(callback: Function | null = null) { - this.music.onComplete = callback; - } - - /** - * 播放背景音乐 - * @param url 资源地址 - * @param callback 音乐播放完成事件 - * @param bundleName 资源包名 - */ - playMusic(url: string, callback?: Function, bundleName?: string) { - if (this.music.switch) { - this.music.loop = false; - this.music.load(url, callback, bundleName).then(); - } - } - - /** 循环播放背景音乐 */ - playMusicLoop(url: string, bundleName?: string) { - if (this.music.switch) { - this.music.loop = true; - this.music.load(url, null!, bundleName).then(); - } - } - - /** 停止背景音乐播放 */ - stopMusic() { - if (this.music.switch && this.music.playing) { - this.music.stop(); - } - } - - /** - * 获取背景音乐播放进度 - */ - get progressMusic(): number { - return this.music.progress; - } - - /** - * 设置背景乐播放进度 - * @param value 播放进度值 - */ - set progressMusic(value: number) { - this.music.progress = value; - } - - /** - * 获取背景音乐音量 - */ - get volumeMusic(): number { - return this.music.volume; - } - - /** - * 设置背景音乐音量 - * @param value 音乐音量值 - */ - set volumeMusic(value: number) { - this.music.volume = value; - this.save(); - } - - /** - * 获取背景音乐开关值 - */ - get switchMusic(): boolean { - return this.music.switch; - } - - /** - * 设置背景音乐开关值 - * @param value 开关值 - */ - set switchMusic(value: boolean) { - this.music.switch = value; - if (!value) this.music.stop(); - this.save(); - } + private localData: any = {}; /** * 播放音效 * @param url 资源地址 - * @param callback 加载完成回调 - * @param bundleName 资源包名 + * @param params 音效参数 */ - playEffect(url: string | AudioClip, bundleName?: string, onPlayComplete?: Function): Promise { - return this.effect.load(url, bundleName, onPlayComplete); + playEffect(url: string | AudioClip, params?: IAudioParams): Promise { + return this.effect.loadAndPlay(url, params); } /** 回收音效播放器 */ @@ -120,35 +36,6 @@ export class AudioManager extends Component { this.effect.put(aeid, url, bundleName); } - /** 获取音效音量 */ - get volumeEffect(): number { - return this.effect.volume; - } - - /** - * 设置获取音效音量 - * @param value 音效音量值 - */ - set volumeEffect(value: number) { - this.effect.volume = value; - this.save(); - } - - /** 获取音效开关值 */ - get switchEffect(): boolean { - return this.effect.switch; - } - - /** - * 设置音效开关值 - * @param value 音效开关值 - */ - set switchEffect(value: boolean) { - this.effect.switch = value; - if (!value) this.effect.stop(); - this.save(); - } - /** 恢复当前暂停的音乐与音效播放 */ resumeAll() { if (!this.music.playing && this.music.progress > 0) this.music.play(); @@ -169,20 +56,20 @@ export class AudioManager extends Component { /** 保存音乐音效的音量、开关配置数据到本地 */ save() { - this.local_data.volume_music = this.music.volume; - this.local_data.volume_effect = this.effect.volume; - this.local_data.switch_music = this.music.switch; - this.local_data.switch_effect = this.effect.switch; + this.localData.volume_music = this.music.volume; + this.localData.volume_effect = this.effect.volume; + this.localData.switch_music = this.music.switch; + this.localData.switch_effect = this.effect.switch; - oops.storage.set(LOCAL_STORE_KEY, this.local_data); + oops.storage.set(LOCAL_STORE_KEY, this.localData); } /** 本地加载音乐音效的音量、开关配置数据并设置到游戏中 */ load() { this.music = this.getComponent(AudioMusic) || this.addComponent(AudioMusic)!; - this.local_data = oops.storage.getJson(LOCAL_STORE_KEY); - if (this.local_data) { + this.localData = oops.storage.getJson(LOCAL_STORE_KEY); + if (this.localData) { try { this.setState(); } @@ -196,14 +83,14 @@ export class AudioManager extends Component { } private setState() { - this.music.volume = this.local_data.volume_music; - this.effect.volume = this.local_data.volume_effect; - this.music.switch = this.local_data.switch_music; - this.effect.switch = this.local_data.switch_effect; + this.music.volume = this.localData.volume_music; + this.effect.volume = this.localData.volume_effect; + this.music.switch = this.localData.switch_music; + this.effect.switch = this.localData.switch_effect; } private setStateDefault() { - this.local_data = {}; + this.localData = {}; this.music.volume = 1; this.effect.volume = 1; this.music.switch = true; diff --git a/assets/core/common/audio/AudioMusic.ts b/assets/core/common/audio/AudioMusic.ts index 8b1b3957324ad46f6c052ee3b223a253ceeac44e..0f30441f4f64dc5eba585fb316def5b9bb9e0cca 100644 --- a/assets/core/common/audio/AudioMusic.ts +++ b/assets/core/common/audio/AudioMusic.ts @@ -6,8 +6,9 @@ */ import { AudioClip, AudioSource, _decorator } from 'cc'; import { resLoader } from '../loader/ResLoader'; +import { IAudioParams } from './IAudio'; -const { ccclass, menu } = _decorator; +const { ccclass } = _decorator; /** * 背景音乐 @@ -15,25 +16,20 @@ const { ccclass, menu } = _decorator; */ @ccclass('AudioMusic') export class AudioMusic extends AudioSource { - /** 背景音乐开关 */ - switch: boolean = true; - /** 背景音乐播放完成回调 */ - onComplete: Function | null = null; - private _progress: number = 0; private _isLoading: boolean = false; - private _nextBundleName: string = null!; // 下一个音乐资源包 - private _nextUrl: string = null!; // 下一个播放音乐 + private _nextUrl: string = null!; + private _nextParams: IAudioParams = null!; + private _params: IAudioParams = null!; - start() { - // this.node.on(AudioSource.EventType.STARTED, this.onAudioStarted, this); - this.node.on(AudioSource.EventType.ENDED, this.onAudioEnded, this); + /** 背景音乐开关 */ + private _switch: boolean = true; + get switch(): boolean { + return this._switch; } - - // private onAudioStarted() { } - - private onAudioEnded() { - this.onComplete && this.onComplete(); + set switch(value: boolean) { + this._switch = value; + if (!value) this.stop(); } /** 获取音乐播放进度 */ @@ -51,34 +47,59 @@ export class AudioMusic extends AudioSource { this.currentTime = value * this.duration; } + start() { + // this.node.on(AudioSource.EventType.STARTED, this.onAudioStarted, this); + this.node.on(AudioSource.EventType.ENDED, this.onAudioEnded, this); + } + + // private onAudioStarted() { } + + private onAudioEnded() { + if (this._params && this._params.onPlayComplete) { + this._params.onPlayComplete(); + } + } + /** * 加载音乐并播放 * @param url 音乐资源地址 - * @param callback 加载完成回调 - * @param bundleName 资源包名 + * @param params 背景音乐资源播放参数 */ - async load(url: string, callback?: Function, bundleName: string = resLoader.defaultBundleName) { + async loadAndPlay(url: string, params?: IAudioParams) { + if (!this.switch) return; // 禁止播放音乐 + // 下一个加载的背景音乐资源 if (this._isLoading) { - this._nextBundleName = bundleName; this._nextUrl = url; + this._nextParams = params!; return; } + let bundleName = resLoader.defaultBundleName; + let loop = false; + let volume = this.volume; + let onPlayComplete: Function = null!; + if (params) { + this._params = params! + if (params.bundle != null) bundleName = params.bundle; + if (params.loop != null) loop = params.loop; + if (params.volume != null) volume = params.volume; + if (params.onPlayComplete != null) onPlayComplete = params.onPlayComplete; + }; + this._isLoading = true; - var data: AudioClip = await resLoader.loadAsync(bundleName, url, AudioClip); - if (data) { + var clip: AudioClip = await resLoader.loadAsync(bundleName, url, AudioClip); + if (clip) { this._isLoading = false; // 处理等待加载的背景音乐 if (this._nextUrl != null) { // 加载等待播放的背景音乐 - this.load(this._nextUrl, callback, this._nextBundleName); - this._nextBundleName = this._nextUrl = null!; + this.loadAndPlay(this._nextUrl, this._nextParams); + this._nextUrl = null!; + this._nextParams = null!; } else { - callback && callback(); - // 正在播放的时候先关闭 if (this.playing) { this.stop(); @@ -88,12 +109,21 @@ export class AudioMusic extends AudioSource { this.release(); // 播放背景音乐 - this.clip = data; + this.clip = clip; + this.loop = loop; + this.volume = volume; + this.currentTime = 0; this.play(); } } } + stop(): void { + if (this.switch && this.playing) { + super.stop(); + } + } + /** 释放当前背景音乐资源 */ release() { if (this.clip) { diff --git a/assets/core/common/audio/IAudio.ts b/assets/core/common/audio/IAudio.ts new file mode 100644 index 0000000000000000000000000000000000000000..68b5ecfc2a98f367315f5a8901d5926743d0d917 --- /dev/null +++ b/assets/core/common/audio/IAudio.ts @@ -0,0 +1,10 @@ +export interface IAudioParams { + /** 资源包名 */ + bundle?: string, + /** 是否循环播放 */ + loop?: boolean; + /** 音效音量 */ + volume?: number; + /** 播放完成事件 */ + onPlayComplete?: Function; +} \ No newline at end of file diff --git a/assets/core/gui/layer/Defines.ts.meta b/assets/core/common/audio/IAudio.ts.meta similarity index 33% rename from assets/core/gui/layer/Defines.ts.meta rename to assets/core/common/audio/IAudio.ts.meta index 98233a91311f074f7e5d2b7867fe144743ddda4d..158a08857ee3aec9a12bc238fb76b70a821d87d0 100644 --- a/assets/core/gui/layer/Defines.ts.meta +++ b/assets/core/common/audio/IAudio.ts.meta @@ -1,13 +1,9 @@ -{ - "ver": "4.0.24", - "importer": "typescript", - "imported": true, - "uuid": "82d3af5c-ef52-4490-8f79-777aac7079bc", - "files": [], - "subMetas": {}, - "userData": { - "moduleId": "project:///assets/script/core/gui/layer/Defines.js", - "importerSettings": 4, - "simulateGlobals": [] - } -} +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "82ec630f-ea0b-4e37-a671-1b4555a756db", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/core/common/loader/ResLoader.ts b/assets/core/common/loader/ResLoader.ts index 50b856c610b6b961d5f5d3981f766d596920fb1d..85098b1db0fd70d10afb880d533e2a999a4c2243 100644 --- a/assets/core/common/loader/ResLoader.ts +++ b/assets/core/common/loader/ResLoader.ts @@ -1,4 +1,4 @@ -import { __private, AnimationClip, Asset, AssetManager, assetManager, AudioClip, error, Font, ImageAsset, js, JsonAsset, Material, Mesh, Prefab, resources, sp, SpriteFrame, Texture2D, warn } from "cc"; +import { __private, AnimationClip, Asset, AssetManager, assetManager, AudioClip, Font, ImageAsset, js, JsonAsset, Material, Mesh, Prefab, resources, sp, SpriteFrame, Texture2D, warn } from "cc"; export type AssetType = __private.__types_globals__Constructor | null; export type Paths = string | string[]; @@ -120,11 +120,12 @@ oops.res.loadRemote(this.url, opt, onComplete); * @example await oops.res.loadBundle(name); */ - loadBundle(name: string) { + loadBundle(name: string): Promise { return new Promise((resolve, reject) => { assetManager.loadBundle(name, (err, bundle: AssetManager.Bundle) => { if (err) { - return error(err); + resolve(null!); + return; } resolve(bundle); }); diff --git a/assets/core/game/GameManager.ts b/assets/core/game/GameManager.ts index 48b351d8986318ddd0227d368fb11eca80798f24..2f5d64ce3c25b65a562bac7f83264ccb317de672 100644 --- a/assets/core/game/GameManager.ts +++ b/assets/core/game/GameManager.ts @@ -5,16 +5,61 @@ * @LastEditTime: 2022-09-02 12:09:55 */ import { Node, director } from 'cc'; +import { ViewUtil } from '../utils/ViewUtil'; +import { resLoader } from '../common/loader/ResLoader'; +import { GameComponent } from '../../module/common/GameComponent'; + +/** 游戏元素打开参数 */ +export interface ElementParams { + /** 远程包名 */ + bundle?: string; + /** 节点排序索引 */ + siblingIndex?: number; +} /** 游戏世界管理 */ export class GameManager { - /** 界面根节点 */ + /** 自定义游戏世界根节点 */ root!: Node; constructor(root: Node) { this.root = root; } + /** + * 自定义游戏元素显示 + * @param parent 元素父节点 + * @param prefabPath 元素预制 + * @param params 可选参数据 + */ + open(parent: Node | GameComponent, prefabPath: string, params?: ElementParams): Promise { + return new Promise(async (resolve, reject) => { + let bundleName: string = null! + if (params && params.bundle) { + bundleName = params.bundle; + } + else { + bundleName = resLoader.defaultBundleName; + } + let node: Node = null!; + // 自动内存管理 + if (parent instanceof GameComponent) { + node = await parent.createPrefabNodeAsync(prefabPath, bundleName); + node.parent = parent.node; + } + // 手动内存管理 + else { + node = await ViewUtil.createPrefabNodeAsync(prefabPath, bundleName); + node.parent = parent; + } + + // 自定义节点排序索引 + if (params && params.siblingIndex) node.setSiblingIndex(params.siblingIndex); + + resolve(node); + }); + } + /** 设置游戏动画速度 */ setTimeScale(scale: number) { //@ts-ignore diff --git a/assets/core/gui/GuiEnum.ts b/assets/core/gui/GuiEnum.ts index c9a3a1953cfc385446004b75251fe337dce161ce..45ef6208927f7a5312c68b0a92db95f020779a55 100644 --- a/assets/core/gui/GuiEnum.ts +++ b/assets/core/gui/GuiEnum.ts @@ -1,3 +1,4 @@ +/** 框架提示资源路径 */ export enum PromptResType { /** 飘动提示 */ Toast = 'common/prefab/notify', diff --git a/assets/core/gui/layer/Defines.ts b/assets/core/gui/layer/Defines.ts deleted file mode 100644 index b6808a47d45cb22ce7d929cd77b1d9f781a7e1bb..0000000000000000000000000000000000000000 --- a/assets/core/gui/layer/Defines.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * @Author: dgflash - * @Date: 2021-11-18 11:21:32 - * @LastEditors: dgflash - * @LastEditTime: 2023-01-09 11:52:38 - */ -import { Node } from "cc"; -import { UIConfig } from "./UIConfig"; - -/*** 界面回调参数对象定义 */ -export interface UICallbacks { - /** - * 节点添加到层级以后的回调 - * @param node 当前界面节点 - * @param params 外部传递参数 - */ - onAdded?: (node: Node, params: any) => void, - - /** - * 窗口节点 destroy 之后回调 - * @param node 当前界面节点 - * @param params 外部传递参数 - */ - onRemoved?: (node: Node | null, params: any) => void, - - /** - * 如果指定onBeforeRemoved,则next必须调用,否则节点不会被正常删除。 - * - * 比如希望节点做一个FadeOut然后删除,则可以在`onBeforeRemoved`当中播放action动画,动画结束后调用next - * @param node 当前界面节点 - * @param next 回调方法 - */ - onBeforeRemove?: (node: Node, next: Function) => void, - - /** 网络异常时,窗口加载失败回调 */ - onLoadFailure?: () => void; -} - -/** 本类型仅供gui模块内部使用,请勿在功能逻辑中使用 */ -export class ViewParams { - /** 界面唯一编号 */ - uiid: number = -1; - /** 界面配置 */ - config: UIConfig = null!; - /** 传递给打开界面的参数 */ - params: any = null!; - /** 窗口事件 */ - callbacks: UICallbacks = null!; - /** 是否在使用状态 */ - valid: boolean = true; - /** 界面根节点 */ - node: Node = null!; -} \ No newline at end of file diff --git a/assets/core/gui/layer/LayerDialog.ts b/assets/core/gui/layer/LayerDialog.ts index 864143f838379bb8059b9c90ed1c64d8f3919567..ff7d51c6df97a4bafa1a062af000261b4aead53b 100644 --- a/assets/core/gui/layer/LayerDialog.ts +++ b/assets/core/gui/layer/LayerDialog.ts @@ -5,15 +5,19 @@ * @LastEditTime: 2023-07-24 17:14:57 */ -import { UICallbacks, ViewParams } from "./Defines"; import { LayerPopUp } from "./LayerPopup"; +import { UICallbacks, UIParams } from "./LayerUIElement"; import { UIConfig } from "./UIConfig"; /** 模式弹窗数据 */ type DialogParam = { - uiid: number; + /** 弹窗唯一编号 */ + uiid: string; + /** 窗口配置 */ config: UIConfig; + /** 窗口附加参数 */ params?: any; + /** 窗口回调 */ callbacks?: UICallbacks; } @@ -24,7 +28,7 @@ export class LayerDialog extends LayerPopUp { /** 窗口调用参数队列 */ private params: Array = []; - add(uiid: number, config: UIConfig, params?: any, callbacks?: UICallbacks) { + add(uiid: string, config: UIConfig, params?: any, callbacks?: UICallbacks) { // 控制同一时间只能显示一个模式窗口 if (this.ui_nodes.size > 0) { this.params.push({ @@ -40,24 +44,24 @@ export class LayerDialog extends LayerPopUp { } /** 显示模式弹窗 */ - private show(uiid: number, config: UIConfig, params?: any, callbacks?: UICallbacks) { - let vp = this.ui_cache.get(config.prefab); - if (vp == null) { - vp = new ViewParams(); - vp.uiid = uiid; - vp.valid = true; - vp.config = config; + private show(uiid: string, config: UIConfig, params?: any, callbacks?: UICallbacks) { + let uip = this.ui_cache.get(config.prefab); + if (uip == null) { + uip = new UIParams(); + uip.uiid = uiid; + uip.valid = true; + uip.config = config; } - vp.params = params || {}; - vp.callbacks = callbacks ?? {}; - this.ui_nodes.set(vp.config.prefab, vp); + uip.params = params || {}; + uip.callbacks = callbacks ?? {}; + this.ui_nodes.set(uip.config.prefab, uip); - this.load(vp, config.bundle); + this.load(uip, config.bundle); } - protected onCloseWindow(vp: ViewParams) { - super.onCloseWindow(vp); + protected onCloseWindow(uip: UIParams) { + super.onCloseWindow(uip); setTimeout(this.next.bind(this), 0); } diff --git a/assets/core/gui/layer/LayerEnum.ts b/assets/core/gui/layer/LayerEnum.ts index 5f13ebc1bd5f2820e31778bec1ddffd0a37299cc..e656455d72b3d155db9389017a9f26d7b1c06bac 100644 --- a/assets/core/gui/layer/LayerEnum.ts +++ b/assets/core/gui/layer/LayerEnum.ts @@ -1,3 +1,10 @@ +import { UIConfig } from "./UIConfig"; + +/** 界面编号 */ +export type Uiid = number | string | UIConfig; +/** 界面配置集合 */ +export type UIConfigMap = { [key: string]: UIConfig } + /** 屏幕适配类型 */ export enum ScreenAdapterType { /** 自动适配 */ @@ -8,10 +15,18 @@ export enum ScreenAdapterType { Portrait } -/** 界面层类型 */ -export enum LayerType { +/** 自定义层类型 */ +export enum LayerCustomType { /** 二维游戏层 */ Game = "LayerGame", + /** 消息提示层 */ + Notify = "LayerNotify", + /** 新手引导层 */ + Guide = "LayerGuide" +} + +/** 界面层类型 */ +export enum LayerType { /** 主界面层 */ UI = "LayerUI", /** 弹窗层 */ @@ -20,10 +35,6 @@ export enum LayerType { Dialog = "LayerDialog", /** 系统触发模式窗口层 */ System = "LayerSystem", - /** 消息提示层 */ - Notify = "LayerNotify", - /** 新手引导层 */ - Guide = "LayerGuide" } /** 界面层组件类型 */ @@ -36,6 +47,8 @@ export enum LayerTypeCls { Dialog = "Dialog", /** 消息提示层 */ Notify = "Notify", + /** 游戏层 */ + Game = "Game", /** 自定义节点层 */ Node = "Node" } diff --git a/assets/core/gui/layer/LayerGame.ts b/assets/core/gui/layer/LayerGame.ts new file mode 100644 index 0000000000000000000000000000000000000000..c16ef28d5a6d6dedb256cb65b8184f695a69ce89 --- /dev/null +++ b/assets/core/gui/layer/LayerGame.ts @@ -0,0 +1,169 @@ +/* + * @Author: dgflash + * @Date: 2025-08-15 10:06:47 + * @LastEditors: dgflash + * @LastEditTime: 2025-08-15 10:06:47 + */ +import { Layers, Node, NodePool, Prefab, Vec3, warn, Widget } from "cc"; +import { resLoader } from "../../common/loader/ResLoader"; +import { ViewUtil } from "../../utils/ViewUtil"; +import { LayerCustomType } from "./LayerEnum"; +import { GameElementParams, LayerGameElement } from "./LayerGameElement"; +import { GameElementConfig } from "./UIConfig"; + +/* 二维游戏层 */ +export class LayerGame extends Node { + /** 当前显示的元素节点 */ + protected elements = new Map(); + + constructor() { + super(LayerCustomType.Game); + + const widget: Widget = this.addComponent(Widget); + widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true; + widget.left = widget.right = widget.top = widget.bottom = 0; + widget.alignMode = 2; + widget.enabled = true; + + this.layer = Layers.Enum.UI_2D; + } + + /** + * 添加游戏元素 + * @param prefab 资源地址 + * @param config 游戏元素自定义配置 + */ + add(prefab: string, config: GameElementConfig = {}): Node { + let params = this.setParams(prefab, config, false); + let node = ViewUtil.createPrefabNode(prefab, params.config.bundle); + if (node) { + // 设置自定义属性 + this.setNode(node, config); + + let lge = node.addComponent(LayerGameElement); + lge.params = params; + params.nodes.push(node); + } + return node; + } + + /** + * 加载资源并添加游戏元素 + * @param prefab 资源地址 + * @param config 游戏元素自定义配置 + */ + addAsync(prefab: string, config: GameElementConfig = {}): Promise { + return new Promise(async (resolve, reject) => { + let bundleName = config.bundle ? config.bundle : resLoader.defaultBundleName; + await resLoader.loadAsync(bundleName, prefab, Prefab); + let node = this.add(prefab, config); + resolve(node); + }); + } + + /** + * 添加游戏元素 - 支持对象池 + * @param prefab 资源地址 + * @param config 游戏元素自定义配置 + */ + addPool(prefab: string, config: GameElementConfig = {}): Node { + let params = this.setParams(prefab, config, true); + let node: Node = null!; + if (params.pool.size() > 0) { + node = params.pool.get()!; + } + else { + node = ViewUtil.createPrefabNode(prefab, params.config.bundle); + node.addComponent(LayerGameElement); + } + + // 设置自定义属性 + this.setNode(node, config); + + let lge = node.getComponent(LayerGameElement)!; + lge.params = params; + + return node; + } + + /** + * 加载资源并添加游戏元素 - 支持对象池 + * @param prefab 资源地址 + * @param config 游戏元素自定义配置 + */ + addPoolAsync(prefab: string, config: GameElementConfig = {}): Promise { + return new Promise(async (resolve, reject) => { + let bundleName = config.bundle ? config.bundle : resLoader.defaultBundleName; + await resLoader.loadAsync(bundleName, prefab, Prefab); + let node = this.addPool(prefab, config); + resolve(node); + }); + } + + /** 清理池数据 */ + clearPool(node: Node) { + let lge = node.getComponent(LayerGameElement)!; + if (lge) { + let params = this.elements.get(lge.params.uiid); + if (params) params.pool.clear(); + } + } + + /** + * 移除游戏元素 + * @param node 游戏元素节点 + */ + remove(node: Node) { + let lge = node.getComponent(LayerGameElement)!; + if (lge) { + if (lge.params.pool) { + lge.params.pool.put(node); + } + else { + let nodes = lge.params.nodes; + let index = nodes.indexOf(node); + if (index != -1) { + nodes.splice(index, 1); + if (nodes.length == 0) { + this.elements.delete(lge.params.uiid); + resLoader.release(lge.params.config.prefab!, lge.params.config.bundle); + } + } + node.removeFromParent(); + } + } + else { + warn(`当前删除游戏元素的 Node 不是通过框架添加的`); + } + } + + /** 设置元素参数 */ + private setParams(prefab: string, config: GameElementConfig, pool: boolean) { + let bundleName = config.bundle ? config.bundle : resLoader.defaultBundleName; + let uuid = bundleName + "_" + prefab; + let params = this.elements.get(uuid); + if (params == null) { + config.prefab = prefab; + params = new GameElementParams(); + params.uiid = uuid; + params.config = config; + if (pool) { + params.pool = new NodePool(); + } + else { + params.nodes = []; + } + this.elements.set(uuid, params); + } + return params; + } + + /** 设置自定义属性 */ + private setNode(node: Node, config: GameElementConfig) { + node.scale = config.scale ? config.scale : Vec3.ONE; + node.position = config.position ? config.position : Vec3.ZERO; + node.eulerAngles = config.eulerAngles ? config.eulerAngles : Vec3.ZERO; + node.parent = config.parent ? config.parent : this; + if (config.siblingIndex != null) node.setSiblingIndex(config.siblingIndex); + } +} \ No newline at end of file diff --git a/assets/core/gui/layer/DelegateComponent.ts.meta b/assets/core/gui/layer/LayerGame.ts.meta similarity index 32% rename from assets/core/gui/layer/DelegateComponent.ts.meta rename to assets/core/gui/layer/LayerGame.ts.meta index 65a39b084845967001772ba173977900711d174b..5801e2c6ece4529aed7ff34427a22db71067b990 100644 --- a/assets/core/gui/layer/DelegateComponent.ts.meta +++ b/assets/core/gui/layer/LayerGame.ts.meta @@ -1,13 +1,9 @@ -{ - "ver": "4.0.24", - "importer": "typescript", - "imported": true, - "uuid": "d8f1f191-0fb7-41cc-8781-4a43a977f3f2", - "files": [], - "subMetas": {}, - "userData": { - "moduleId": "project:///assets/script/core/gui/layer/DelegateComponent.js", - "importerSettings": 4, - "simulateGlobals": [] - } -} +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "77041037-badf-4f11-b538-33a855aae209", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/core/gui/layer/LayerGameElement.ts b/assets/core/gui/layer/LayerGameElement.ts new file mode 100644 index 0000000000000000000000000000000000000000..32938aa6a7e8a5854df53b3725bfdd83bed6e655 --- /dev/null +++ b/assets/core/gui/layer/LayerGameElement.ts @@ -0,0 +1,27 @@ +import { _decorator, Component, Node, NodePool } from "cc"; +import { GameElementConfig } from "./UIConfig"; + +const { ccclass } = _decorator; + +/** 游戏元素组件 */ +@ccclass('LayerGameElement') +export class LayerGameElement extends Component { + /** 视图参数 */ + params: GameElementParams = null!; + + protected onDestroy(): void { + this.params = null!; + } +} + +/** 游戏元素参数 */ +export class GameElementParams { + /** 游戏元素唯一编号 */ + uiid: string = null!; + /** 游戏元素配置 */ + config: GameElementConfig = null! + /** 同类游戏元素集合 */ + nodes: Node[] = null!; + /** 同类游戏元素对象池 */ + pool: NodePool = null!; +} \ No newline at end of file diff --git a/assets/core/gui/layer/LayerGameElement.ts.meta b/assets/core/gui/layer/LayerGameElement.ts.meta new file mode 100644 index 0000000000000000000000000000000000000000..bed6853a00bf2b42ebcfb0071482682f4af1cdee --- /dev/null +++ b/assets/core/gui/layer/LayerGameElement.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "b43406cb-41d3-42a1-9393-40dbcd0a853e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/core/gui/layer/LayerManager.ts b/assets/core/gui/layer/LayerManager.ts index b4e6e5f85bed3d1210a5a84993506aceb17cc9aa..c3666abdf1396506a158625a3978ea22f14ee6ae 100644 --- a/assets/core/gui/layer/LayerManager.ts +++ b/assets/core/gui/layer/LayerManager.ts @@ -1,27 +1,15 @@ import { Camera, Layers, Node, ResolutionPolicy, SafeArea, Widget, screen, view, warn } from "cc"; +import { resLoader } from "../../common/loader/ResLoader"; import { oops } from "../../Oops"; -import { UICallbacks } from "./Defines"; -import { DelegateComponent } from "./DelegateComponent"; +import { LayerUIElement, UICallbacks } from "./LayerUIElement"; import { LayerDialog } from "./LayerDialog"; -import { LayerType, LayerTypeCls } from "./LayerEnum"; +import { LayerCustomType, LayerTypeCls, UIConfigMap, Uiid } from "./LayerEnum"; +import { LayerGame } from "./LayerGame"; import { LayerNotify } from "./LayerNotify"; import { LayerPopUp } from "./LayerPopup"; import { LayerUI } from "./LayerUI"; import { UIConfig } from "./UIConfig"; -/** 自动生成界面编号最小值 */ -const uiidMin = 1000000; -/** 自动生成界面编号最大值 */ -const uiidMax = 9999999; -var uiid: number = uiidMin; // 当前自动递增界面编号 - -/** 自动获取界面唯一编号 */ -function getUiid(): number { - if (uiid == uiidMax) uiid = uiidMin; - uiid++; - return uiid; -} - /** 界面层级管理器 */ export class LayerManager { /** 界面根节点 */ @@ -29,7 +17,7 @@ export class LayerManager { /** 界面摄像机 */ camera!: Camera; /** 游戏界面特效层 */ - game!: Node; + game!: LayerGame; /** 新手引导层 */ guide!: Node; @@ -43,7 +31,7 @@ export class LayerManager { /** 消息提示控制器,请使用show方法来显示 */ private notify!: LayerNotify; /** UI配置 */ - private configs: { [key: number]: UIConfig } = {}; + private configs: UIConfigMap = {}; /** 界面层集合 - 无自定义类型 */ private uiLayers: Map = new Map(); /** 界面层组件集合 */ @@ -54,6 +42,7 @@ export class LayerManager { this.clsLayers.set(LayerTypeCls.PopUp, LayerPopUp); this.clsLayers.set(LayerTypeCls.Dialog, LayerDialog); this.clsLayers.set(LayerTypeCls.Notify, LayerNotify); + this.clsLayers.set(LayerTypeCls.Game, LayerGame); this.clsLayers.set(LayerTypeCls.Node, null); } @@ -88,13 +77,10 @@ export class LayerManager { let data = config[i]; let layer: Node = null!; if (data.type == LayerTypeCls.Node) { - layer = this.create_node(data.name); switch (data.name) { - case LayerType.Game: - this.game = layer; - break - case LayerType.Guide: - this.guide = layer; + case LayerCustomType.Guide: + this.guide = this.create_node(data.name); + layer = this.guide; break } } @@ -113,6 +99,8 @@ export class LayerManager { this.uiLayers.set(data.name, layer); else if (layer instanceof LayerNotify) this.notify = layer; + else if (layer instanceof LayerGame) + this.game = layer; } } @@ -148,7 +136,7 @@ export class LayerManager { * 初始化所有UI的配置对象 * @param configs 配置对象 */ - init(configs: { [key: number]: UIConfig }): void { + init(configs: UIConfigMap): void { this.configs = configs; } @@ -170,7 +158,6 @@ export class LayerManager { * oops.gui.toast("提示内容"); */ toast(content: string, useI18n: boolean = false) { - this.notify.toast(content, useI18n) } @@ -184,16 +171,28 @@ export class LayerManager { this.notify.waitClose(); } - /** - * 设置界面配置 - * @param uiid 要设置的界面id - * @param config 要设置的配置 - */ - setConfig(uiid: number, config: UIConfig): void { - if (config) - this.configs[uiid] = config; - else - delete this.configs[uiid]; + private getInfo(uiid: Uiid): { key: string; config: UIConfig } { + let key = ""; + let config: UIConfig = null!; + + // 确定 key 和 config + if (typeof uiid === 'object') { + if (uiid.bundle == null) uiid.bundle = resLoader.defaultBundleName; + key = uiid.bundle + "_" + uiid.prefab; + config = this.configs[key]; + if (config == null) { + config = uiid; + this.configs[key] = uiid; + } + } + else { + key = uiid.toString(); + config = this.configs[uiid]; + if (config == null) { + console.error(`打开编号为【${uiid}】的界面失败,配置信息不存在`); + } + } + return { key, config }; } /** @@ -212,16 +211,11 @@ export class LayerManager { }; oops.gui.open(UIID.Loading, null, uic); */ - open(uiid: number, uiArgs: any = null, callbacks?: UICallbacks): void { - const config = this.configs[uiid]; - if (config == null) { - warn(`打开编号为【${uiid}】的界面失败,配置信息不存在`); - return; - } - - let layer = this.uiLayers.get(config.layer); + open(uiid: Uiid, uiArgs: any = null, callbacks?: UICallbacks): void { + let info = this.getInfo(uiid); + let layer = this.uiLayers.get(info.config.layer); if (layer) { - layer.add(uiid, config, uiArgs, callbacks); + layer.add(info.key, info.config, uiArgs, callbacks); } else { console.error(`打开编号为【${uiid}】的界面失败,界面层不存在`); @@ -235,7 +229,7 @@ export class LayerManager { * @example * var node = await oops.gui.openAsync(UIID.Loading); */ - async openAsync(uiid: number, uiArgs: any = null): Promise { + async openAsync(uiid: Uiid, uiArgs: any = null): Promise { return new Promise((resolve, reject) => { const callbacks: UICallbacks = { onAdded: (node: Node, params: any) => { @@ -250,18 +244,53 @@ export class LayerManager { } /** - * 通过界面配置打开一个界面 - * @param config 界面配置数据 - * @returns + * 移除指定标识的窗口 + * @param uiid 窗口唯一标识 + * @param isDestroy 移除后是否释放(默认释放内存) + * @example + * oops.gui.remove(UIID.Loading); */ - openAsyncConfig(config: UIConfig): Promise { - return new Promise(async (resolve, reject) => { - let uiid = getUiid(); - config.auto = true; - this.setConfig(uiid, config); - await oops.gui.openAsync(uiid, { uiid: uiid }); - resolve(uiid); - }); + remove(uiid: Uiid, isDestroy: boolean = true) { + let info = this.getInfo(uiid); + let layer = this.uiLayers.get(info.config.layer); + if (layer) { + layer.remove(info.config.prefab, isDestroy); + } + else { + console.error(`移除编号为【${uiid}】的界面失败,界面层不存在`); + } + } + + /** + * 通过界面节点移除 + * @param node 窗口节点 + * @param isDestroy 移除后是否释放资源(默认释放内存) + * @example + * oops.gui.removeByNode(cc.Node); + */ + removeByNode(node: Node, isDestroy: boolean = true) { + if (node instanceof Node) { + let comp = node.getComponent(LayerUIElement); + if (comp && comp.params) { + // 释放显示的界面 + if (node.parent) { + let uiid = this.configs[comp.params.uiid]; + this.remove(uiid, isDestroy); + } + // 释放缓存中的界面 + else if (isDestroy) { + let layer = this.uiLayers.get(comp.params.config.layer); + if (layer) { + // @ts-ignore 注:不对外使用 + layer.removeCache(comp.params.config.prefab); + } + } + } + else { + warn(`当前删除的 Node 不是通过界面管理器添加的`); + node.destroy(); + } + } } /** @@ -270,14 +299,13 @@ export class LayerManager { * @param openUiId 新打开场景编号 * @param uiArgs 新打开场景参数 */ - replace(removeUiId: number, openUiId: number, uiArgs: any = null) { + replace(removeUiId: Uiid, openUiId: Uiid, uiArgs: any = null) { const callbacks: UICallbacks = { onAdded: (node: Node, params: any) => { this.remove(removeUiId); } }; this.open(openUiId, uiArgs, callbacks); - } /** @@ -286,7 +314,7 @@ export class LayerManager { * @param openUiId 新打开场景编号 * @param uiArgs 新打开场景参数 */ - replaceAsync(removeUiId: number, openUiId: number, uiArgs: any = null): Promise { + replaceAsync(removeUiId: Uiid, openUiId: Uiid, uiArgs: any = null): Promise { return new Promise(async (resolve, reject) => { const node = await this.openAsync(openUiId, uiArgs); if (node) { @@ -305,17 +333,12 @@ export class LayerManager { * @example * oops.gui.has(UIID.Loading); */ - has(uiid: number): boolean { - const config = this.configs[uiid]; - if (config == null) { - warn(`编号为【${uiid}】的界面配置不存在,配置信息不存在`); - return false; - } - - var result = false; - let layer = this.uiLayers.get(config.layer); + has(uiid: Uiid): boolean { + let info = this.getInfo(uiid); + let result = false; + let layer = this.uiLayers.get(info.config.layer); if (layer) { - result = layer.has(config.prefab); + result = layer.has(info.config.prefab); } else { console.error(`验证编号为【${uiid}】的界面失败,界面层不存在`); @@ -330,17 +353,12 @@ export class LayerManager { * @example * oops.gui.has(UIID.Loading); */ - get(uiid: number): Node { - const config = this.configs[uiid]; - if (config == null) { - warn(`编号为【${uiid}】的界面配置不存在,配置信息不存在`); - return null!; - } - + get(uiid: Uiid): Node { + let info = this.getInfo(uiid); let result: Node = null!; - let layer = this.uiLayers.get(config.layer); + let layer = this.uiLayers.get(info.config.layer); if (layer) { - result = layer.get(config.prefab); + result = layer.get(info.config.prefab); } else { console.error(`获取编号为【${uiid}】的界面失败,界面层不存在`); @@ -348,60 +366,6 @@ export class LayerManager { return result; } - /** - * 移除指定标识的窗口 - * @param uiid 窗口唯一标识 - * @param isDestroy 移除后是否释放(默认释放内存) - * @example - * oops.gui.remove(UIID.Loading); - */ - remove(uiid: number, isDestroy: boolean = true) { - const config = this.configs[uiid]; - if (config == null) { - warn(`删除编号为【${uiid}】的界面失败,配置信息不存在`); - return; - } - - let layer = this.uiLayers.get(config.layer); - if (layer) { - layer.remove(config.prefab, isDestroy); - } - else { - console.error(`移除编号为【${uiid}】的界面失败,界面层不存在`); - } - } - - /** - * 通过界面节点移除 - * @param node 窗口节点 - * @param isDestroy 移除后是否释放资源(默认释放内存) - * @example - * oops.gui.removeByNode(cc.Node); - */ - removeByNode(node: Node, isDestroy: boolean = true) { - if (node instanceof Node) { - let comp = node.getComponent(DelegateComponent); - if (comp && comp.vp) { - // 释放显示的界面 - if (node.parent) { - this.remove(comp.vp.uiid, isDestroy); - } - // 释放缓存中的界面 - else if (isDestroy) { - let layer = this.uiLayers.get(comp.vp.config.layer); - if (layer) { - // @ts-ignore 注:不对外使用 - layer.removeCache(comp.vp.config.prefab); - } - } - } - else { - warn(`当前删除的 Node 不是通过界面管理器添加`); - node.destroy(); - } - } - } - /** * 清除所有窗口 * @param isDestroy 移除后是否释放 diff --git a/assets/core/gui/layer/LayerPopup.ts b/assets/core/gui/layer/LayerPopup.ts index c3a397b3a8cf84230fa12313cefd65009c028021..aaa7ab4652e290e4bbf378029f71dac41718840c 100644 --- a/assets/core/gui/layer/LayerPopup.ts +++ b/assets/core/gui/layer/LayerPopup.ts @@ -7,9 +7,9 @@ import { BlockInputEvents, EventTouch, Layers, Node } from "cc"; import { ViewUtil } from "../../utils/ViewUtil"; import { PromptResType } from "../GuiEnum"; -import { ViewParams } from "./Defines"; import { LayerUI } from "./LayerUI"; import { UIConfig } from "./UIConfig"; +import { UIParams } from "./LayerUIElement"; /* 弹窗层,允许同时弹出多个窗口 */ export class LayerPopUp extends LayerUI { @@ -20,7 +20,7 @@ export class LayerPopUp extends LayerUI { constructor(name: string) { super(name); - + this.layer = Layers.Enum.UI_2D; this.on(Node.EventType.CHILD_ADDED, this.onChildAdded, this); this.on(Node.EventType.CHILD_REMOVED, this.onChildRemoved, this); @@ -38,11 +38,11 @@ export class LayerPopUp extends LayerUI { } } - protected async showUi(vp: ViewParams): Promise { - const r = await super.showUi(vp); + protected async showUi(uip: UIParams): Promise { + const r = await super.showUi(uip); if (r) { // 界面加载完成显示时,启动触摸非窗口区域关闭 - this.openVacancyRemove(vp.config); + this.openVacancyRemove(uip.config); // 界面加载完成显示时,层级事件阻挡 this.black.enabled = true; @@ -50,8 +50,8 @@ export class LayerPopUp extends LayerUI { return r; } - protected onCloseWindow(vp: ViewParams) { - super.onCloseWindow(vp); + protected onCloseWindow(uip: UIParams) { + super.onCloseWindow(uip); // 界面关闭后,关闭触摸事件阻挡、关闭触摸非窗口区域关闭、关闭遮罩 this.setBlackDisable(); diff --git a/assets/core/gui/layer/LayerUI.ts b/assets/core/gui/layer/LayerUI.ts index 52c2ccf3ffac172994c2a1eac7127c96df0c8b4c..432a1c0779bbdd014a4083f1be2486cffe5ac8a9 100644 --- a/assets/core/gui/layer/LayerUI.ts +++ b/assets/core/gui/layer/LayerUI.ts @@ -1,8 +1,8 @@ import { instantiate, Node, Prefab, SafeArea, Widget } from "cc"; import { Collection } from "db://oops-framework/libs/collection/Collection"; import { oops } from "../../Oops"; -import { UICallbacks, ViewParams } from "./Defines"; -import { DelegateComponent } from "./DelegateComponent"; +import { Uiid } from "./LayerEnum"; +import { LayerUIElement, UICallbacks, UIParams } from "./LayerUIElement"; import { UIConfig } from "./UIConfig"; /** 界面层对象 */ @@ -10,9 +10,9 @@ export class LayerUI extends Node { /** 全局窗口打开失败 */ onOpenFailure: Function = null!; /** 显示界面节点集合 */ - protected ui_nodes = new Collection(); + protected ui_nodes = new Collection(); /** 被移除的界面缓存数据 */ - protected ui_cache = new Map(); + protected ui_cache = new Map(); /** * UI基础层,允许添加多个预制件节点 @@ -35,26 +35,26 @@ export class LayerUI extends Node { * @param callbacks 回调函数对象,可选 * @returns ture为成功,false为失败 */ - add(uiid: number, config: UIConfig, params?: any, callbacks?: UICallbacks) { + add(uiid: Uiid, config: UIConfig, params?: any, callbacks?: UICallbacks) { if (this.ui_nodes.has(config.prefab)) { console.warn(`路径为【${config.prefab}】的预制重复加载`); return; } // 检查缓存中是否存界面 - let vp = this.ui_cache.get(config.prefab); - if (vp == null) { - vp = new ViewParams(); - vp.uiid = uiid; - vp.config = config; + let uip = this.ui_cache.get(config.prefab); + if (uip == null) { + uip = new UIParams(); + uip.uiid = uiid.toString(); + uip.config = config; } - this.ui_nodes.set(config.prefab, vp); + this.ui_nodes.set(config.prefab, uip); - vp.params = params ?? {}; - vp.callbacks = callbacks ?? {}; - vp.valid = true; + uip.params = params ?? {}; + uip.callbacks = callbacks ?? {}; + uip.valid = true; - this.load(vp, config.bundle) + this.load(uip, config.bundle) } /** @@ -62,7 +62,7 @@ export class LayerUI extends Node { * @param vp 显示参数 * @param bundle 远程资源包名,如果为空就是默认本地资源包 */ - protected async load(vp: ViewParams, bundle?: string) { + protected async load(vp: UIParams, bundle?: string) { // 加载界面资源超时提示 const timerId = setTimeout(this.onLoadingTimeoutGui, oops.config.game.loadingTimeoutGui); @@ -79,8 +79,8 @@ export class LayerUI extends Node { if (vp.config.safeArea) vp.node.addComponent(SafeArea); // 窗口事件委托 - const dc = vp.node.addComponent(DelegateComponent); - dc.vp = vp; + const dc = vp.node.addComponent(LayerUIElement); + dc.params = vp; dc.onCloseWindow = this.onCloseWindow.bind(this); // 显示界面 @@ -103,35 +103,35 @@ export class LayerUI extends Node { } /** 窗口关闭事件 */ - protected onCloseWindow(vp: ViewParams) { + protected onCloseWindow(vp: UIParams) { this.ui_nodes.delete(vp.config.prefab); } /** * 创建界面节点 - * @param vp 视图参数 + * @param uip 视图参数 */ - protected async showUi(vp: ViewParams): Promise { + protected async showUi(uip: UIParams): Promise { // 触发窗口添加事件 - const comp = vp.node.getComponent(DelegateComponent)!; + const comp = uip.node.getComponent(LayerUIElement)!; const r: boolean = await comp.add(); if (r) { - vp.node.parent = this; + uip.node.parent = this; // 标记界面为使用状态 - vp.valid = true; + uip.valid = true; } else { - console.warn(`路径为【${vp.config.prefab}】的自定义预处理逻辑异常.检查预制上绑定的组件中 onAdded 方法,返回true才能正确完成窗口显示流程`); - this.failure(vp); + console.warn(`路径为【${uip.config.prefab}】的自定义预处理逻辑异常.检查预制上绑定的组件中 onAdded 方法,返回true才能正确完成窗口显示流程`); + this.failure(uip); } return r; } /** 打开窗口失败逻辑 */ - protected failure(vp: ViewParams) { - this.onCloseWindow(vp); - vp.callbacks && vp.callbacks.onLoadFailure && vp.callbacks.onLoadFailure(); + protected failure(uip: UIParams) { + this.onCloseWindow(uip); + uip.callbacks && uip.callbacks.onLoadFailure && uip.callbacks.onLoadFailure(); this.onOpenFailure && this.onOpenFailure(); } @@ -158,7 +158,7 @@ export class LayerUI extends Node { } const childNode = vp.node; - const comp = childNode.getComponent(DelegateComponent)!; + const comp = childNode.getComponent(LayerUIElement)!; comp.remove(release); } @@ -173,7 +173,7 @@ export class LayerUI extends Node { this.onCloseWindow(vp); this.ui_cache.delete(prefabPath); const childNode = vp.node; - const comp = childNode.getComponent(DelegateComponent)!; + const comp = childNode.getComponent(LayerUIElement)!; if (comp) { comp.remove(true); } @@ -206,7 +206,7 @@ export class LayerUI extends Node { */ clear(isDestroy: boolean): void { // 清除所有显示的界面 - this.ui_nodes.forEach((value: ViewParams, key: string) => { + this.ui_nodes.forEach((value: UIParams, key: string) => { this.remove(value.config.prefab, isDestroy); value.valid = false; }); @@ -214,7 +214,7 @@ export class LayerUI extends Node { // 清除缓存中的界面 if (isDestroy) { - this.ui_cache.forEach((value: ViewParams, prefabPath: string) => { + this.ui_cache.forEach((value: UIParams, prefabPath: string) => { this.removeCache(prefabPath); }); } diff --git a/assets/core/gui/layer/DelegateComponent.ts b/assets/core/gui/layer/LayerUIElement.ts similarity index 50% rename from assets/core/gui/layer/DelegateComponent.ts rename to assets/core/gui/layer/LayerUIElement.ts index bd2042048bf1b485955c4c26e2cdd990a8d401a9..142a5d4ee586d4fe72b6f3231b8bd46c36073241 100644 --- a/assets/core/gui/layer/DelegateComponent.ts +++ b/assets/core/gui/layer/LayerUIElement.ts @@ -1,127 +1,169 @@ -/* - * @Author: dgflash - * @Date: 2022-09-01 18:00:28 - * @LastEditors: dgflash - * @LastEditTime: 2023-01-09 11:55:03 - */ -import { Component, Node, _decorator } from "cc"; -import { oops } from "../../Oops"; -import { ViewParams } from "./Defines"; - -const { ccclass } = _decorator; - -const EventOnAdded: string = "onAdded"; -const EventOnBeforeRemove: string = "onBeforeRemove"; -const EventOnRemoved: string = "onRemoved"; - -/** 窗口事件触发组件 */ -@ccclass('DelegateComponent') -export class DelegateComponent extends Component { - /** 视图参数 */ - vp: ViewParams = null!; - /** 关闭窗口之前 */ - onCloseWindowBefore: Function = null!; - /** 界面关闭回调 - 包括关闭动画播放完(辅助框架内存业务流程使用) */ - onCloseWindow: Function = null!; - - /** 窗口添加 */ - add(): Promise { - return new Promise(async (resolve, reject) => { - // 触发窗口组件上添加到父节点后的事件 - for (let i = 0; i < this.node.components.length; i++) { - const component: any = this.node.components[i]; - const func = component[EventOnAdded]; - if (func) { - if (await func.call(component, this.vp.params) == false) { - resolve(false); - return; - } - } - } - - // 触发外部窗口显示前的事件(辅助实现自定义动画逻辑) - if (typeof this.vp.callbacks.onAdded === "function") { - this.vp.callbacks.onAdded(this.node, this.vp.params); - } - - resolve(true); - }); - } - - /** 删除节点,该方法只能调用一次,将会触发onBeforeRemoved回调 */ - remove(isDestroy?: boolean) { - if (this.vp.valid) { - // 触发窗口移除舞台之前事件 - this.applyComponentsFunction(this.node, EventOnBeforeRemove, this.vp.params); - - // 通知外部对象窗口组件上移除之前的事件(关闭窗口前的关闭动画处理) - if (typeof this.vp.callbacks.onBeforeRemove === "function") { - this.vp.callbacks.onBeforeRemove( - this.node, - this.onBeforeRemoveNext.bind(this, isDestroy)); - } - else { - this.removed(this.vp, isDestroy); - } - } - else { - this.removed(this.vp, isDestroy); - } - } - - /** 窗口关闭前动画处理完后的回调方法,主要用于释放资源 */ - private onBeforeRemoveNext(isDestroy?: boolean) { - if (this.onCloseWindowBefore) { - this.onCloseWindowBefore(); - this.onCloseWindowBefore = null!; - } - this.removed(this.vp, isDestroy); - } - - /** 窗口组件中触发移除事件与释放窗口对象 */ - private removed(vp: ViewParams, isDestroy?: boolean) { - vp.valid = false; - - if (vp.callbacks && typeof vp.callbacks.onRemoved === "function") { - vp.callbacks.onRemoved(this.node, vp.params); - } - - // 界面移除舞台事件 - this.onCloseWindow && this.onCloseWindow(vp); - - if (isDestroy) { - // 释放界面显示对象 - this.node.destroy(); - - // 释放界面相关资源 - oops.res.release(vp.config.prefab, vp.config.bundle); - - // 释放自动递增编号的界面配置 - if (vp.config.auto) { - oops.gui.setConfig(vp.uiid, null!); - } - - oops.log.logView(`【界面管理】释放【${vp.config.prefab}】界面资源`); - } - else { - this.node.removeFromParent(); - } - - // 触发窗口组件上窗口移除之后的事件 - this.applyComponentsFunction(this.node, EventOnRemoved, this.vp.params); - } - - onDestroy() { - this.vp = null!; - } - - protected applyComponentsFunction(node: Node, funName: string, params: any) { - for (let i = 0; i < node.components.length; i++) { - const component: any = node.components[i]; - const func = component[funName]; - if (func) { - func.call(component, params); - } - } - } +/* + * @Author: dgflash + * @Date: 2022-09-01 18:00:28 + * @LastEditors: dgflash + * @LastEditTime: 2023-01-09 11:55:03 + */ +import { Component, Node, _decorator } from "cc"; +import { oops } from "../../Oops"; +import { UIConfig } from "./UIConfig"; + +const { ccclass } = _decorator; + +const EventOnAdded: string = "onAdded"; +const EventOnBeforeRemove: string = "onBeforeRemove"; +const EventOnRemoved: string = "onRemoved"; + +/** 窗口元素组件 */ +@ccclass('LayerUIElement') +export class LayerUIElement extends Component { + /** 视图参数 */ + params: UIParams = null!; + /** 关闭窗口之前 */ + onCloseWindowBefore: Function = null!; + /** 界面关闭回调 - 包括关闭动画播放完(辅助框架内存业务流程使用) */ + onCloseWindow: Function = null!; + + /** 窗口添加 */ + add(): Promise { + return new Promise(async (resolve, reject) => { + // 触发窗口组件上添加到父节点后的事件 + for (let i = 0; i < this.node.components.length; i++) { + const component: any = this.node.components[i]; + const func = component[EventOnAdded]; + if (func) { + if (await func.call(component, this.params.params) == false) { + resolve(false); + return; + } + } + } + + // 触发外部窗口显示前的事件(辅助实现自定义动画逻辑) + if (typeof this.params.callbacks.onAdded === "function") { + this.params.callbacks.onAdded(this.node, this.params.params); + } + + resolve(true); + }); + } + + /** 删除节点,该方法只能调用一次,将会触发onBeforeRemoved回调 */ + remove(isDestroy?: boolean) { + if (this.params.valid) { + // 触发窗口移除舞台之前事件 + this.applyComponentsFunction(this.node, EventOnBeforeRemove, this.params.params); + + // 通知外部对象窗口组件上移除之前的事件(关闭窗口前的关闭动画处理) + if (typeof this.params.callbacks.onBeforeRemove === "function") { + this.params.callbacks.onBeforeRemove( + this.node, + this.onBeforeRemoveNext.bind(this, isDestroy)); + } + else { + this.removed(this.params, isDestroy); + } + } + else { + this.removed(this.params, isDestroy); + } + } + + /** 窗口关闭前动画处理完后的回调方法,主要用于释放资源 */ + private onBeforeRemoveNext(isDestroy?: boolean) { + if (this.onCloseWindowBefore) { + this.onCloseWindowBefore(); + this.onCloseWindowBefore = null!; + } + this.removed(this.params, isDestroy); + } + + /** 窗口组件中触发移除事件与释放窗口对象 */ + private removed(uip: UIParams, isDestroy?: boolean) { + uip.valid = false; + + if (uip.callbacks && typeof uip.callbacks.onRemoved === "function") { + uip.callbacks.onRemoved(this.node, uip.params); + } + + // 界面移除舞台事件 + this.onCloseWindow && this.onCloseWindow(uip); + + if (isDestroy) { + // 释放界面显示对象 + this.node.destroy(); + + // 释放界面相关资源 + oops.res.release(uip.config.prefab, uip.config.bundle); + + oops.log.logView(`【界面管理】释放【${uip.config.prefab}】界面资源`); + } + else { + this.node.removeFromParent(); + } + + // 触发窗口组件上窗口移除之后的事件 + this.applyComponentsFunction(this.node, EventOnRemoved, this.params.params); + } + + private applyComponentsFunction(node: Node, funName: string, params: any) { + for (let i = 0; i < node.components.length; i++) { + const component: any = node.components[i]; + const func = component[funName]; + if (func) { + func.call(component, params); + } + } + } + + onDestroy() { + this.params = null!; + this.onCloseWindowBefore = null!; + this.onCloseWindow = null!; + } +} + +/** 本类型仅供gui模块内部使用,请勿在功能逻辑中使用 */ +export class UIParams { + /** 界面唯一编号 */ + uiid: string = null!; + /** 界面配置 */ + config: UIConfig = null!; + /** 传递给打开界面的参数 */ + params: any = null!; + /** 窗口事件 */ + callbacks: UICallbacks = null!; + /** 是否在使用状态 */ + valid: boolean = true; + /** 界面根节点 */ + node: Node = null!; +} + +/*** 界面回调参数对象定义 */ +export interface UICallbacks { + /** + * 节点添加到层级以后的回调 + * @param node 当前界面节点 + * @param params 外部传递参数 + */ + onAdded?: (node: Node, params: any) => void, + + /** + * 窗口节点 destroy 之后回调 + * @param node 当前界面节点 + * @param params 外部传递参数 + */ + onRemoved?: (node: Node | null, params: any) => void, + + /** + * 如果指定onBeforeRemoved,则next必须调用,否则节点不会被正常删除。 + * + * 比如希望节点做一个FadeOut然后删除,则可以在`onBeforeRemoved`当中播放action动画,动画结束后调用next + * @param node 当前界面节点 + * @param next 回调方法 + */ + onBeforeRemove?: (node: Node, next: Function) => void, + + /** 网络异常时,窗口加载失败回调 */ + onLoadFailure?: () => void; } \ No newline at end of file diff --git a/assets/core/gui/layer/LayerUIElement.ts.meta b/assets/core/gui/layer/LayerUIElement.ts.meta new file mode 100644 index 0000000000000000000000000000000000000000..3caf651e917ea988a996a30d64ba41077d5d7e1c --- /dev/null +++ b/assets/core/gui/layer/LayerUIElement.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "e7207ab9-8ef7-49af-9c19-84f4d6a2e589", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/core/gui/layer/UIConfig.ts b/assets/core/gui/layer/UIConfig.ts index 50ee75623a9808159e4e00d6825e8faf867f1796..344c4d7581e21e3b87d5f00a9e071331145f9b3d 100644 --- a/assets/core/gui/layer/UIConfig.ts +++ b/assets/core/gui/layer/UIConfig.ts @@ -1,3 +1,4 @@ +import { Node, Vec3 } from "cc"; /** * 界面配置结构体 @@ -10,16 +11,19 @@ export enum UIID { Netinstable } -// 打开界面方式的配置数据 +// 打开界面方式1 export var UIConfigData: { [key: number]: UIConfig } = { [UIID.Loading]: { layer: LayerType.UI, prefab: "loading/prefab/loading", bundle: "resources" }, [UIID.Netinstable]: { layer: LayerType.PopUp, prefab: "common/prefab/netinstable" }, [UIID.Window]: { layer: LayerType.Dialog, prefab: "common/prefab/window" } } + +// 打开界面方式2 +export class InitializeUIConfig { + static Loading = { layer: LayerType.UI, prefab: "gui/loading/loading" } +} */ export interface UIConfig { - /** 是否为自动生成的界面编号 */ - auto?: boolean, /** -----公共属性----- */ /** 远程包名 */ bundle?: string; @@ -40,3 +44,21 @@ export interface UIConfig { /** 界面弹出时的节点排序索引 */ siblingIndex?: number; } + +/** 游戏元素配置 */ +export interface GameElementConfig { + /** 预制资源相对路径 */ + prefab?: string; + /** 游戏元素副节点 */ + parent?: Node; + /** 游戏元素位置 */ + position?: Vec3; + /** 游戏元素旋转 */ + eulerAngles?: Vec3; + /** 游戏元素缩放 */ + scale?: Vec3; + /** 远程包名 */ + bundle?: string; + /** 节点排序索引 */ + siblingIndex?: number; +} diff --git a/assets/core/utils/ArrayUtil.ts b/assets/core/utils/ArrayUtil.ts index 6add054adf9e8e7e3dc8c1ff72e218b922a0e80f..cd269628a7ecccc225c43f2e40d63618ac670c74 100644 --- a/assets/core/utils/ArrayUtil.ts +++ b/assets/core/utils/ArrayUtil.ts @@ -97,10 +97,39 @@ export class ArrayUtil { } /** - * 获取随机数组成员 + * 获取数组中随机成员 * @param array 目标数组 */ static getRandomValueInArray(array: any[]): any { return array[Math.floor(Math.random() * array.length)]; } + + /** + * 随机打乱数组 + * @param array 目标数组 + * @example [1,2,3,4,5] --> [5, 1, 2, 3, 4] + */ + static shuffleArray(array: T[]): T[] { + // 创建一个原数组的副本 + const newArr = [...array]; + + // 使用Fisher-Yates 洗牌算法打乱新数组 + for (let i = newArr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [newArr[i], newArr[j]] = [newArr[j], newArr[i]]; + } + + // 返回打乱后的新数组 + return newArr; + } + + /** + * 获取连续数字数组, 范围在[start, end]之间 + * @param start 开始数字 + * @param end 结束数字 + * @example getNumsBetween(1, 10) => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + */ + static getNumsBetween(start: number, end: number): number[] { + return Array.from({ length: end - start + 1 }, (_, i) => start + i); + } } diff --git a/assets/core/utils/ObjectUtil.ts b/assets/core/utils/ObjectUtil.ts index 673ae20932f3fc4be597024a8007da02de3cd2e7..b90214718df0dd3cea41b13728729ff2c3589792 100644 --- a/assets/core/utils/ObjectUtil.ts +++ b/assets/core/utils/ObjectUtil.ts @@ -60,4 +60,25 @@ export class ObjectUtil { static copy(target: object): object { return JSON.parse(JSON.stringify(target)); } + + /** + * @function 检测是否为非法对象,比如"",null, undefined, NaN, [], {} + * @param {any} obj 任意基础数据对象,如:number、string、array、object等 + * @returns boolean 非法为trre, 否则为false + */ + static isIllegalObject(obj: any): boolean { + // 检查是否为空或未定义 + if (obj == null || obj == undefined) return true; + // 检查是否是特殊值 + if (obj === Infinity || obj === -Infinity) return true; + // 检测是否包含空格的字符串 + if (typeof obj === "string" && obj.trim() === "") return true; + // 检查是否是无效的数字 + if (Number.isNaN(obj)) return true; + // 检查是否是空数组 + if (Array.isArray(obj) && obj.length <= 0) return true; + // 检查是否是空对象 + if (typeof (obj) == "object" && Object.keys(obj).length <= 0) return true; + return false; + } } diff --git a/assets/libs/collection/List.ts b/assets/libs/collection/List.ts deleted file mode 100644 index f1e6ba62a3a5fd0ec62cc8adaeb97a0825568fea..0000000000000000000000000000000000000000 --- a/assets/libs/collection/List.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** 列表 */ -export class List { - private element: Array; - - /** 是否保证元素的唯一性 */ - private only: boolean = false; - - /** 元素数量(内部再增删时会修改这个参数,外部只做计算和绑定使用,切记不可做赋值操作) */ - count: number = 0; - - constructor(only: boolean = true) { - this.only = only; - this.element = []; - } - - /** - * 添加到末尾(注意如果保证唯一性,那么重复时就直接返回) - * @param value - */ - push(value: T): boolean { - if (this.only) { - let index: number = this.element.indexOf(value); - if (index >= 0) { - return false; - } - } - this.element.push(value); - this.count = this.element.length; - return true; - } - - /** - * 添加到列表头部(注意如果保证唯一性,那么重复时就直接返回) - * @param value - * @returns - */ - unshift(value: T): boolean { - if (this.only) { - let index: number = this.element.indexOf(value); - if (index >= 0) { - return false; - } - } - this.element.unshift(value); - this.count = this.element.length; - return true; - } - - /** - * 获取并删除最后一个元素 - * @returns - */ - pop(): T { - if (this.element.length > 0) { - const result = this.element.pop(); - this.count = this.element.length; - return result!; - } - return null!; - } - - /** - * 获取并删除第一个元素 - * @returns - */ - shift(): T { - if (this.element.length > 0) { - const result = this.element.shift(); - this.count = this.element.length; - return result!; - } - return null!; - } - - /** - * 删除指定索引的元素 - * @param index - */ - removeAt(index: number): T { - if (index >= this.element.length) { - throw new Error("删除索引超出范围!"); - } - const result = this.element[index]; - this.element.splice(index, 1); - this.count = this.element.length; - return result; - } - - /** - * 删除元素 - * @param value - */ - remove(value: T): void { - let index: number = this.element.indexOf(value); - if (index < 0) { - throw new Error("要删除的内容不在列表中!" + value); - } - const result = this.element[index]; - this.element.splice(index, 1); - this.count = this.element.length; - } - - /** 移除所有元素 */ - clear(): void { - this.count = 0; - this.element.length = 0; - } - - /** - * 判断是否包含 - * @param value - * @returns - */ - has(value: T): boolean { - return this.find(value) >= 0; - } - - /** - * 查找元素下标 - * @param value - * @returns - */ - find(value: T): number { - return this.element.indexOf(value); - } - - /** - * 查找元素下标 - * @param predicate - * @returns - */ - findIndex(predicate: (value: T, index: number, obj: T[]) => unknown): number { - let index = this.element.findIndex(predicate); - return index; - } - - /** - * 获取指定元素 - * @param index - * @returns - */ - get(index: number): T { - if (index >= this.element.length) { - throw new Error("超出索引范围:" + index + "/" + this.element.length); - } - return this.element[index]; - } - - /** - * 源列表数据(注意不要直接进行增删操作,而是通过List.push....等接口进行操作) - */ - get elements(): Array { - return this.element; - } -} \ No newline at end of file diff --git a/assets/libs/extension/DateExt.ts b/assets/libs/extension/DateExt.ts index 466ba7ccfa79a9c2f534d2e0c26758afad975059..4120fec7afad06488843436f56a30cd54728491f 100644 --- a/assets/libs/extension/DateExt.ts +++ b/assets/libs/extension/DateExt.ts @@ -12,14 +12,27 @@ Date.prototype.format = function (format: string): string { const hours: number = this.getHours(); const minutes: number = this.getMinutes(); const seconds: number = this.getSeconds(); + const milliseconds: number = this.getMilliseconds(); - return format + let r = format .replace('yy', year.toString()) .replace('mm', (month < 10 ? '0' : '') + month) .replace('dd', (day < 10 ? '0' : '') + day) .replace('hh', (hours < 10 ? '0' : '') + hours) .replace('mm', (minutes < 10 ? '0' : '') + minutes) .replace('ss', (seconds < 10 ? '0' : '') + seconds); + + if (milliseconds < 10) { + r = r.replace('ms', '00' + milliseconds); + } + else if (milliseconds < 100) { + r = r.replace('ms', '0' + milliseconds); + } + else { + r = r.replace('ms', milliseconds.toString()); + } + + return r; }; export { }; diff --git a/assets/libs/gui/button/UIButton.ts b/assets/libs/gui/button/UIButton.ts index cadf9997a9c524fb4cff36374c0ad73e7dc7c961..00180da30aecc4ea68c3bba32c3c41c3baf67676 100644 --- a/assets/libs/gui/button/UIButton.ts +++ b/assets/libs/gui/button/UIButton.ts @@ -26,7 +26,6 @@ export default class UIButton extends Button { type: AudioClip }) private effect: AudioClip = null!; - // private effectIds: number[] = []; /** 触摸次数 */ private _touchCount = 0; @@ -74,22 +73,9 @@ export default class UIButton extends Button { } /** 短按触摸音效 */ - protected async playEffect() { + protected playEffect() { if (this.effect) { oops.audio.playEffect(this.effect); - // const effectId = await oops.audio.playEffect(this.effect, resLoader.defaultBundleName, () => { - // this.effectIds.remove(effectId); - // }); - // if (effectId > 0) this.effectIds.push(effectId); } } - - // onDestroy() { - // if (this.effect) { - // this.effectIds.forEach(effectId => { - // console.log(effectId); - // oops.audio.putEffect(effectId, this.effect); - // }); - // } - // } } diff --git a/assets/libs/gui/language/LanguagePack.ts b/assets/libs/gui/language/LanguagePack.ts index f2e56bf26e57f9c9bceb098acd16d37c0f2437ed..53bfadffad03fe039a697d8e22e28caf2b015336 100644 --- a/assets/libs/gui/language/LanguagePack.ts +++ b/assets/libs/gui/language/LanguagePack.ts @@ -42,35 +42,35 @@ export class LanguagePack { } /** 多语言Excel配置表数据 */ - private loadTable(lang: string) { + private loadTable(lang: string): Promise { return new Promise(async (resolve, reject) => { let json = await JsonUtil.loadAsync("Language"); if (json) { LanguageData.language.set(LanguageDataType.Excel, json); Logger.instance.logConfig("config/game/Language", "下载语言包 table 资源"); } - resolve(null); + resolve(); }); } /** 纹理多语言资源 */ - private loadTexture(lang: string) { + private loadTexture(lang: string): Promise { return new Promise((resolve, reject) => { const path = `${LanguageData.path_texture}/${lang}`; resLoader.loadDir(path, (err: any, assets: any) => { if (err) { error(err); - resolve(null); + resolve(); return; } Logger.instance.logConfig(path, "下载语言包 textures 资源"); - resolve(null); + resolve(); }); }); } /** Json格式多语言资源 */ - private loadJson(lang: string) { + private loadJson(lang: string): Promise { return new Promise(async (resolve, reject) => { const path = `${LanguageData.path_json}/${lang}`; const jsonAsset = await resLoader.loadAsync(path, JsonAsset); @@ -79,30 +79,30 @@ export class LanguagePack { Logger.instance.logConfig(path, "下载语言包 json 资源"); } else { - resolve(null); + resolve(); return; } resLoader.load(path, TTFFont, (err: Error | null, font: TTFFont) => { if (err == null) Logger.instance.logConfig(path, "下载语言包 ttf 资源"); LanguageData.font = font; - resolve(null); + resolve(); }); }); } /** SPINE动画多语言资源 */ - private loadSpine(lang: string) { + private loadSpine(lang: string): Promise { return new Promise(async (resolve, reject) => { const path = `${LanguageData.path_spine}/${lang}`; resLoader.loadDir(path, (err: any, assets: any) => { if (err) { error(err); - resolve(null); + resolve(); return; } Logger.instance.logConfig(path, "下载语言包 spine 资源"); - resolve(null); + resolve(); }) }); } diff --git a/assets/libs/model-view/StringFormat.ts b/assets/libs/model-view/StringFormat.ts index aa31c5ab98430983bf2d3f14984f4fdd4cde0991..89e3b527e4f867f464df9b25d8bb172a6997e12d 100644 --- a/assets/libs/model-view/StringFormat.ts +++ b/assets/libs/model-view/StringFormat.ts @@ -21,19 +21,21 @@ class StringFormat { switch (func) { case 'int': res = this.int(value); break; case 'fix': res = this.fix(value, num); break; - case 'kmbt': res = this.KMBT(value); break; + case 'kmbt': res = this.kmbt(value); break; case 'per': res = this.per(value, num); break; case 'sep': res = this.sep(value); break; - default: - break; - } + case 'stamp': res = this.time_stamp(value); break; + case 'ms': res = this.time_ms(value); break; + case 'hms': res = this.time_hms(value); break; + case 'hmss': res = this.time_hmss(value); break; + default: break; + } } else { switch (func) { case 'limit': res = this.limit(value, num); break; - default: break; } @@ -43,36 +45,36 @@ class StringFormat { return res as string; } - // 将数字按分号显示 + /** 将数字按分号显示 */ private sep(value: number) { let num = Math.round(value).toString(); return num.replace(new RegExp('(\\d)(?=(\\d{3})+$)', 'ig'), "$1,"); } - // 将数字按分显示 00:00 显示 (ms制) - private time_m(value: number) { - //todo + /** 将数字按分显示 00:00 显示 (分:秒) */ + private time_ms(value: number) { + return new Date(value).format('mm:ss'); } - // 将数字按秒显示 00:00:00 显示 (ms制) - private time_s(value: number) { - //todo + /** 将数字按秒显示 00:00:00 显示 (时:分:秒) */ + private time_hms(value: number) { + return new Date(value).format('hh:mm:ss'); } - // 将数字按 0:00:00:000 显示 (ms制) - private time_ms(value: number) { - //todo + /** 将数字按 0:00:00:000 显示 (时:分:秒:毫秒) */ + private time_hmss(value: number) { + return new Date(value).format('hh:mm:ss:ms'); } - // 将时间戳显示为详细的内容 - private timeStamp(value: number) { - //todo - return new Date(value).toString() + /** 将时间戳显示为详细的内容 */ + private time_stamp(value: number) { + return new Date(value).format('yy-mm-dd hh:mm:ss'); } /** [value:int] 将取值0~1 变成 1~100,可以指定修饰的小数位数 */ private per(value: number, fd: number) { - return Math.round(value * 100).toFixed(fd); + let r = value * 100; + return r.toFixed(fd); } /** [value:int] 将取值变成整数 */ @@ -80,7 +82,7 @@ class StringFormat { return Math.round(value); } - /** [value:fix2]数值转换为小数*/ + /** [value:fix2]数值转换为小数 */ private fix(value: number, fd: number) { return value.toFixed(fd) } @@ -91,7 +93,7 @@ class StringFormat { } /** 将数字缩短显示为KMBT单位 大写,目前只支持英文 */ - private KMBT(value: number, lang: string = 'en') { + private kmbt(value: number, lang: string = 'en') { //10^4=万, 10^8=亿,10^12=兆,10^16=京, let counts = [1000, 1000000, 1000000000, 1000000000000]; let units = ['', 'K', 'M', 'B', 'T']; @@ -99,8 +101,8 @@ class StringFormat { switch (lang) { case 'zh': //10^4=万, 10^8=亿,10^12=兆,10^16=京, - let counts = [10000, 100000000, 1000000000000, 10000000000000000]; - let units = ['', '万', '亿', '兆', '京']; + counts = [10000, 100000000, 1000000000000, 10000000000000000]; + units = ['', '万', '亿', '兆', '京']; break; default: @@ -110,12 +112,12 @@ class StringFormat { return this.compressUnit(value, counts, units, 2); } - //压缩任意单位的数字,后缀加上单位文字 + /** 压缩任意单位的数字,后缀加上单位文字 */ private compressUnit(value: any, valueArr: number[], unitArr: string[], fixNum: number = 2): string { let counts = valueArr; let units = unitArr; let res: string = ""; - let index; + let index: number; for (index = 0; index < counts.length; index++) { const e = counts[index]; if (value < e) { @@ -127,11 +129,10 @@ class StringFormat { } break; } - } return res + units[index]; } } -/**格式化处理函数 */ +/** 格式化处理函数 */ export let StringFormatFunction = new StringFormat(); \ No newline at end of file diff --git a/assets/libs/model-view/VMCompsEdit.ts b/assets/libs/model-view/VMCompsEdit.ts index fa7e786f8d71c807da34b955ac33feb9999b826c..d8c622817e81437613864bebeb275f1f3142a807 100644 --- a/assets/libs/model-view/VMCompsEdit.ts +++ b/assets/libs/model-view/VMCompsEdit.ts @@ -1,4 +1,4 @@ -import { CCString, Component, Enum, log, Node, _decorator } from "cc"; +import { _decorator, CCString, Component, Enum, log, Node } from "cc"; import { VMEnv } from "./VMEnv"; const { ccclass, property, executeInEditMode, menu, help } = _decorator; @@ -36,10 +36,10 @@ export default class MVCompsEdit extends Component { return this.actionType === ACTION_MODE.SEARCH_COMPONENT; } }) - public get findTrigger() { + get findTrigger() { return false; } - public set findTrigger(v: boolean) { + set findTrigger(v: boolean) { this.setComponents(0); } @@ -50,10 +50,10 @@ export default class MVCompsEdit extends Component { return this.actionType === ACTION_MODE.ENABLE_COMPONENT; } }) - public get enableTrigger() { + get enableTrigger() { return false; } - public set enableTrigger(v: boolean) { + set enableTrigger(v: boolean) { this.setComponents(1); } @@ -64,10 +64,10 @@ export default class MVCompsEdit extends Component { return this.actionType === ACTION_MODE.ENABLE_COMPONENT; } }) - public get disableTrigger() { + get disableTrigger() { return false; } - public set disableTrigger(v: boolean) { + set disableTrigger(v: boolean) { this.setComponents(2); } @@ -88,10 +88,10 @@ export default class MVCompsEdit extends Component { return this.allowDelete && this.actionType === ACTION_MODE.DELETE_COMPONENT; } }) - public get deleteTrigger() { + get deleteTrigger() { return false; } - public set deleteTrigger(v: boolean) { + set deleteTrigger(v: boolean) { this.setComponents(3); } @@ -102,10 +102,10 @@ export default class MVCompsEdit extends Component { return this.actionType === ACTION_MODE.REPLACE_WATCH_PATH; } }) - public get replaceTrigger() { + get replaceTrigger() { return false; } - public set replaceTrigger(v: boolean) { + set replaceTrigger(v: boolean) { this.setComponents(4); } @@ -269,7 +269,7 @@ export default class MVCompsEdit extends Component { getNodePath(node: Node) { let parent = node; - let array = []; + let array: string[] = []; while (parent) { let p = parent.getParent(); if (p) { @@ -282,4 +282,4 @@ export default class MVCompsEdit extends Component { } return array.reverse().join('/'); } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/VMCustom.ts b/assets/libs/model-view/VMCustom.ts index 47ba4bfed37a134adbb3a9e557fbbdad84577213..5db423056bb8229400ae61568b5ba8eb6e2e2253 100644 --- a/assets/libs/model-view/VMCustom.ts +++ b/assets/libs/model-view/VMCustom.ts @@ -18,7 +18,6 @@ const COMP_ARRAY_CHECK = [ ['cc.Toggle', 'isChecked', true] ]; - /** * [VM-Custom] * 自定义数值监听, 可以快速对该节点上任意一个组件上的属性进行双向绑定 @@ -170,4 +169,4 @@ export class VMCustom extends VMBase { this._oldValue = this.getComponentValue(); this.onValueController(newValue, oldValue); } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/VMEnv.ts b/assets/libs/model-view/VMEnv.ts index addb3e80318ff7d0d960d6028b8628d81a12e388..0992efb623754808a2636102da41762b6492b97a 100644 --- a/assets/libs/model-view/VMEnv.ts +++ b/assets/libs/model-view/VMEnv.ts @@ -4,7 +4,6 @@ import { EDITOR } from "cc/env"; export class VMEnv { /** 编辑状态 */ static get editor() { - // @ts-ignore - return EDITOR && !cc.GAME_VIEW; + return EDITOR; } } \ No newline at end of file diff --git a/assets/libs/model-view/VMEvent.ts b/assets/libs/model-view/VMEvent.ts index dbdd1dd428d8cd70969bd2d6241a66fbacdf392f..4086d80444ba3cc7fab494883373ab566bbd9e00 100644 --- a/assets/libs/model-view/VMEvent.ts +++ b/assets/libs/model-view/VMEvent.ts @@ -35,7 +35,7 @@ export default class VMEvent extends VMBase { @property({ tooltip: '使用模板模式,可以使用多路径监听' }) - public templateMode: boolean = false; + templateMode: boolean = false; @property({ tooltip: '监听获取值的路径', @@ -65,7 +65,7 @@ export default class VMEvent extends VMBase { tooltip: '过滤模式,会根据条件过滤掉时间的触发', type: Enum(FILTER_MODE) }) - public filterMode: FILTER_MODE = FILTER_MODE.none; + filterMode: FILTER_MODE = FILTER_MODE.none; @property({ visible: function () { @@ -73,7 +73,7 @@ export default class VMEvent extends VMBase { return this.filterMode !== FILTER_MODE.none } }) - public compareValue: string = ''; + compareValue: string = ''; @property([EventHandler]) changeEvents: EventHandler[] = []; diff --git a/assets/libs/model-view/VMLabel.ts b/assets/libs/model-view/VMLabel.ts index b5c89f8a736d0c6dc06e9caf167504f4ae32d25b..6b4da4e5847775cc9ab7d948e5766f10da2fb176 100644 --- a/assets/libs/model-view/VMLabel.ts +++ b/assets/libs/model-view/VMLabel.ts @@ -186,4 +186,4 @@ export default class VMLabel extends VMBase { return false; } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/VMModify.ts b/assets/libs/model-view/VMModify.ts index 44305accafd59118d9036ade6947c40051778ef9..cd07d421b43f4257a0165233bf5b7b90ff4ab7b6 100644 --- a/assets/libs/model-view/VMModify.ts +++ b/assets/libs/model-view/VMModify.ts @@ -146,4 +146,4 @@ export default class VMModify extends VMBase { if (int) { a = Math.round(a) } this.VM.setValue(this.watchPath, this.clampValue(a)); } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/VMParent.ts b/assets/libs/model-view/VMParent.ts index a120605d599877a3dfe349679a4fecd4f860a5b3..f176b497170dfb8c2fc37c7b042891a50f0dd5b5 100644 --- a/assets/libs/model-view/VMParent.ts +++ b/assets/libs/model-view/VMParent.ts @@ -36,6 +36,8 @@ export default class VMParent extends GameComponent { */ onLoad() { if (this.data == null) return; + this.onBind(); + this.tag = '_temp' + '<' + this.node.uuid.replace('.', '') + '>'; VM.add(this.data, this.tag); // log(VM['_mvs'],this.tag) @@ -47,8 +49,6 @@ export default class VMParent extends GameComponent { this.replaceVMPath(comp, this.tag) } // console.groupEnd() - - this.onBind(); } /**在 onLoad 完成 和 start() 之前调用,你可以在这里进行初始化数据等操作 */ @@ -119,4 +119,4 @@ export default class VMParent extends GameComponent { super.onDestroy(); } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/VMProgress.ts b/assets/libs/model-view/VMProgress.ts index abfe5140e2416dd3cb5089f5d4a7d17b774cea65..d41a043d49e1dcfe65c10ab41ae52f4fe9960ea9 100644 --- a/assets/libs/model-view/VMProgress.ts +++ b/assets/libs/model-view/VMProgress.ts @@ -95,4 +95,4 @@ export default class VMProgress extends VMCustom { this.setComponentValue(value); } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/VMState.ts b/assets/libs/model-view/VMState.ts index 76bcf04e645c8f0e4b401cfc2856975ed373ebf8..057a3293d0c9ad685d4c227af647c244568c5028 100644 --- a/assets/libs/model-view/VMState.ts +++ b/assets/libs/model-view/VMState.ts @@ -295,4 +295,4 @@ export default class VMState extends VMBase { return false; } -} +} \ No newline at end of file diff --git a/assets/libs/model-view/ViewModel.ts b/assets/libs/model-view/ViewModel.ts index c92759febd39bc5b8f2c850dd2b9dfe73e340959..1d1af9c56064d6ebf0bb1a4445f2a737b7067c3a 100644 --- a/assets/libs/model-view/ViewModel.ts +++ b/assets/libs/model-view/ViewModel.ts @@ -34,7 +34,7 @@ function getValueFromPath(obj: any, path: string, def?: any, tag: string | null /** * ModelViewer 类 */ -class ViewModel{ +class ViewModel { constructor(data: T, tag: string) { new JsonOb(data, this._callback.bind(this)); this.$data = data; @@ -149,6 +149,9 @@ class VMManager { */ getValue(path: string, def?: any): any { path = path.trim(); // 防止空格,自动剔除 + + if (path === '') return ''; + let rs = path.split('.'); if (rs.length < 2) { console.error('Get Value Cant find path:' + path); return; }; let vm = this.get(rs[0]); @@ -215,10 +218,8 @@ class VMManager { } } -// 整数、小数、时间、缩写 - /** * VM管理对象,使用文档: - * https://github.com/wsssheep/cocos_creator_mvvm_tools/blob/master/docs/ViewModelScript.md + * https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12037849&doc_id=2873565 */ export let VM = new VMManager(); \ No newline at end of file diff --git a/assets/libs/model-view/ui/BhvRollNumber.ts b/assets/libs/model-view/ui/BhvRollNumber.ts index 9b33aeba53594676ff6fcbd9db2f1629dbbf4437..e39da040b374ec281f52794e3be284e574da49d3 100644 --- a/assets/libs/model-view/ui/BhvRollNumber.ts +++ b/assets/libs/model-view/ui/BhvRollNumber.ts @@ -45,10 +45,10 @@ export class BhvRollNumber extends Component { @property({ tooltip: '滚动的目标值' }) - public get targetValue(): number { + get targetValue(): number { return this._targetValue; } - public set targetValue(v: number) { + set targetValue(v: number) { this._targetValue = v; this.scroll();//数据变动了就开始滚动 } diff --git a/assets/libs/model-view/ui/BhvSwitchPage.ts b/assets/libs/model-view/ui/BhvSwitchPage.ts index ae58d21e23a1940a2f8284c4065dab71804fbff0..244b31d49d72b48bf71f74fe257b3cdc127a8ce7 100644 --- a/assets/libs/model-view/ui/BhvSwitchPage.ts +++ b/assets/libs/model-view/ui/BhvSwitchPage.ts @@ -13,13 +13,13 @@ export class BhvSwitchPage extends Component { @property private _index: number = 0; - public get index(): number { + get index(): number { return this._index; } @property({ type: CCInteger }) - public set index(v: number) { + set index(v: number) { if (this.isChanging) return; v = Math.round(v); let count = this.node.children.length - 1; @@ -49,7 +49,7 @@ export class BhvSwitchPage extends Component { private _isChanging: boolean = false; /**只读,是否在changing 的状态 */ - public get isChanging(): boolean { + get isChanging(): boolean { return this._isChanging; } @@ -85,7 +85,7 @@ export class BhvSwitchPage extends Component { showNode.active = true; } - public next(): boolean { + next(): boolean { if (this.isChanging) { return false; } @@ -95,7 +95,7 @@ export class BhvSwitchPage extends Component { } } - public previous(): boolean { + previous(): boolean { if (this.isChanging) { return false; } @@ -105,7 +105,7 @@ export class BhvSwitchPage extends Component { } } - public setEventIndex(e: any, index: any): boolean { + setEventIndex(e: any, index: any): boolean { if (this.index >= 0 && this.index != null && this.isChanging === false) { this.index = index; return true; @@ -114,4 +114,4 @@ export class BhvSwitchPage extends Component { return false; } } -} +} \ No newline at end of file diff --git a/assets/libs/network.meta b/assets/libs/network.meta index 56f3c5eaa89b66f73c42179c9fe16ae069a030d1..bafd121704ed1b6ac0e4f7ecebdfe39003a1cc5d 100644 --- a/assets/libs/network.meta +++ b/assets/libs/network.meta @@ -2,11 +2,8 @@ "ver": "1.2.0", "importer": "directory", "imported": true, - "uuid": "2b3f701c-188a-4390-94a5-b5d3781f54f9", + "uuid": "c378c808-8d92-4f96-9eb6-a122b5f1716f", "files": [], "subMetas": {}, - "userData": { - "compressionType": {}, - "isRemoteBundle": {} - } + "userData": {} } diff --git a/assets/libs/network/HttpRequest.ts b/assets/libs/network/HttpRequest.ts index 48bff56ef449df64e199865da6ecc307af1e0195..3a62b60b6f9cc6b233b9c96a0eff971fd01e856b 100644 --- a/assets/libs/network/HttpRequest.ts +++ b/assets/libs/network/HttpRequest.ts @@ -1,317 +1,317 @@ -/* - * @Author: dgflash - * @Date: 2022-09-01 18:00:28 - * @LastEditors: dgflash - * @LastEditTime: 2022-09-09 18:10:50 - */ -import { error, warn } from "cc"; - -/** - * 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容 - * https://store.cocos.com/app/detail/5877 - */ - -/** 当前请求地址集合 */ -var urls: any = {}; -/** 请求参数 */ -var reqparams: any = {}; - -type HttpCallback = (ret: HttpReturn) => void; - -/** 请求事件 */ -export enum HttpEvent { - /** 断网 */ - NO_NETWORK = "http_request_no_network", - /** 未知错误 */ - UNKNOWN_ERROR = "http_request_unknown_error", - /** 请求超时 */ - TIMEOUT = "http_request_timout" -} - -/** - * HTTP请求返回值 - */ -export class HttpReturn { - /** 是否请求成功 */ - isSucc: boolean = false; - /** 请求返回数据 */ - res?: any; - /** 请求错误数据 */ - err?: any; -} - -/** HTTP请求 */ -export class HttpRequest { - /** 服务器地址 */ - server: string = "http://127.0.0.1/"; - /** 请求超时时间 */ - timeout: number = 10000; - /** 自定义请求头信息 */ - private header: Map = new Map(); - - /** - * 添加自定义请求头信息 - * @param name 信息名 - * @param value 信息值 - */ - addHeader(name: string, value: string) { - this.header.set(name, value); - } - - /** - * HTTP GET请求 - * @param name 协议名 - * @param onComplete 请求完整回调方法 - * @param params 查询参数 - * @example - var param = '{"uid":12345}' - var complete = (ret: HttpReturn) => { - console.log(ret.res); - } - oops.http.getWithParams(name, complete, param); - */ - get(name: string, onComplete: HttpCallback, params: any = null) { - this.sendRequest(name, params, false, onComplete) - } - - /** - * HTTP GET请求 - * @param name 协议名 - * @param params 查询参数 - * @example - var txt = await oops.http.getAsync(name); - if (txt.isSucc) { - console.log(txt.res); - } - */ - getAsync(name: string, params: any = null): Promise { - return new Promise((resolve, reject) => { - this.sendRequest(name, params, false, (ret: HttpReturn) => { - resolve(ret); - }) - }); - } - - /** - * HTTP GET请求非文本格式数据 - * @param name 协议名 - * @param onComplete 请求完整回调方法 - * @param params 查询参数 - */ - getByArraybuffer(name: string, onComplete: HttpCallback, params: any = null) { - this.sendRequest(name, params, false, onComplete, 'arraybuffer', false); - } - - /** - * HTTP GET请求非文本格式数据 - * @param name 协议名 - * @param params 查询参数 - * @returns Promise - */ - getAsyncByArraybuffer(name: string, params: any = null): Promise { - return new Promise((resolve, reject) => { - this.sendRequest(name, params, false, (ret: HttpReturn) => { - resolve(ret); - }, 'arraybuffer', false); - }); - } - - /** - * HTTP POST请求 - * @param name 协议名 - * @param params 查询参数 - * @param onComplete 请求完整回调方法 - * @example - var param = '{"LoginCode":"donggang_dev","Password":"e10adc3949ba59abbe56e057f20f883e"}' - var complete = (ret: HttpReturn) => { - console.log(ret.res); - } - oops.http.post(name, complete, param); - */ - post(name: string, onComplete: HttpCallback, params: any = null) { - this.sendRequest(name, params, true, onComplete); - } - - /** - * HTTP POST请求 - * @param name 协议名 - * @param params 查询参数 - */ - postAsync(name: string, params: any = null): Promise { - return new Promise((resolve, reject) => { - this.sendRequest(name, params, true, (ret: HttpReturn) => { - resolve(ret); - }); - }); - } - - /** - * 取消请求中的请求 - * @param name 协议名 - */ - abort(name: string) { - var xhr = urls[this.server + name]; - if (xhr) { - xhr.abort(); - } - } - - /** - * 获得字符串形式的参数 - * @param params 参数对象 - * @returns 参数字符串 - */ - private getParamString(params: any) { - var result = ""; - for (var name in params) { - let data = params[name]; - if (data instanceof Object) { - for (var key in data) - result += `${key}=${data[key]}&`; - } - else { - result += `${name}=${data}&`; - } - } - return result.substring(0, result.length - 1); - } - - /** - * Http请求 - * @param name(string) 请求地址 - * @param params(JSON) 请求参数 - * @param isPost(boolen) 是否为POST方式 - * @param callback(function) 请求成功回调 - * @param responseType(string) 响应类型 - * @param isOpenTimeout(boolean) 是否触发请求超时错误 - */ - private sendRequest(name: string, - params: any, - isPost: boolean, - onComplete: HttpCallback, - responseType?: string, - isOpenTimeout: boolean = true) { - if (name == null || name == '') { - error("请求地址不能为空"); - return; - } - - var url: string, newUrl: string, paramsStr: string = ""; - if (name.toLocaleLowerCase().indexOf("http") == 0) { - url = name; - } - else { - url = this.server + name; - } - - if (params) { - paramsStr = this.getParamString(params); - if (url.indexOf("?") > -1) - newUrl = url + "&" + paramsStr; - else - newUrl = url + "?" + paramsStr; - } - else { - newUrl = url; - } - - if (urls[newUrl] != null && reqparams[newUrl] == paramsStr) { - warn(`地址【${url}】已正在请求中,不能重复请求`); - return; - } - - var xhr = new XMLHttpRequest(); - - // 防重复请求功能 - urls[newUrl] = xhr; - reqparams[newUrl] = paramsStr; - - if (isPost) { - xhr.open("POST", url); - } - else { - xhr.open("GET", newUrl); - } - - // 添加自定义请求头信息 - for (const [key, value] of this.header) { - xhr.setRequestHeader(key, value); - } - // xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); - // xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); - - var data: any = {}; - data.url = url; - data.params = params; - - // 请求超时 - if (isOpenTimeout) { - xhr.timeout = this.timeout; - xhr.ontimeout = () => { - this.deleteCache(newUrl); - - ret.isSucc = false; - ret.err = HttpEvent.TIMEOUT; // 超时 - onComplete(data); - } - } - - // 响应结果 - var ret: HttpReturn = new HttpReturn(); - - xhr.onloadend = () => { - if (xhr.status == 500) { - this.deleteCache(newUrl); - - ret.isSucc = false; - ret.err = HttpEvent.NO_NETWORK; // 断网 - onComplete(ret); - } - } - - xhr.onerror = () => { - this.deleteCache(newUrl); - - ret.isSucc = false; - if (xhr.readyState == 0 || xhr.readyState == 1 || xhr.status == 0) { - ret.err = HttpEvent.NO_NETWORK; // 断网 - } - else { - ret.err = HttpEvent.UNKNOWN_ERROR; // 未知错误 - } - - onComplete(ret); - }; - - xhr.onreadystatechange = () => { - if (xhr.readyState != 4) return; - - this.deleteCache(newUrl); - - if (xhr.status == 200 && onComplete) { - ret.isSucc = true; - if (responseType == 'arraybuffer') { - xhr.responseType = responseType; // 加载非文本格式 - ret.res = xhr.response; - } - else { - ret.res = JSON.parse(xhr.response); - } - onComplete(ret); - } - }; - - // 发送请求 - if (params == null || params == "") { - xhr.send(); - } - else { - xhr.send(paramsStr); - } - } - - private deleteCache(url: string) { - delete urls[url]; - delete reqparams[url]; - } +/* + * @Author: dgflash + * @Date: 2022-09-01 18:00:28 + * @LastEditors: dgflash + * @LastEditTime: 2022-09-09 18:10:50 + */ +import { error, warn } from "cc"; + +/** + * 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容 + * https://store.cocos.com/app/detail/5877 + */ + +/** 当前请求地址集合 */ +var urls: any = {}; +/** 请求参数 */ +var reqparams: any = {}; + +type HttpCallback = (ret: HttpReturn) => void; + +/** 请求事件 */ +export enum HttpEvent { + /** 断网 */ + NO_NETWORK = "http_request_no_network", + /** 未知错误 */ + UNKNOWN_ERROR = "http_request_unknown_error", + /** 请求超时 */ + TIMEOUT = "http_request_timout" +} + +/** + * HTTP请求返回值 + */ +export class HttpReturn { + /** 是否请求成功 */ + isSucc: boolean = false; + /** 请求返回数据 */ + res?: any; + /** 请求错误数据 */ + err?: any; +} + +/** HTTP请求 */ +export class HttpRequest { + /** 服务器地址 */ + server: string = "http://127.0.0.1/"; + /** 请求超时时间 */ + timeout: number = 10000; + /** 自定义请求头信息 */ + private header: Map = new Map(); + + /** + * 添加自定义请求头信息 + * @param name 信息名 + * @param value 信息值 + */ + addHeader(name: string, value: string) { + this.header.set(name, value); + } + + /** + * HTTP GET请求 + * @param name 协议名 + * @param onComplete 请求完整回调方法 + * @param params 查询参数 + * @example + var param = '{"uid":12345}' + var complete = (ret: HttpReturn) => { + console.log(ret.res); + } + oops.http.getWithParams(name, complete, param); + */ + get(name: string, onComplete: HttpCallback, params: any = null) { + this.sendRequest(name, params, false, onComplete) + } + + /** + * HTTP GET请求 + * @param name 协议名 + * @param params 查询参数 + * @example + var txt = await oops.http.getAsync(name); + if (txt.isSucc) { + console.log(txt.res); + } + */ + getAsync(name: string, params: any = null): Promise { + return new Promise((resolve, reject) => { + this.sendRequest(name, params, false, (ret: HttpReturn) => { + resolve(ret); + }) + }); + } + + /** + * HTTP GET请求非文本格式数据 + * @param name 协议名 + * @param onComplete 请求完整回调方法 + * @param params 查询参数 + */ + getByArraybuffer(name: string, onComplete: HttpCallback, params: any = null) { + this.sendRequest(name, params, false, onComplete, 'arraybuffer', false); + } + + /** + * HTTP GET请求非文本格式数据 + * @param name 协议名 + * @param params 查询参数 + * @returns Promise + */ + getAsyncByArraybuffer(name: string, params: any = null): Promise { + return new Promise((resolve, reject) => { + this.sendRequest(name, params, false, (ret: HttpReturn) => { + resolve(ret); + }, 'arraybuffer', false); + }); + } + + /** + * HTTP POST请求 + * @param name 协议名 + * @param params 查询参数 + * @param onComplete 请求完整回调方法 + * @example + var param = '{"LoginCode":"donggang_dev","Password":"e10adc3949ba59abbe56e057f20f883e"}' + var complete = (ret: HttpReturn) => { + console.log(ret.res); + } + oops.http.post(name, complete, param); + */ + post(name: string, onComplete: HttpCallback, params: any = null) { + this.sendRequest(name, params, true, onComplete); + } + + /** + * HTTP POST请求 + * @param name 协议名 + * @param params 查询参数 + */ + postAsync(name: string, params: any = null): Promise { + return new Promise((resolve, reject) => { + this.sendRequest(name, params, true, (ret: HttpReturn) => { + resolve(ret); + }); + }); + } + + /** + * 取消请求中的请求 + * @param name 协议名 + */ + abort(name: string) { + var xhr = urls[this.server + name]; + if (xhr) { + xhr.abort(); + } + } + + /** + * 获得字符串形式的参数 + * @param params 参数对象 + * @returns 参数字符串 + */ + private getParamString(params: any) { + var result = ""; + for (var name in params) { + let data = params[name]; + if (data instanceof Object) { + for (var key in data) + result += `${key}=${data[key]}&`; + } + else { + result += `${name}=${data}&`; + } + } + return result.substring(0, result.length - 1); + } + + /** + * Http请求 + * @param name(string) 请求地址 + * @param params(JSON) 请求参数 + * @param isPost(boolen) 是否为POST方式 + * @param callback(function) 请求成功回调 + * @param responseType(string) 响应类型 + * @param isOpenTimeout(boolean) 是否触发请求超时错误 + */ + private sendRequest(name: string, + params: any, + isPost: boolean, + onComplete: HttpCallback, + responseType?: string, + isOpenTimeout: boolean = true) { + if (name == null || name == '') { + error("请求地址不能为空"); + return; + } + + var url: string, newUrl: string, paramsStr: string = ""; + if (name.toLocaleLowerCase().indexOf("http") == 0) { + url = name; + } + else { + url = this.server + name; + } + + if (params) { + paramsStr = this.getParamString(params); + if (url.indexOf("?") > -1) + newUrl = url + "&" + paramsStr; + else + newUrl = url + "?" + paramsStr; + } + else { + newUrl = url; + } + + if (urls[newUrl] != null && reqparams[newUrl] == paramsStr) { + warn(`地址【${url}】已正在请求中,不能重复请求`); + return; + } + + var xhr = new XMLHttpRequest(); + + // 防重复请求功能 + urls[newUrl] = xhr; + reqparams[newUrl] = paramsStr; + + if (isPost) { + xhr.open("POST", url); + } + else { + xhr.open("GET", newUrl); + } + + // 添加自定义请求头信息 + for (const [key, value] of this.header) { + xhr.setRequestHeader(key, value); + } + // xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); + // xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); + + var data: any = {}; + data.url = url; + data.params = params; + + // 请求超时 + if (isOpenTimeout) { + xhr.timeout = this.timeout; + xhr.ontimeout = () => { + this.deleteCache(newUrl); + + ret.isSucc = false; + ret.err = HttpEvent.TIMEOUT; // 超时 + onComplete(data); + } + } + + // 响应结果 + var ret: HttpReturn = new HttpReturn(); + + xhr.onloadend = () => { + if (xhr.status == 500) { + this.deleteCache(newUrl); + + ret.isSucc = false; + ret.err = HttpEvent.NO_NETWORK; // 断网 + onComplete(ret); + } + } + + xhr.onerror = () => { + this.deleteCache(newUrl); + + ret.isSucc = false; + if (xhr.readyState == 0 || xhr.readyState == 1 || xhr.status == 0) { + ret.err = HttpEvent.NO_NETWORK; // 断网 + } + else { + ret.err = HttpEvent.UNKNOWN_ERROR; // 未知错误 + } + + onComplete(ret); + }; + + xhr.onreadystatechange = () => { + if (xhr.readyState != 4) return; + + this.deleteCache(newUrl); + + if (xhr.status == 200 && onComplete) { + ret.isSucc = true; + if (responseType == 'arraybuffer') { + xhr.responseType = responseType; // 加载非文本格式 + ret.res = xhr.response; + } + else { + ret.res = JSON.parse(xhr.response); + } + onComplete(ret); + } + }; + + // 发送请求 + if (params == null || params == "") { + xhr.send(); + } + else { + xhr.send(paramsStr); + } + } + + private deleteCache(url: string) { + delete urls[url]; + delete reqparams[url]; + } } \ No newline at end of file diff --git a/assets/libs/network/NetInterface.ts b/assets/libs/network/NetInterface.ts index 90dab00be637add9534ac9f1d39b4fca7b84aa8d..4bf7608c948a279627e635249d8864a49a31fbd4 100644 --- a/assets/libs/network/NetInterface.ts +++ b/assets/libs/network/NetInterface.ts @@ -1,92 +1,92 @@ -/* - * @Author: dgflash - * @Date: 2022-09-01 18:00:28 - * @LastEditors: dgflash - * @LastEditTime: 2022-09-09 18:31:18 - */ - -/* - * 网络相关接口定义 - */ -export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView); -export type NetCallFunc = (data: any) => void; - -/** 请求协议 */ -export interface IRequestProtocol { - /** 协议命令编号 */ - cmd: string, - /** 回调方法名 */ - callback?: string, - /** 是否压缩 */ - isCompress: boolean, - /** 渠道编号 */ - channelid: number, - /** 消息内容 */ - data?: any; -} - -/** 响应协议 */ -export interface IResponseProtocol { - /** 响应协议状态码 */ - code: number, - /** 数据是否压缩 */ - isCompress: boolean, - /** 协议数据 */ - data?: any, - /** 协议回调方法名 */ - callback?: string -} - -/** 回调对象 */ -export interface CallbackObject { - target: any, // 回调对象,不为null时调用target.callback(xxx) - callback: NetCallFunc, // 回调函数 -} - -/** 请求对象 */ -export interface RequestObject { - buffer: NetData, // 请求的Buffer - rspCmd: string, // 等待响应指令 - rspObject: CallbackObject | null, // 等待响应的回调对象 -} - -/** 协议辅助接口 */ -export interface IProtocolHelper { - /** 返回包头长度 */ - getHeadlen(): number; - /** 返回一个心跳包 */ - getHearbeat(): NetData; - /** 返回整个包的长度 */ - getPackageLen(msg: NetData): number; - /** 检查包数据是否合法(避免客户端报错崩溃) */ - checkResponsePackage(msg: IResponseProtocol): boolean; - /** 处理请求包数据 */ - handlerRequestPackage(reqProtocol: IRequestProtocol): string; - /** 处理响应包数据 */ - handlerResponsePackage(respProtocol: IResponseProtocol): boolean; - /** 返回包的id或协议类型 */ - getPackageId(msg: IResponseProtocol): string; -} - -export type SocketFunc = (event: any) => void; -export type MessageFunc = (msg: NetData) => void; - -/** Socket接口 */ -export interface ISocket { - onConnected: SocketFunc | null; // 连接回调 - onMessage: MessageFunc | null; // 消息回调 - onError: SocketFunc | null; // 错误回调 - onClosed: SocketFunc | null; // 关闭回调 - - connect(options: any): any; // 连接接口 - send(buffer: NetData): number; // 数据发送接口 - close(code?: number, reason?: string): void; // 关闭接口 -} - -/** 网络提示接口 */ -export interface INetworkTips { - connectTips(isShow: boolean): void; - reconnectTips(isShow: boolean): void; - requestTips(isShow: boolean): void; - responseErrorCode(code: number): void; +/* + * @Author: dgflash + * @Date: 2022-09-01 18:00:28 + * @LastEditors: dgflash + * @LastEditTime: 2022-09-09 18:31:18 + */ + +/* + * 网络相关接口定义 + */ +export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView); +export type NetCallFunc = (data: any) => void; + +/** 请求协议 */ +export interface IRequestProtocol { + /** 协议命令编号 */ + cmd: string, + /** 回调方法名 */ + callback?: string, + /** 是否压缩 */ + isCompress: boolean, + /** 渠道编号 */ + channelid: number, + /** 消息内容 */ + data?: any; +} + +/** 响应协议 */ +export interface IResponseProtocol { + /** 响应协议状态码 */ + code: number, + /** 数据是否压缩 */ + isCompress: boolean, + /** 协议数据 */ + data?: any, + /** 协议回调方法名 */ + callback?: string +} + +/** 回调对象 */ +export interface CallbackObject { + target: any, // 回调对象,不为null时调用target.callback(xxx) + callback: NetCallFunc, // 回调函数 +} + +/** 请求对象 */ +export interface RequestObject { + buffer: NetData, // 请求的Buffer + rspCmd: string, // 等待响应指令 + rspObject: CallbackObject | null, // 等待响应的回调对象 +} + +/** 协议辅助接口 */ +export interface IProtocolHelper { + /** 返回包头长度 */ + getHeadlen(): number; + /** 返回一个心跳包 */ + getHearbeat(): NetData; + /** 返回整个包的长度 */ + getPackageLen(msg: NetData): number; + /** 检查包数据是否合法(避免客户端报错崩溃) */ + checkResponsePackage(msg: IResponseProtocol): boolean; + /** 处理请求包数据 */ + handlerRequestPackage(reqProtocol: IRequestProtocol): string; + /** 处理响应包数据 */ + handlerResponsePackage(respProtocol: IResponseProtocol): boolean; + /** 返回包的id或协议类型 */ + getPackageId(msg: IResponseProtocol): string; +} + +export type SocketFunc = (event: any) => void; +export type MessageFunc = (msg: NetData) => void; + +/** Socket接口 */ +export interface ISocket { + onConnected: SocketFunc | null; // 连接回调 + onMessage: MessageFunc | null; // 消息回调 + onError: SocketFunc | null; // 错误回调 + onClosed: SocketFunc | null; // 关闭回调 + + connect(options: any): any; // 连接接口 + send(buffer: NetData): number; // 数据发送接口 + close(code?: number, reason?: string): void; // 关闭接口 +} + +/** 网络提示接口 */ +export interface INetworkTips { + connectTips(isShow: boolean): void; + reconnectTips(isShow: boolean): void; + requestTips(isShow: boolean): void; + responseErrorCode(code: number): void; } \ No newline at end of file diff --git a/assets/libs/network/NetManager.ts b/assets/libs/network/NetManager.ts index 753ce04f58b96f4ab0e8a1bd67cdc622b1d8123a..b88ca8874cf35fc71104babbe37eaddc3c0c0ac5 100644 --- a/assets/libs/network/NetManager.ts +++ b/assets/libs/network/NetManager.ts @@ -1,148 +1,148 @@ -/* - * @Author: dgflash - * @Date: 2022-09-01 18:00:28 - * @LastEditors: dgflash - * @LastEditTime: 2022-09-09 18:10:50 - */ -import { CallbackObject, IRequestProtocol, NetData } from "./NetInterface"; -import { NetConnectOptions, NetNode } from "./NetNode"; - -/** - * 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容 - * https://store.cocos.com/app/detail/5877 - */ - -/* - * 网络节点管理类 - */ -export class NetManager { - private static _instance: NetManager; - protected _channels: { [key: number]: NetNode } = {}; - - /** 网络管理单例对象 */ - static getInstance(): NetManager { - if (!this._instance) { - this._instance = new NetManager(); - } - return this._instance; - } - - /** - * 添加网络节点 - * @param node 网络节点 - * @param channelId 通道编号 - * @example - // 游戏服务器心跳协议 - class GameProtocol extends NetProtocolPako { - // 自定义心跳协议 - getHearbeat(): NetData { - return '{"action":"LoginAction","method":"heart","data":"null","callback":"LoginAction_heart"}'; - } - } - - var net = new NetNodeGame(); - var ws = new WebSock(); // WebSocket 网络连接对象 - var gp = new GameProtocol(); // 网络通讯协议对象 - var gt = new NetGameTips() // 网络提示对象 - net.init(ws, gp, gt); - NetManager.getInstance().setNetNode(net, NetChannelType.Game); - */ - setNetNode(node: NetNode, channelId: number = 0) { - this._channels[channelId] = node; - } - - /** 移除Node */ - removeNetNode(channelId: number) { - delete this._channels[channelId]; - } - - /** - * 网络节点连接服务器 - * @param options 连接参数 - * @param channelId 通道编号 - * @example - var options = { - url: 'ws://127.0.0.1:3000', - autoReconnect: 0 // -1 永久重连,0不自动重连,其他正整数为自动重试次数 - } - NetManager.getInstance().connect(options, NetChannelType.Game); - */ - connect(options: NetConnectOptions, channelId: number = 0): boolean { - if (this._channels[channelId]) { - return this._channels[channelId].connect(options); - } - return false; - } - - /** 节点连接发送数据*/ - send(buf: NetData, force: boolean = false, channelId: number = 0): number { - let node = this._channels[channelId]; - if (node) { - return node!.send(buf, force); - } - return -1; - } - - /** - * 发起请求,并在在结果返回时调用指定好的回调函数 - * @param reqProtocol 请求协议 - * @param rspObject 回调对象 - * @param showTips 是否触发请求提示 - * @param force 是否强制发送 - * @param channelId 通道编号 - * @example - let protocol: IRequestProtocol = { - action: action, - method: method, - data: JSON.stringify(data), - isCompress: this.isCompress, - channelid: netConfig.channelid - } - return this.request(protocol, rspObject, showTips, force); - */ - request(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0) { - let node = this._channels[channelId]; - if (node) { - node.request(reqProtocol, rspObject, showTips, force); - } - } - - /** - * 同request功能一致,但在request之前会先判断队列中是否已有rspCmd,如有重复的则直接返回 - * @param reqProtocol 请求协议 - * @param rspObject 回调对象 - * @param showTips 是否触发请求提示 - * @param force 是否强制发送 - * @param channelId 通道编号 - * @example - let protocol: IRequestProtocol = { - action: action, - method: method, - data: JSON.stringify(data), - isCompress: this.isCompress, - channelid: netConfig.channelid - } - return this.request(protocol, rspObject, showTips, force); - */ - requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0): boolean { - let node = this._channels[channelId]; - if (node) { - return node.requestUnique(reqProtocol, rspObject, showTips, force); - } - return false; - } - - /** - * 节点网络断开 - * @param code 关闭码 - * @param reason 关闭原因 - * @param channelId 通道编号 - * @example - * NetManager.getInstance().close(undefined, undefined, NetChannelType.Game); - */ - close(code?: number, reason?: string, channelId: number = 0) { - if (this._channels[channelId]) { - return this._channels[channelId].closeSocket(code, reason); - } - } +/* + * @Author: dgflash + * @Date: 2022-09-01 18:00:28 + * @LastEditors: dgflash + * @LastEditTime: 2022-09-09 18:10:50 + */ +import { CallbackObject, IRequestProtocol, NetData } from "./NetInterface"; +import { NetConnectOptions, NetNode } from "./NetNode"; + +/** + * 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容 + * https://store.cocos.com/app/detail/5877 + */ + +/* + * 网络节点管理类 + */ +export class NetManager { + private static _instance: NetManager; + protected _channels: { [key: number]: NetNode } = {}; + + /** 网络管理单例对象 */ + static getInstance(): NetManager { + if (!this._instance) { + this._instance = new NetManager(); + } + return this._instance; + } + + /** + * 添加网络节点 + * @param node 网络节点 + * @param channelId 通道编号 + * @example + // 游戏服务器心跳协议 + class GameProtocol extends NetProtocolPako { + // 自定义心跳协议 + getHearbeat(): NetData { + return '{"action":"LoginAction","method":"heart","data":"null","callback":"LoginAction_heart"}'; + } + } + + var net = new NetNodeGame(); + var ws = new WebSock(); // WebSocket 网络连接对象 + var gp = new GameProtocol(); // 网络通讯协议对象 + var gt = new NetGameTips() // 网络提示对象 + net.init(ws, gp, gt); + NetManager.getInstance().setNetNode(net, NetChannelType.Game); + */ + setNetNode(node: NetNode, channelId: number = 0) { + this._channels[channelId] = node; + } + + /** 移除Node */ + removeNetNode(channelId: number) { + delete this._channels[channelId]; + } + + /** + * 网络节点连接服务器 + * @param options 连接参数 + * @param channelId 通道编号 + * @example + var options = { + url: 'ws://127.0.0.1:3000', + autoReconnect: 0 // -1 永久重连,0不自动重连,其他正整数为自动重试次数 + } + NetManager.getInstance().connect(options, NetChannelType.Game); + */ + connect(options: NetConnectOptions, channelId: number = 0): boolean { + if (this._channels[channelId]) { + return this._channels[channelId].connect(options); + } + return false; + } + + /** 节点连接发送数据*/ + send(buf: NetData, force: boolean = false, channelId: number = 0): number { + let node = this._channels[channelId]; + if (node) { + return node!.send(buf, force); + } + return -1; + } + + /** + * 发起请求,并在在结果返回时调用指定好的回调函数 + * @param reqProtocol 请求协议 + * @param rspObject 回调对象 + * @param showTips 是否触发请求提示 + * @param force 是否强制发送 + * @param channelId 通道编号 + * @example + let protocol: IRequestProtocol = { + action: action, + method: method, + data: JSON.stringify(data), + isCompress: this.isCompress, + channelid: netConfig.channelid + } + return this.request(protocol, rspObject, showTips, force); + */ + request(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0) { + let node = this._channels[channelId]; + if (node) { + node.request(reqProtocol, rspObject, showTips, force); + } + } + + /** + * 同request功能一致,但在request之前会先判断队列中是否已有rspCmd,如有重复的则直接返回 + * @param reqProtocol 请求协议 + * @param rspObject 回调对象 + * @param showTips 是否触发请求提示 + * @param force 是否强制发送 + * @param channelId 通道编号 + * @example + let protocol: IRequestProtocol = { + action: action, + method: method, + data: JSON.stringify(data), + isCompress: this.isCompress, + channelid: netConfig.channelid + } + return this.request(protocol, rspObject, showTips, force); + */ + requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0): boolean { + let node = this._channels[channelId]; + if (node) { + return node.requestUnique(reqProtocol, rspObject, showTips, force); + } + return false; + } + + /** + * 节点网络断开 + * @param code 关闭码 + * @param reason 关闭原因 + * @param channelId 通道编号 + * @example + * NetManager.getInstance().close(undefined, undefined, NetChannelType.Game); + */ + close(code?: number, reason?: string, channelId: number = 0) { + if (this._channels[channelId]) { + return this._channels[channelId].closeSocket(code, reason); + } + } } \ No newline at end of file diff --git a/assets/libs/network/NetNode.ts b/assets/libs/network/NetNode.ts index c17259ae68e82721402ac6c938667189a603c517..76862cf69c86464f6b7dbc347b84eafb8decd7e7 100644 --- a/assets/libs/network/NetNode.ts +++ b/assets/libs/network/NetNode.ts @@ -1,491 +1,491 @@ -import { error, warn } from "cc"; -import { Logger } from "../../core/common/log/Logger"; -import { CallbackObject, INetworkTips, IProtocolHelper, IRequestProtocol, ISocket, NetCallFunc, NetData, RequestObject } from "./NetInterface"; - -/* -* CocosCreator网络节点基类,以及网络相关接口定义 -* 1. 网络连接、断开、请求发送、数据接收等基础功能 -* 2. 心跳机制 -* 3. 断线重连 + 请求重发 -* 4. 调用网络屏蔽层 -*/ - -type ExecuterFunc = (callback: CallbackObject, buffer: NetData) => void; -type CheckFunc = (checkedFunc: VoidFunc) => void; -type VoidFunc = () => void; -type BoolFunc = () => boolean; - -var NetNodeStateStrs = ["已关闭", "连接中", "验证中", "可传输数据"]; - -/** 网络提示类型枚举 */ -export enum NetTipsType { - Connecting, - ReConnecting, - Requesting, -} - -/** 网络状态枚举 */ -export enum NetNodeState { - Closed, // 已关闭 - Connecting, // 连接中 - Checking, // 验证中 - Working, // 可传输数据 -} - -/** 网络连接参数 */ -export interface NetConnectOptions { - host?: string, // 地址 - port?: number, // 端口 - url?: string, // url,与地址+端口二选一 - autoReconnect?: number, // -1 永久重连,0不自动重连,其他正整数为自动重试次数 -} - -/** 网络节点 */ -export class NetNode { - protected _connectOptions: NetConnectOptions | null = null; - protected _autoReconnect: number = 0; - protected _isSocketInit: boolean = false; // Socket是否初始化过 - protected _isSocketOpen: boolean = false; // Socket是否连接成功过 - protected _state: NetNodeState = NetNodeState.Closed; // 节点当前状态 - protected _socket: ISocket | null = null; // Socket对象(可能是原生socket、websocket、wx.socket...) - - protected _networkTips: INetworkTips | null = null; // 网络提示ui对象(请求提示、断线重连提示等) - protected _protocolHelper: IProtocolHelper | null = null; // 包解析对象 - protected _connectedCallback: CheckFunc | null = null; // 连接完成回调 - protected _disconnectCallback: BoolFunc | null = null; // 断线回调 - protected _callbackExecuter: ExecuterFunc | null = null; // 回调执行 - - protected _keepAliveTimer: any = null; // 心跳定时器 - protected _receiveMsgTimer: any = null; // 接收数据定时器 - protected _reconnectTimer: any = null; // 重连定时器 - protected _heartTime: number = 10000; // 心跳间隔 - protected _receiveTime: number = 6000000; // 多久没收到数据断开 - protected _reconnetTimeOut: number = 8000000; // 重连间隔 - protected _requests: RequestObject[] = Array(); // 请求列表 - protected _listener: { [key: string]: CallbackObject[] | null } = {} // 监听者列表 - - /********************** 网络相关处理 *********************/ - init(socket: ISocket, protocol: IProtocolHelper, networkTips: INetworkTips | null = null, execFunc: ExecuterFunc | null = null) { - Logger.instance.logNet(`网络初始化`); - this._socket = socket; - this._protocolHelper = protocol; - this._networkTips = networkTips; - this._callbackExecuter = execFunc ? execFunc : (callback: CallbackObject, buffer: NetData) => { - callback.callback.call(callback.target, buffer); - } - } - - /** - * 请求连接服务器 - * @param options 连接参数 - */ - connect(options: NetConnectOptions): boolean { - if (this._socket && this._state == NetNodeState.Closed) { - if (!this._isSocketInit) { - this.initSocket(); - } - this._state = NetNodeState.Connecting; - if (!this._socket.connect(options)) { - this.updateNetTips(NetTipsType.Connecting, false); - return false; - } - if (this._connectOptions == null && typeof options.autoReconnect == "number") { - this._autoReconnect = options.autoReconnect; - } - this._connectOptions = options; - this.updateNetTips(NetTipsType.Connecting, true); - return true; - } - return false; - } - - protected initSocket() { - if (this._socket) { - this._socket.onConnected = (event) => { this.onConnected(event) }; - this._socket.onMessage = (msg) => { this.onMessage(msg) }; - this._socket.onError = (event) => { this.onError(event) }; - this._socket.onClosed = (event) => { this.onClosed(event) }; - this._isSocketInit = true; - } - } - - protected updateNetTips(tipsType: NetTipsType, isShow: boolean) { - if (this._networkTips) { - if (tipsType == NetTipsType.Requesting) { - this._networkTips.requestTips(isShow); - } - else if (tipsType == NetTipsType.Connecting) { - this._networkTips.connectTips(isShow); - } - else if (tipsType == NetTipsType.ReConnecting) { - this._networkTips.reconnectTips(isShow); - } - } - } - - /** 网络连接成功 */ - protected onConnected(event: any) { - Logger.instance.logNet("网络已连接") - this._isSocketOpen = true; - // 如果设置了鉴权回调,在连接完成后进入鉴权阶段,等待鉴权结束 - if (this._connectedCallback !== null) { - this._state = NetNodeState.Checking; - this._connectedCallback(() => { this.onChecked() }); - } - else { - this.onChecked(); - } - Logger.instance.logNet(`网络已连接当前状态为【${NetNodeStateStrs[this._state]}】`); - } - - /** 连接验证成功,进入工作状态 */ - protected onChecked() { - Logger.instance.logNet("连接验证成功,进入工作状态"); - this._state = NetNodeState.Working; - // 关闭连接或重连中的状态显示 - this.updateNetTips(NetTipsType.Connecting, false); - this.updateNetTips(NetTipsType.ReConnecting, false); - - // 重发待发送信息 - var requests = this._requests.concat(); - if (requests.length > 0) { - Logger.instance.logNet(`请求【${this._requests.length}】个待发送的信息`); - - for (var i = 0; i < requests.length;) { - let req = requests[i]; - this._socket!.send(req.buffer); - if (req.rspObject == null || req.rspCmd != "") { - requests.splice(i, 1); - } - else { - ++i; - } - } - // 如果还有等待返回的请求,启动网络请求层 - this.updateNetTips(NetTipsType.Requesting, this._requests.length > 0); - } - } - - /** 接收到一个完整的消息包 */ - protected onMessage(msg: any): void { - // Logger.logNet(`接受消息状态为【${NetNodeStateStrs[this._state]}】`); - - var json = JSON.parse(msg); - - // 进行头部的校验(实际包长与头部长度是否匹配) - if (!this._protocolHelper!.checkResponsePackage(json)) { - error(`校验接受消息数据异常`); - return; - } - - // 处理相应包数据 - if (!this._protocolHelper!.handlerResponsePackage(json)) { - if (this._networkTips) - this._networkTips.responseErrorCode(json.code); - } - - // 接受到数据,重新定时收数据计时器 - this.resetReceiveMsgTimer(); - // 重置心跳包发送器 - this.resetHearbeatTimer(); - // 触发消息执行 - let rspCmd = this._protocolHelper!.getPackageId(json); - - Logger.instance.logNet(`接受到命令【${rspCmd}】的消息`); - // 优先触发request队列 - if (this._requests.length > 0) { - for (let reqIdx in this._requests) { - let req = this._requests[reqIdx]; - if (req.rspCmd == rspCmd && req.rspObject) { - Logger.instance.logNet(`触发请求命令【${rspCmd}】的回调`); - this._callbackExecuter!(req.rspObject, json.data); - this._requests.splice(parseInt(reqIdx), 1); - break; - } - } - - if (this._requests.length == 0) { - this.updateNetTips(NetTipsType.Requesting, false); - } - else { - Logger.instance.logNet(`请求队列中还有【${this._requests.length}】个请求在等待`); - } - } - - let listeners = this._listener[rspCmd]; - if (null != listeners) { - for (const rsp of listeners) { - Logger.instance.logNet(`触发监听命令【${rspCmd}】的回调`); - this._callbackExecuter!(rsp, json.data); - } - } - } - - protected onError(event: any) { - error(event); - } - - protected onClosed(event: any) { - this.clearTimer(); - - // 执行断线回调,返回false表示不进行重连 - if (this._disconnectCallback && !this._disconnectCallback()) { - Logger.instance.logNet(`断开连接`); - return; - } - - // 自动重连 - if (this.isAutoReconnect()) { - this.updateNetTips(NetTipsType.ReConnecting, true); - this._reconnectTimer = setTimeout(() => { - this._socket!.close(); - this._state = NetNodeState.Closed; - this.connect(this._connectOptions!); - if (this._autoReconnect > 0) { - this._autoReconnect -= 1; - } - }, this._reconnetTimeOut); - } - else { - this._state = NetNodeState.Closed; - } - } - - /** - * 断开网络 - * @param code 关闭码 - * @param reason 关闭原因 - */ - close(code?: number, reason?: string) { - this.clearTimer(); - this._listener = {}; - this._requests.length = 0; - if (this._networkTips) { - this._networkTips.connectTips(false); - this._networkTips.reconnectTips(false); - this._networkTips.requestTips(false); - } - if (this._socket) { - this._socket.close(code, reason); - } - else { - this._state = NetNodeState.Closed; - } - } - - /** - * 只是关闭Socket套接字(仍然重用缓存与当前状态) - * @param code 关闭码 - * @param reason 关闭原因 - */ - closeSocket(code?: number, reason?: string) { - if (this._socket) { - this._socket.close(code, reason); - } - } - - /** - * 发起请求,如果当前处于重连中,进入缓存列表等待重连完成后发送 - * @param buf 网络数据 - * @param force 是否强制发送 - */ - send(buf: NetData, force: boolean = false): number { - if (this._state == NetNodeState.Working || force) { - return this._socket!.send(buf); - } - else if (this._state == NetNodeState.Checking || - this._state == NetNodeState.Connecting) { - this._requests.push({ - buffer: buf, - rspCmd: "", - rspObject: null - }); - Logger.instance.logNet(`当前状态为【${NetNodeStateStrs[this._state]}】,繁忙并缓冲发送数据`); - return 0; - } - else { - error(`当前状态为【${NetNodeStateStrs[this._state]}】,请求错误`); - return -1; - } - } - - /** - * 发起请求,并进入缓存列表 - * @param reqProtocol 请求协议 - * @param rspObject 回调对象 - * @param showTips 是否触发请求提示 - * @param force 是否强制发送 - */ - request(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) { - var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol); - this.base_request(reqProtocol, rspCmd, rspObject, showTips, force); - } - - /** - * 唯一request,确保没有同一响应的请求(避免一个请求重复发送,netTips界面的屏蔽也是一个好的方法) - * @param reqProtocol 请求协议 - * @param rspObject 回调对象 - * @param showTips 是否触发请求提示 - * @param force 是否强制发送 - */ - requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false): boolean { - var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol); - - for (let i = 0; i < this._requests.length; ++i) { - if (this._requests[i].rspCmd == rspCmd) { - Logger.instance.logNet(`命令【${rspCmd}】重复请求`); - return false; - } - } - - this.base_request(reqProtocol, rspCmd, rspObject, showTips, force); - return true; - } - - private base_request(reqProtocol: IRequestProtocol, rspCmd: string, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) { - var buf: NetData = JSON.stringify(reqProtocol); // 转为二进制流发送 - - if (this._state == NetNodeState.Working || force) { - this._socket!.send(buf); - } - - Logger.instance.logNet(`队列命令为【${rspCmd}】的请求,等待请求数据的回调`); - - // 进入发送缓存列表 - this._requests.push({ - buffer: buf, rspCmd, rspObject - }); - // 启动网络请求层 - if (showTips) { - this.updateNetTips(NetTipsType.Requesting, true); - } - } - - /********************** 回调相关处理 *********************/ - /** - * 设置一个唯一的服务器推送监听 - * @param cmd 命令字串 - * @param callback 回调方法 - * @param target 目标对象 - */ - setResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean { - if (callback == null) { - error(`命令为【${cmd}】设置响应处理程序错误`); - return false; - } - this._listener[cmd] = [{ target, callback }]; - return true; - } - - /** - * 可添加多个同类返回消息的监听 - * @param cmd 命令字串 - * @param callback 回调方法 - * @param target 目标对象 - * @returns - */ - addResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean { - if (callback == null) { - error(`命令为【${cmd}】添加响应处理程序错误`); - return false; - } - let rspObject = { target, callback }; - if (null == this._listener[cmd]) { - this._listener[cmd] = [rspObject]; - } - else { - let index = this.getNetListenersIndex(cmd, rspObject); - if (-1 == index) { - this._listener[cmd]!.push(rspObject); - } - } - return true; - } - - /** - * 删除一个监听中指定子回调 - * @param cmd 命令字串 - * @param callback 回调方法 - * @param target 目标对象 - */ - removeResponeHandler(cmd: string, callback: NetCallFunc, target?: any) { - if (null != this._listener[cmd] && callback != null) { - let index = this.getNetListenersIndex(cmd, { target, callback }); - if (-1 != index) { - this._listener[cmd]!.splice(index, 1); - } - } - } - - /** - * 清除所有监听或指定命令的监听 - * @param cmd 命令字串(默认不填为清除所有) - */ - cleanListeners(cmd: string = "") { - if (cmd == "") { - this._listener = {} - } - else { - delete this._listener[cmd]; - } - } - - protected getNetListenersIndex(cmd: string, rspObject: CallbackObject): number { - let index = -1; - for (let i = 0; i < this._listener[cmd]!.length; i++) { - let iterator = this._listener[cmd]![i]; - if (iterator.callback == rspObject.callback - && iterator.target == rspObject.target) { - index = i; - break; - } - } - return index; - } - - /********************** 心跳、超时相关处理 *********************/ - protected resetReceiveMsgTimer() { - if (this._receiveMsgTimer !== null) { - clearTimeout(this._receiveMsgTimer); - } - - this._receiveMsgTimer = setTimeout(() => { - warn("接收消息定时器关闭网络连接"); - this._socket!.close(); - }, this._receiveTime); - } - - protected resetHearbeatTimer() { - if (this._keepAliveTimer !== null) { - clearTimeout(this._keepAliveTimer); - } - - this._keepAliveTimer = setTimeout(() => { - Logger.instance.logNet("网络节点保持活跃发送心跳信息"); - this.send(this._protocolHelper!.getHearbeat()); - }, this._heartTime); - } - - protected clearTimer() { - if (this._receiveMsgTimer !== null) { - clearTimeout(this._receiveMsgTimer); - } - if (this._keepAliveTimer !== null) { - clearTimeout(this._keepAliveTimer); - } - if (this._reconnectTimer !== null) { - clearTimeout(this._reconnectTimer); - } - } - - /** 是否自动重连接 */ - isAutoReconnect() { - return this._autoReconnect != 0; - } - - /** 拒绝重新连接 */ - rejectReconnect() { - this._autoReconnect = 0; - this.clearTimer(); - } +import { error, warn } from "cc"; +import { Logger } from "../../core/common/log/Logger"; +import { CallbackObject, INetworkTips, IProtocolHelper, IRequestProtocol, ISocket, NetCallFunc, NetData, RequestObject } from "./NetInterface"; + +/* +* CocosCreator网络节点基类,以及网络相关接口定义 +* 1. 网络连接、断开、请求发送、数据接收等基础功能 +* 2. 心跳机制 +* 3. 断线重连 + 请求重发 +* 4. 调用网络屏蔽层 +*/ + +type ExecuterFunc = (callback: CallbackObject, buffer: NetData) => void; +type CheckFunc = (checkedFunc: VoidFunc) => void; +type VoidFunc = () => void; +type BoolFunc = () => boolean; + +var NetNodeStateStrs = ["已关闭", "连接中", "验证中", "可传输数据"]; + +/** 网络提示类型枚举 */ +export enum NetTipsType { + Connecting, + ReConnecting, + Requesting, +} + +/** 网络状态枚举 */ +export enum NetNodeState { + Closed, // 已关闭 + Connecting, // 连接中 + Checking, // 验证中 + Working, // 可传输数据 +} + +/** 网络连接参数 */ +export interface NetConnectOptions { + host?: string, // 地址 + port?: number, // 端口 + url?: string, // url,与地址+端口二选一 + autoReconnect?: number, // -1 永久重连,0不自动重连,其他正整数为自动重试次数 +} + +/** 网络节点 */ +export class NetNode { + protected _connectOptions: NetConnectOptions | null = null; + protected _autoReconnect: number = 0; + protected _isSocketInit: boolean = false; // Socket是否初始化过 + protected _isSocketOpen: boolean = false; // Socket是否连接成功过 + protected _state: NetNodeState = NetNodeState.Closed; // 节点当前状态 + protected _socket: ISocket | null = null; // Socket对象(可能是原生socket、websocket、wx.socket...) + + protected _networkTips: INetworkTips | null = null; // 网络提示ui对象(请求提示、断线重连提示等) + protected _protocolHelper: IProtocolHelper | null = null; // 包解析对象 + protected _connectedCallback: CheckFunc | null = null; // 连接完成回调 + protected _disconnectCallback: BoolFunc | null = null; // 断线回调 + protected _callbackExecuter: ExecuterFunc | null = null; // 回调执行 + + protected _keepAliveTimer: any = null; // 心跳定时器 + protected _receiveMsgTimer: any = null; // 接收数据定时器 + protected _reconnectTimer: any = null; // 重连定时器 + protected _heartTime: number = 10000; // 心跳间隔 + protected _receiveTime: number = 6000000; // 多久没收到数据断开 + protected _reconnetTimeOut: number = 8000000; // 重连间隔 + protected _requests: RequestObject[] = Array(); // 请求列表 + protected _listener: { [key: string]: CallbackObject[] | null } = {} // 监听者列表 + + /********************** 网络相关处理 *********************/ + init(socket: ISocket, protocol: IProtocolHelper, networkTips: INetworkTips | null = null, execFunc: ExecuterFunc | null = null) { + Logger.instance.logNet(`网络初始化`); + this._socket = socket; + this._protocolHelper = protocol; + this._networkTips = networkTips; + this._callbackExecuter = execFunc ? execFunc : (callback: CallbackObject, buffer: NetData) => { + callback.callback.call(callback.target, buffer); + } + } + + /** + * 请求连接服务器 + * @param options 连接参数 + */ + connect(options: NetConnectOptions): boolean { + if (this._socket && this._state == NetNodeState.Closed) { + if (!this._isSocketInit) { + this.initSocket(); + } + this._state = NetNodeState.Connecting; + if (!this._socket.connect(options)) { + this.updateNetTips(NetTipsType.Connecting, false); + return false; + } + if (this._connectOptions == null && typeof options.autoReconnect == "number") { + this._autoReconnect = options.autoReconnect; + } + this._connectOptions = options; + this.updateNetTips(NetTipsType.Connecting, true); + return true; + } + return false; + } + + protected initSocket() { + if (this._socket) { + this._socket.onConnected = (event) => { this.onConnected(event) }; + this._socket.onMessage = (msg) => { this.onMessage(msg) }; + this._socket.onError = (event) => { this.onError(event) }; + this._socket.onClosed = (event) => { this.onClosed(event) }; + this._isSocketInit = true; + } + } + + protected updateNetTips(tipsType: NetTipsType, isShow: boolean) { + if (this._networkTips) { + if (tipsType == NetTipsType.Requesting) { + this._networkTips.requestTips(isShow); + } + else if (tipsType == NetTipsType.Connecting) { + this._networkTips.connectTips(isShow); + } + else if (tipsType == NetTipsType.ReConnecting) { + this._networkTips.reconnectTips(isShow); + } + } + } + + /** 网络连接成功 */ + protected onConnected(event: any) { + Logger.instance.logNet("网络已连接") + this._isSocketOpen = true; + // 如果设置了鉴权回调,在连接完成后进入鉴权阶段,等待鉴权结束 + if (this._connectedCallback !== null) { + this._state = NetNodeState.Checking; + this._connectedCallback(() => { this.onChecked() }); + } + else { + this.onChecked(); + } + Logger.instance.logNet(`网络已连接当前状态为【${NetNodeStateStrs[this._state]}】`); + } + + /** 连接验证成功,进入工作状态 */ + protected onChecked() { + Logger.instance.logNet("连接验证成功,进入工作状态"); + this._state = NetNodeState.Working; + // 关闭连接或重连中的状态显示 + this.updateNetTips(NetTipsType.Connecting, false); + this.updateNetTips(NetTipsType.ReConnecting, false); + + // 重发待发送信息 + var requests = this._requests.concat(); + if (requests.length > 0) { + Logger.instance.logNet(`请求【${this._requests.length}】个待发送的信息`); + + for (var i = 0; i < requests.length;) { + let req = requests[i]; + this._socket!.send(req.buffer); + if (req.rspObject == null || req.rspCmd != "") { + requests.splice(i, 1); + } + else { + ++i; + } + } + // 如果还有等待返回的请求,启动网络请求层 + this.updateNetTips(NetTipsType.Requesting, this._requests.length > 0); + } + } + + /** 接收到一个完整的消息包 */ + protected onMessage(msg: any): void { + // Logger.logNet(`接受消息状态为【${NetNodeStateStrs[this._state]}】`); + + var json = JSON.parse(msg); + + // 进行头部的校验(实际包长与头部长度是否匹配) + if (!this._protocolHelper!.checkResponsePackage(json)) { + error(`校验接受消息数据异常`); + return; + } + + // 处理相应包数据 + if (!this._protocolHelper!.handlerResponsePackage(json)) { + if (this._networkTips) + this._networkTips.responseErrorCode(json.code); + } + + // 接受到数据,重新定时收数据计时器 + this.resetReceiveMsgTimer(); + // 重置心跳包发送器 + this.resetHearbeatTimer(); + // 触发消息执行 + let rspCmd = this._protocolHelper!.getPackageId(json); + + Logger.instance.logNet(`接受到命令【${rspCmd}】的消息`); + // 优先触发request队列 + if (this._requests.length > 0) { + for (let reqIdx in this._requests) { + let req = this._requests[reqIdx]; + if (req.rspCmd == rspCmd && req.rspObject) { + Logger.instance.logNet(`触发请求命令【${rspCmd}】的回调`); + this._callbackExecuter!(req.rspObject, json.data); + this._requests.splice(parseInt(reqIdx), 1); + break; + } + } + + if (this._requests.length == 0) { + this.updateNetTips(NetTipsType.Requesting, false); + } + else { + Logger.instance.logNet(`请求队列中还有【${this._requests.length}】个请求在等待`); + } + } + + let listeners = this._listener[rspCmd]; + if (null != listeners) { + for (const rsp of listeners) { + Logger.instance.logNet(`触发监听命令【${rspCmd}】的回调`); + this._callbackExecuter!(rsp, json.data); + } + } + } + + protected onError(event: any) { + error(event); + } + + protected onClosed(event: any) { + this.clearTimer(); + + // 执行断线回调,返回false表示不进行重连 + if (this._disconnectCallback && !this._disconnectCallback()) { + Logger.instance.logNet(`断开连接`); + return; + } + + // 自动重连 + if (this.isAutoReconnect()) { + this.updateNetTips(NetTipsType.ReConnecting, true); + this._reconnectTimer = setTimeout(() => { + this._socket!.close(); + this._state = NetNodeState.Closed; + this.connect(this._connectOptions!); + if (this._autoReconnect > 0) { + this._autoReconnect -= 1; + } + }, this._reconnetTimeOut); + } + else { + this._state = NetNodeState.Closed; + } + } + + /** + * 断开网络 + * @param code 关闭码 + * @param reason 关闭原因 + */ + close(code?: number, reason?: string) { + this.clearTimer(); + this._listener = {}; + this._requests.length = 0; + if (this._networkTips) { + this._networkTips.connectTips(false); + this._networkTips.reconnectTips(false); + this._networkTips.requestTips(false); + } + if (this._socket) { + this._socket.close(code, reason); + } + else { + this._state = NetNodeState.Closed; + } + } + + /** + * 只是关闭Socket套接字(仍然重用缓存与当前状态) + * @param code 关闭码 + * @param reason 关闭原因 + */ + closeSocket(code?: number, reason?: string) { + if (this._socket) { + this._socket.close(code, reason); + } + } + + /** + * 发起请求,如果当前处于重连中,进入缓存列表等待重连完成后发送 + * @param buf 网络数据 + * @param force 是否强制发送 + */ + send(buf: NetData, force: boolean = false): number { + if (this._state == NetNodeState.Working || force) { + return this._socket!.send(buf); + } + else if (this._state == NetNodeState.Checking || + this._state == NetNodeState.Connecting) { + this._requests.push({ + buffer: buf, + rspCmd: "", + rspObject: null + }); + Logger.instance.logNet(`当前状态为【${NetNodeStateStrs[this._state]}】,繁忙并缓冲发送数据`); + return 0; + } + else { + error(`当前状态为【${NetNodeStateStrs[this._state]}】,请求错误`); + return -1; + } + } + + /** + * 发起请求,并进入缓存列表 + * @param reqProtocol 请求协议 + * @param rspObject 回调对象 + * @param showTips 是否触发请求提示 + * @param force 是否强制发送 + */ + request(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) { + var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol); + this.base_request(reqProtocol, rspCmd, rspObject, showTips, force); + } + + /** + * 唯一request,确保没有同一响应的请求(避免一个请求重复发送,netTips界面的屏蔽也是一个好的方法) + * @param reqProtocol 请求协议 + * @param rspObject 回调对象 + * @param showTips 是否触发请求提示 + * @param force 是否强制发送 + */ + requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false): boolean { + var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol); + + for (let i = 0; i < this._requests.length; ++i) { + if (this._requests[i].rspCmd == rspCmd) { + Logger.instance.logNet(`命令【${rspCmd}】重复请求`); + return false; + } + } + + this.base_request(reqProtocol, rspCmd, rspObject, showTips, force); + return true; + } + + private base_request(reqProtocol: IRequestProtocol, rspCmd: string, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) { + var buf: NetData = JSON.stringify(reqProtocol); // 转为二进制流发送 + + if (this._state == NetNodeState.Working || force) { + this._socket!.send(buf); + } + + Logger.instance.logNet(`队列命令为【${rspCmd}】的请求,等待请求数据的回调`); + + // 进入发送缓存列表 + this._requests.push({ + buffer: buf, rspCmd, rspObject + }); + // 启动网络请求层 + if (showTips) { + this.updateNetTips(NetTipsType.Requesting, true); + } + } + + /********************** 回调相关处理 *********************/ + /** + * 设置一个唯一的服务器推送监听 + * @param cmd 命令字串 + * @param callback 回调方法 + * @param target 目标对象 + */ + setResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean { + if (callback == null) { + error(`命令为【${cmd}】设置响应处理程序错误`); + return false; + } + this._listener[cmd] = [{ target, callback }]; + return true; + } + + /** + * 可添加多个同类返回消息的监听 + * @param cmd 命令字串 + * @param callback 回调方法 + * @param target 目标对象 + * @returns + */ + addResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean { + if (callback == null) { + error(`命令为【${cmd}】添加响应处理程序错误`); + return false; + } + let rspObject = { target, callback }; + if (null == this._listener[cmd]) { + this._listener[cmd] = [rspObject]; + } + else { + let index = this.getNetListenersIndex(cmd, rspObject); + if (-1 == index) { + this._listener[cmd]!.push(rspObject); + } + } + return true; + } + + /** + * 删除一个监听中指定子回调 + * @param cmd 命令字串 + * @param callback 回调方法 + * @param target 目标对象 + */ + removeResponeHandler(cmd: string, callback: NetCallFunc, target?: any) { + if (null != this._listener[cmd] && callback != null) { + let index = this.getNetListenersIndex(cmd, { target, callback }); + if (-1 != index) { + this._listener[cmd]!.splice(index, 1); + } + } + } + + /** + * 清除所有监听或指定命令的监听 + * @param cmd 命令字串(默认不填为清除所有) + */ + cleanListeners(cmd: string = "") { + if (cmd == "") { + this._listener = {} + } + else { + delete this._listener[cmd]; + } + } + + protected getNetListenersIndex(cmd: string, rspObject: CallbackObject): number { + let index = -1; + for (let i = 0; i < this._listener[cmd]!.length; i++) { + let iterator = this._listener[cmd]![i]; + if (iterator.callback == rspObject.callback + && iterator.target == rspObject.target) { + index = i; + break; + } + } + return index; + } + + /********************** 心跳、超时相关处理 *********************/ + protected resetReceiveMsgTimer() { + if (this._receiveMsgTimer !== null) { + clearTimeout(this._receiveMsgTimer); + } + + this._receiveMsgTimer = setTimeout(() => { + warn("接收消息定时器关闭网络连接"); + this._socket!.close(); + }, this._receiveTime); + } + + protected resetHearbeatTimer() { + if (this._keepAliveTimer !== null) { + clearTimeout(this._keepAliveTimer); + } + + this._keepAliveTimer = setTimeout(() => { + Logger.instance.logNet("网络节点保持活跃发送心跳信息"); + this.send(this._protocolHelper!.getHearbeat()); + }, this._heartTime); + } + + protected clearTimer() { + if (this._receiveMsgTimer !== null) { + clearTimeout(this._receiveMsgTimer); + } + if (this._keepAliveTimer !== null) { + clearTimeout(this._keepAliveTimer); + } + if (this._reconnectTimer !== null) { + clearTimeout(this._reconnectTimer); + } + } + + /** 是否自动重连接 */ + isAutoReconnect() { + return this._autoReconnect != 0; + } + + /** 拒绝重新连接 */ + rejectReconnect() { + this._autoReconnect = 0; + this.clearTimer(); + } } \ No newline at end of file diff --git a/assets/libs/network/NetProtocolPako.ts b/assets/libs/network/NetProtocolPako.ts index fb0fa9c4515d92a200385e0c2a9f51b1622095d3..3637b8a913c56945711f4ccd56086cc687604663 100644 --- a/assets/libs/network/NetProtocolPako.ts +++ b/assets/libs/network/NetProtocolPako.ts @@ -1,69 +1,69 @@ -/* - * @Author: dgflash - * @Date: 2022-04-21 13:45:51 - * @LastEditors: dgflash - * @LastEditTime: 2022-04-21 13:51:33 - */ -import { IProtocolHelper, IRequestProtocol, IResponseProtocol, NetData } from "./NetInterface"; - -var unzip = function (str: string) { - let charData = str.split('').map(function (x) { - return x.charCodeAt(0); - }); - let binData = new Uint8Array(charData); - //@ts-ignore - let data = pako.inflate(binData, { to: 'string' }); - return data; -} - -var zip = function (str: string) { - //@ts-ignore - let binaryString = pako.gzip(str, { to: 'string' }); - return binaryString; -} - -/** Pako.js 数据压缩协议 */ -export class NetProtocolPako implements IProtocolHelper { - getHeadlen(): number { - return 0; - } - - getHearbeat(): NetData { - return ""; - } - - getPackageLen(msg: NetData): number { - return msg.toString().length; - } - - checkResponsePackage(respProtocol: IResponseProtocol): boolean { - return true; - } - - handlerResponsePackage(respProtocol: IResponseProtocol): boolean { - if (respProtocol.code == 1) { - if (respProtocol.isCompress) { - respProtocol.data = unzip(respProtocol.data); - } - respProtocol.data = JSON.parse(respProtocol.data); - - return true; - } - else { - return false; - } - } - - handlerRequestPackage(reqProtocol: IRequestProtocol): string { - var rspCmd = reqProtocol.cmd; - reqProtocol.callback = rspCmd; - if (reqProtocol.isCompress) { - reqProtocol.data = zip(reqProtocol.data); - } - return rspCmd; - } - - getPackageId(respProtocol: IResponseProtocol): string { - return respProtocol.callback!; - } +/* + * @Author: dgflash + * @Date: 2022-04-21 13:45:51 + * @LastEditors: dgflash + * @LastEditTime: 2022-04-21 13:51:33 + */ +import { IProtocolHelper, IRequestProtocol, IResponseProtocol, NetData } from "./NetInterface"; + +var unzip = function (str: string) { + let charData = str.split('').map(function (x) { + return x.charCodeAt(0); + }); + let binData = new Uint8Array(charData); + //@ts-ignore + let data = pako.inflate(binData, { to: 'string' }); + return data; +} + +var zip = function (str: string) { + //@ts-ignore + let binaryString = pako.gzip(str, { to: 'string' }); + return binaryString; +} + +/** Pako.js 数据压缩协议 */ +export class NetProtocolPako implements IProtocolHelper { + getHeadlen(): number { + return 0; + } + + getHearbeat(): NetData { + return ""; + } + + getPackageLen(msg: NetData): number { + return msg.toString().length; + } + + checkResponsePackage(respProtocol: IResponseProtocol): boolean { + return true; + } + + handlerResponsePackage(respProtocol: IResponseProtocol): boolean { + if (respProtocol.code == 1) { + if (respProtocol.isCompress) { + respProtocol.data = unzip(respProtocol.data); + } + respProtocol.data = JSON.parse(respProtocol.data); + + return true; + } + else { + return false; + } + } + + handlerRequestPackage(reqProtocol: IRequestProtocol): string { + var rspCmd = reqProtocol.cmd; + reqProtocol.callback = rspCmd; + if (reqProtocol.isCompress) { + reqProtocol.data = zip(reqProtocol.data); + } + return rspCmd; + } + + getPackageId(respProtocol: IResponseProtocol): string { + return respProtocol.callback!; + } } \ No newline at end of file diff --git a/assets/libs/network/WebSock.ts b/assets/libs/network/WebSock.ts index 32521c1ab4decd0617a83fb2d256eed2732158a7..1ceaca9d4a7ac35ea589bede6ed42d76455f9105 100644 --- a/assets/libs/network/WebSock.ts +++ b/assets/libs/network/WebSock.ts @@ -1,84 +1,84 @@ -/* - * @Author: dgflash - * @Date: 2021-07-03 16:13:17 - * @LastEditors: dgflash - * @LastEditTime: 2022-09-09 17:42:19 - */ -import { Logger } from "../../core/common/log/Logger"; -import { ISocket, MessageFunc, NetData } from "./NetInterface"; - -type Connected = (event: any) => void; - -/** - * WebSocket 封装 - * 1. 连接/断开相关接口 - * 2. 网络异常回调 - * 3. 数据发送与接收 - */ -export class WebSock implements ISocket { - private _ws: WebSocket | null = null; // websocket对象 - - /** 网络连接成功事件 */ - onConnected: ((this: WebSocket, ev: Event) => any) | null = null; - /** 接受到网络数据事件 */ - onMessage: MessageFunc | null = null; - /** 网络错误事件 */ - onError: ((this: WebSocket, ev: Event) => any) | null = null; - /** 网络断开事件 */ - onClosed: ((this: WebSocket, ev: CloseEvent) => any) | null = null; - - /** 请求连接 */ - connect(options: any) { - if (this._ws) { - if (this._ws.readyState === WebSocket.CONNECTING) { - Logger.logNet("websocket connecting, wait for a moment...") - return false; - } - } - - let url = null; - if (options.url) { - url = options.url; - } - else { - let ip = options.ip; - let port = options.port; - let protocol = options.protocol; - url = `${protocol}://${ip}:${port}`; - } - - this._ws = new WebSocket(url); - this._ws.binaryType = options.binaryType ? options.binaryType : "arraybuffer"; - this._ws.onmessage = (event) => { - let onMessage: MessageFunc = this.onMessage!; - onMessage(event.data); - }; - this._ws.onopen = this.onConnected; - this._ws.onerror = this.onError; - this._ws.onclose = this.onClosed; - return true; - } - - /** - * 发送数据 - * @param buffer 网络数据 - */ - send(buffer: NetData): number { - if (this._ws && this._ws.readyState == WebSocket.OPEN) { - this._ws.send(buffer); - return 1; - } - return -1; - } - - /** - * 网络断开 - * @param code 关闭码 - * @param reason 关闭原因 - */ - close(code?: number, reason?: string) { - if (this._ws) { - this._ws.close(code, reason); - } - } +/* + * @Author: dgflash + * @Date: 2021-07-03 16:13:17 + * @LastEditors: dgflash + * @LastEditTime: 2022-09-09 17:42:19 + */ +import { Logger } from "../../core/common/log/Logger"; +import { ISocket, MessageFunc, NetData } from "./NetInterface"; + +type Connected = (event: any) => void; + +/** + * WebSocket 封装 + * 1. 连接/断开相关接口 + * 2. 网络异常回调 + * 3. 数据发送与接收 + */ +export class WebSock implements ISocket { + private _ws: WebSocket | null = null; // websocket对象 + + /** 网络连接成功事件 */ + onConnected: ((this: WebSocket, ev: Event) => any) | null = null; + /** 接受到网络数据事件 */ + onMessage: MessageFunc | null = null; + /** 网络错误事件 */ + onError: ((this: WebSocket, ev: Event) => any) | null = null; + /** 网络断开事件 */ + onClosed: ((this: WebSocket, ev: CloseEvent) => any) | null = null; + + /** 请求连接 */ + connect(options: any) { + if (this._ws) { + if (this._ws.readyState === WebSocket.CONNECTING) { + Logger.logNet("websocket connecting, wait for a moment...") + return false; + } + } + + let url = null; + if (options.url) { + url = options.url; + } + else { + let ip = options.ip; + let port = options.port; + let protocol = options.protocol; + url = `${protocol}://${ip}:${port}`; + } + + this._ws = new WebSocket(url); + this._ws.binaryType = options.binaryType ? options.binaryType : "arraybuffer"; + this._ws.onmessage = (event) => { + let onMessage: MessageFunc = this.onMessage!; + onMessage(event.data); + }; + this._ws.onopen = this.onConnected; + this._ws.onerror = this.onError; + this._ws.onclose = this.onClosed; + return true; + } + + /** + * 发送数据 + * @param buffer 网络数据 + */ + send(buffer: NetData): number { + if (this._ws && this._ws.readyState == WebSocket.OPEN) { + this._ws.send(buffer); + return 1; + } + return -1; + } + + /** + * 网络断开 + * @param code 关闭码 + * @param reason 关闭原因 + */ + close(code?: number, reason?: string) { + if (this._ws) { + this._ws.close(code, reason); + } + } } \ No newline at end of file diff --git a/assets/module/common/GameComponent.ts b/assets/module/common/GameComponent.ts index 68f02c545eda62fbf35af8e0b249a632dd10f410..4a0e35e5bc067b16be03b11ab23632b6df7825d0 100644 --- a/assets/module/common/GameComponent.ts +++ b/assets/module/common/GameComponent.ts @@ -6,6 +6,7 @@ */ import { Asset, Button, Component, EventHandler, EventKeyboard, EventTouch, Input, Node, Prefab, Sprite, SpriteFrame, __private, _decorator, input, isValid } from "cc"; import { oops } from "../../core/Oops"; +import { IAudioParams } from "../../core/common/audio/IAudio"; import { EventDispatcher } from "../../core/common/event/EventDispatcher"; import { EventMessage, ListenerFunc } from "../../core/common/event/EventMessage"; import { AssetType, CompleteCallback, Paths, ProgressCallback, resLoader } from "../../core/common/loader/ResLoader"; @@ -339,41 +340,44 @@ export class GameComponent extends Component { /** * 播放背景音乐(不受自动释放资源管理) * @param url 资源地址 - * @param callback 资源加载完成回调 - * @param bundleName 资源包名 + * @param params 背景音乐资源播放参数 */ - playMusic(url: string, callback?: Function, bundleName?: string) { - oops.audio.playMusic(url, callback, bundleName); - } - - /** - * 循环播放背景音乐(不受自动释放资源管理) - * @param url 资源地址 - * @param bundleName 资源包名 - */ - playMusicLoop(url: string, bundleName?: string) { - oops.audio.stopMusic(); - oops.audio.playMusicLoop(url, bundleName); + playMusic(url: string, params?: IAudioParams) { + oops.audio.music.loadAndPlay(url, params); } /** * 播放音效 * @param url 资源地址 - * @param callback 资源加载完成回调 - * @param bundleName 资源包名 + * @param params 音效播放参数 */ - async playEffect(url: string, bundleName?: string) { - if (bundleName == null) bundleName = oops.res.defaultBundleName; - let resId = await oops.audio.playEffect(url, bundleName, () => { - if (!this.isValid) return; + async playEffect(url: string, params?: IAudioParams): Promise { + return new Promise(async (resolve, reject) => { + let bundleName = resLoader.defaultBundleName; + if (params == null) { + params = { bundle: bundleName } + } + else if (params.bundle != null) { + bundleName = params.bundle; + } - const rps = this.resPaths.get(ResType.Audio); - if (rps) { - const key = this.getResKey(bundleName, url); - rps.delete(key); + // 音效播放完,关闭正在播放状态的音乐效果 + params.onPlayComplete = (aeid: number, url: string, bundleName: string) => { + // 音效播放完前,界面被释放 + if (!this.isValid) return; + + // 删除界面音效的播放记录 + const rps = this.resPaths.get(ResType.Audio); + if (rps) { + const key = this.getResKey(bundleName, url, aeid); + rps.delete(key); + } } + + let resId = await oops.audio.playEffect(url, params); + this.addPathToRecord(ResType.Audio, bundleName, url, resId); // 支持界面释放时,立即停止所有音效的播放 + resolve(resId); }); - this.addPathToRecord(ResType.Audio, bundleName, url, resId); } //#endregion diff --git a/assets/module/common/ModuleUtil.ts b/assets/module/common/ModuleUtil.ts index 3ec7dae347577d9480d1547b1d2131992d4ccd9c..9b583604c5db47f253cd5fa75874878bf17944b0 100644 --- a/assets/module/common/ModuleUtil.ts +++ b/assets/module/common/ModuleUtil.ts @@ -2,7 +2,9 @@ import { Node, __private } from "cc"; import { oops } from "../../core/Oops"; import { resLoader } from "../../core/common/loader/ResLoader"; import { UICallbacks } from "../../core/gui/layer/Defines"; -import { DelegateComponent } from "../../core/gui/layer/DelegateComponent"; +import { LayerUIElement } from "../../core/gui/layer/LayerUIElement"; +import { Uiid } from "../../core/gui/layer/LayerEnum"; +import { UIConfig } from "../../core/gui/layer/UIConfig"; import { ViewUtil } from "../../core/utils/ViewUtil"; import { ecs } from "../../libs/ecs/ECS"; import { CompType } from "../../libs/ecs/ECSModel"; @@ -43,7 +45,7 @@ export class ModuleUtil { static addViewUiAsync( ent: ecs.Entity, ctor: __private.__types_globals__Constructor | __private.__types_globals__AbstractedConstructor, - uiId: number, + uiId: number | UIConfig, uiArgs: any = null): Promise { return new Promise((resolve, reject) => { const uic: UICallbacks = { @@ -88,22 +90,22 @@ export class ModuleUtil { * @param isDestroy 是否释放界面缓存(默认为释放界面缓存) * @param onRemoved 窗口关闭完成事件 */ - static removeViewUi(ent: ecs.Entity, ctor: CompType, uiId: number, isDestroy: boolean = true, onRemoved?: Function) { + static removeViewUi(ent: ecs.Entity, ctor: CompType, uiId: Uiid, isDestroy: boolean = true, onRemoved?: Function) { const node = oops.gui.get(uiId); if (!node) { if (onRemoved) onRemoved(); return; } - const comp = node.getComponent(DelegateComponent); + const comp = node.getComponent(LayerUIElement); if (comp) { - if (comp.vp.callbacks.onBeforeRemove) { + if (comp.params.callbacks.onBeforeRemove) { comp.onCloseWindowBefore = () => { ent.remove(ctor, isDestroy); if (onRemoved) onRemoved(); }; } - else if (comp.vp.callbacks.onRemoved) { + else if (comp.params.callbacks.onRemoved) { comp.onCloseWindow = () => { ent.remove(ctor, isDestroy); if (onRemoved) onRemoved();