!16 !15 Add: ObjectUtil中添加接口,是否为非法对象,可用于检测number、string、array、object等

Merge pull request !16 from dgflash/develop
This commit is contained in:
dgflash
2025-08-17 05:00:00 +00:00
committed by Gitee
52 changed files with 2161 additions and 2082 deletions

View File

@@ -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 */

View File

@@ -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<number> {
async loadAndPlay(url: string | AudioClip, params?: IAudioParams): Promise<number> {
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();
}
}

View File

@@ -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<number> {
return this.effect.load(url, bundleName, onPlayComplete);
playEffect(url: string | AudioClip, params?: IAudioParams): Promise<number> {
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;

View File

@@ -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) {

View File

@@ -0,0 +1,10 @@
export interface IAudioParams {
/** 资源包名 */
bundle?: string,
/** 是否循环播放 */
loop?: boolean;
/** 音效音量 */
volume?: number;
/** 播放完成事件 */
onPlayComplete?: Function;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "82ec630f-ea0b-4e37-a671-1b4555a756db",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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<T = Asset> = __private.__types_globals__Constructor<T> | null;
export type Paths = string | string[];
@@ -120,11 +120,12 @@ oops.res.loadRemote<ImageAsset>(this.url, opt, onComplete);
* @example
await oops.res.loadBundle(name);
*/
loadBundle(name: string) {
loadBundle(name: string): Promise<AssetManager.Bundle> {
return new Promise<AssetManager.Bundle>((resolve, reject) => {
assetManager.loadBundle(name, (err, bundle: AssetManager.Bundle) => {
if (err) {
return error(err);
resolve(null!);
return;
}
resolve(bundle);
});

View File

@@ -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<Node> {
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

View File

@@ -1,3 +1,4 @@
/** 框架提示资源路径 */
export enum PromptResType {
/** 飘动提示 */
Toast = 'common/prefab/notify',

View File

@@ -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!;
}

View File

@@ -1,13 +0,0 @@
{
"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": []
}
}

View File

@@ -1,13 +0,0 @@
{
"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": []
}
}

View File

@@ -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<DialogParam> = [];
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);
}

View File

@@ -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"
}

View File

@@ -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<string, GameElementParams>();
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<Node> {
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<Node> {
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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "77041037-badf-4f11-b538-33a855aae209",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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!;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b43406cb-41d3-42a1-9393-40dbcd0a853e",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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<string, LayerUI> = 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<Node | null> {
async openAsync(uiid: Uiid, uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>((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<number> {
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<Node | null> {
replaceAsync(removeUiId: Uiid, openUiId: Uiid, uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>(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 移除后是否释放

View File

@@ -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<boolean> {
const r = await super.showUi(vp);
protected async showUi(uip: UIParams): Promise<boolean> {
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();

View File

@@ -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<string, ViewParams>();
protected ui_nodes = new Collection<string, UIParams>();
/** 被移除的界面缓存数据 */
protected ui_cache = new Map<string, ViewParams>();
protected ui_cache = new Map<string, UIParams>();
/**
* 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<boolean> {
protected async showUi(uip: UIParams): Promise<boolean> {
// 触发窗口添加事件
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);
});
}

View File

@@ -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<boolean> {
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<boolean> {
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,
/**
* onBeforeRemovednext必须调用
*
* FadeOut然后删除`onBeforeRemoved`action动画next
* @param node
* @param next
*/
onBeforeRemove?: (node: Node, next: Function) => void,
/** 网络异常时,窗口加载失败回调 */
onLoadFailure?: () => void;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "e7207ab9-8ef7-49af-9c19-84f4d6a2e589",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}

View File

@@ -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<T>(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);
}
}

View File

@@ -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;
}
}

View File

@@ -1,155 +0,0 @@
/** 列表 */
export class List<T> {
private element: Array<T>;
/** 是否保证元素的唯一性 */
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<T> {
return this.element;
}
}

View File

@@ -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 { };

View File

@@ -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);
// });
// }
// }
}

View File

@@ -42,35 +42,35 @@ export class LanguagePack {
}
/** 多语言Excel配置表数据 */
private loadTable(lang: string) {
private loadTable(lang: string): Promise<void> {
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<void> {
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<void> {
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<void> {
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();
})
});
}

View File

@@ -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:00 显示 ms制
private time_s(value: number) {
//todo
}
// 将数字按 0:00:00:000 显示 ms制
/** 将数字按分显示 00:00 显示 分:秒) */
private time_ms(value: number) {
//todo
return new Date(value).format('mm:ss');
}
// 将时间戳显示为详细的内容
private timeStamp(value: number) {
//todo
return new Date(value).toString()
/** 将数字按秒显示 00:00:00 显示 (时:分:秒) */
private time_hms(value: number) {
return new Date(value).format('hh:mm:ss');
}
/** 将数字按 0:00:00:000 显示 (时:分:秒:毫秒) */
private time_hmss(value: number) {
return new Date(value).format('hh:mm:ss:ms');
}
/** 将时间戳显示为详细的内容 */
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();

View File

@@ -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('/');
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -4,7 +4,6 @@ import { EDITOR } from "cc/env";
export class VMEnv {
/** 编辑状态 */
static get editor() {
// @ts-ignore
return EDITOR && !cc.GAME_VIEW;
return EDITOR;
}
}

View File

@@ -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[] = [];

View File

@@ -186,4 +186,4 @@ export default class VMLabel extends VMBase {
return false;
}
}
}

View File

@@ -146,4 +146,4 @@ export default class VMModify extends VMBase {
if (int) { a = Math.round(a) }
this.VM.setValue(this.watchPath, this.clampValue(a));
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -95,4 +95,4 @@ export default class VMProgress extends VMCustom {
this.setComponentValue(value);
}
}
}

View File

@@ -295,4 +295,4 @@ export default class VMState extends VMBase {
return false;
}
}
}

View File

@@ -34,7 +34,7 @@ function getValueFromPath(obj: any, path: string, def?: any, tag: string | null
/**
* ModelViewer 类
*/
class ViewModel<T>{
class ViewModel<T> {
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();

View File

@@ -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();//数据变动了就开始滚动
}

View File

@@ -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;
}
}
}
}

View File

@@ -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": {}
}

