mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-05-07 19:07:30 +08:00
Merge pull request !16 from dgflash/develop
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
10
assets/core/common/audio/IAudio.ts
Normal file
10
assets/core/common/audio/IAudio.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface IAudioParams {
|
||||
/** 资源包名 */
|
||||
bundle?: string,
|
||||
/** 是否循环播放 */
|
||||
loop?: boolean;
|
||||
/** 音效音量 */
|
||||
volume?: number;
|
||||
/** 播放完成事件 */
|
||||
onPlayComplete?: Function;
|
||||
}
|
||||
9
assets/core/common/audio/IAudio.ts.meta
Normal file
9
assets/core/common/audio/IAudio.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "82ec630f-ea0b-4e37-a671-1b4555a756db",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** 框架提示资源路径 */
|
||||
export enum PromptResType {
|
||||
/** 飘动提示 */
|
||||
Toast = 'common/prefab/notify',
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
169
assets/core/gui/layer/LayerGame.ts
Normal file
169
assets/core/gui/layer/LayerGame.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
9
assets/core/gui/layer/LayerGame.ts.meta
Normal file
9
assets/core/gui/layer/LayerGame.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "77041037-badf-4f11-b538-33a855aae209",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
27
assets/core/gui/layer/LayerGameElement.ts
Normal file
27
assets/core/gui/layer/LayerGameElement.ts
Normal 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!;
|
||||
}
|
||||
9
assets/core/gui/layer/LayerGameElement.ts.meta
Normal file
9
assets/core/gui/layer/LayerGameElement.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "b43406cb-41d3-42a1-9393-40dbcd0a853e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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 移除后是否释放
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
/**
|
||||
* 如果指定onBeforeRemoved,则next必须调用,否则节点不会被正常删除。
|
||||
*
|
||||
* 比如希望节点做一个FadeOut然后删除,则可以在`onBeforeRemoved`当中播放action动画,动画结束后调用next
|
||||
* @param node 当前界面节点
|
||||
* @param next 回调方法
|
||||
*/
|
||||
onBeforeRemove?: (node: Node, next: Function) => void,
|
||||
|
||||
/** 网络异常时,窗口加载失败回调 */
|
||||
onLoadFailure?: () => void;
|
||||
}
|
||||
9
assets/core/gui/layer/LayerUIElement.ts.meta
Normal file
9
assets/core/gui/layer/LayerUIElement.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e7207ab9-8ef7-49af-9c19-84f4d6a2e589",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 { };
|
||||
|
||||
@@ -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);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { EDITOR } from "cc/env";
|
||||
export class VMEnv {
|
||||
/** 编辑状态 */
|
||||
static get editor() {
|
||||
// @ts-ignore
|
||||
return EDITOR && !cc.GAME_VIEW;
|
||||
return EDITOR;
|
||||
}
|
||||
}
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -186,4 +186,4 @@ export default class VMLabel extends VMBase {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,4 +146,4 @@ export default class VMModify extends VMBase {
|
||||
if (int) { a = Math.round(a) }
|
||||
this.VM.setValue(this.watchPath, this.clampValue(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,4 +95,4 @@ export default class VMProgress extends VMCustom {
|
||||
|
||||
this.setComponentValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,4 +295,4 @@ export default class VMState extends VMBase {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();//数据变动了就开始滚动
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user