View File

@@ -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<string, string> = new Map<string, string>();
/**
* 添加自定义请求头信息
* @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<HttpReturn> {
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<any>
*/
getAsyncByArraybuffer(name: string, params: any = null): Promise<HttpReturn> {
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<HttpReturn> {
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<string, string> = new Map<string, string>();
/**
* 添加自定义请求头信息
* @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<HttpReturn> {
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<any>
*/
getAsyncByArraybuffer(name: string, params: any = null): Promise<HttpReturn> {
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<HttpReturn> {
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];
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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<RequestObject>(); // 请求列表
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<T>(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<RequestObject>(); // 请求列表
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<T>(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();
}
}

View File

@@ -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!;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
const rps = this.resPaths.get(ResType.Audio);
if (rps) {
const key = this.getResKey(bundleName, url);
rps.delete(key);
async playEffect(url: string, params?: IAudioParams): Promise<number> {
return new Promise(async (resolve, reject) => {
let bundleName = resLoader.defaultBundleName;
if (params == null) {
params = { bundle: bundleName }
}
else if (params.bundle != null) {
bundleName = params.bundle;
}
// 音效播放完,关闭正在播放状态的音乐效果
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

View File

@@ -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<T extends CCVMParentComp | CCComp>(
ent: ecs.Entity,
ctor: __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>,
uiId: number,
uiId: number | UIConfig,
uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>((resolve, reject) => {
const uic: UICallbacks = {
@@ -88,22 +90,22 @@ export class ModuleUtil {
* @param isDestroy 是否释放界面缓存(默认为释放界面缓存)
* @param onRemoved 窗口关闭完成事件
*/
static removeViewUi(ent: ecs.Entity, ctor: CompType<ecs.IComp>, uiId: number, isDestroy: boolean = true, onRemoved?: Function) {
static removeViewUi(ent: ecs.Entity, ctor: CompType<ecs.IComp>, 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();