Merge pull request !22 from dgflash/develop
This commit is contained in:
dgflash
2025-09-21 00:36:39 +00:00
committed by Gitee
47 changed files with 1221 additions and 742 deletions

View File

@@ -4,7 +4,7 @@
* @LastEditors: dgflash
* @LastEditTime: 2023-08-28 10:02:57
*/
import { _decorator, Component, director, Game, game, JsonAsset, Node, resources, screen, sys } from "cc";
import { _decorator, Component, director, Game, game, JsonAsset, Node, profiler, resources, screen, sys } from "cc";
import { GameConfig } from "../module/config/GameConfig";
import { GameQueryConfig } from "../module/config/GameQueryConfig";
import { oops, version } from "./Oops";
@@ -94,6 +94,8 @@ export class Root extends Component {
//@ts-ignore
oops.gui.initLayer(this.gui, config.json.gui);
// 初始化统计信息
oops.config.game.stats ? profiler.showStats() : profiler.hideStats();
// 初始化每秒传输帧数
game.frameRate = oops.config.game.frameRate;

View File

@@ -15,13 +15,13 @@ export type ListenerFunc = (event: string, ...args: any) => void
/** 框架内部全局事件 */
export enum EventMessage {
/** 游戏从后台进入事件 */
GAME_SHOW = "GAME_ENTER",
GAME_SHOW = "onGameShow",
/** 游戏切到后台事件 */
GAME_HIDE = "GAME_EXIT",
GAME_HIDE = "onGameHide",
/** 游戏画笔尺寸变化事件 */
GAME_RESIZE = "GAME_RESIZE",
GAME_RESIZE = "onGameResize",
/** 游戏全屏事件 */
GAME_FULL_SCREEN = "GAME_FULL_SCREEN",
GAME_FULL_SCREEN = "onGameFullScreen",
/** 游戏旋转屏幕事件 */
GAME_ORIENTATION = "GAME_ORIENTATION"
GAME_ORIENTATION = "onGameOrientation"
}

View File

@@ -0,0 +1,87 @@
import { BufferAsset, SpriteFrame, Texture2D } from "cc";
import { resLoader } from "./ResLoader";
/**
* 加载Zip资源
* 注:
* 1. 使用此功能需要教程项目中项目资源目录libs/jszip目录拷贝到自己的项目中
* 2. 选中libs/jszip/jszip文件属性检查器中勾选导入为插件、允许指点平台加载此库
* 3. 压缩软件打包的 game.zip 修改为 game.bin 则可在游戏中加载
*/
export class ZipLoader {
private static zips: Map<string, JSZip> = new Map();
/**
* 加载ZIP资源包
* @param url
* @returns
*/
static load(url: string): Promise<JSZip> {
return new Promise((resolve, reject) => {
resLoader.load(url, BufferAsset, async (error: Error | null, asset: BufferAsset) => {
if (error) return reject(error);
var zip = await JSZip.loadAsync(asset.buffer());
this.zips.set(url, zip);
resolve(zip);
})
});
}
static getJson(zipName: string, path: string): Promise<any> {
return new Promise(async (resolve, reject) => {
var zip = this.zips.get(zipName);
if (zip == null) {
console.error(`名为【${zipName}】的资源包不存在`);
resolve(null);
return;
}
var file = zip.file(path);
var json = JSON.parse(await file.async("text"));
resolve(json);
});
}
static getSpriteFrame(zipName: string, path: string): Promise<SpriteFrame> {
return new Promise(async (resolve, reject) => {
var zip = this.zips.get(zipName);
if (zip == null) {
console.error(`名为【${zipName}】的资源包不存在`);
resolve(null!);
return;
}
var file = zip.file(path);
var buf = await file.async("base64");
var img = new Image();
img.src = 'data:image/png;base64,' + buf;
img.onload = () => {
var texture = new Texture2D();
texture.reset({
width: img.width,
height: img.height
});
texture.uploadData(img, 0, 0);
texture.loaded = true;
var sf = new SpriteFrame();
sf.texture = texture;
resolve(sf);
}
});
}
/** 释放Zip资源 */
static release(url?: string) {
if (url) {
resLoader.release(url);
}
else {
this.zips.forEach((value: JSZip, key: string) => {
resLoader.release(key);
});
}
}
}

View File

@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "52a6c740-3b9b-46c5-a784-d53a6b67954a",
"uuid": "2c6ce5e4-08fd-4ff1-b0b6-8c07d7192263",
"files": [],
"subMetas": {},
"userData": {}

View File

@@ -126,7 +126,7 @@ oops.log.table(object);
* 打印标准日志
* @param msg 日志消息
*/
trace(msg: any, color: string = "#000000") {
trace(msg: any, color: string = "#000000ff") {
this.print(LogType.Trace, msg, color);
}

View File

@@ -8,10 +8,29 @@ import { Component, game } from "cc";
import { StringUtil } from "../../utils/StringUtil";
import { Timer } from "./Timer";
interface ITimer {
/** 倒计时编号 */
id: string;
/** 定时器 */
timer: Timer;
/** 数据对象 */
object: any;
/** 修改数据对象的字段 */
field: string;
/** 事件侦听器的目标和被叫方 */
target: any;
/** 开始时间 */
startTime: number;
/** 每秒触发事件 */
onSeconds: Function[];
/** 时间完成事件 */
onCompletes: Function[];
}
/** 时间管理 */
export class TimerManager extends Component {
/** 倒计时数据 */
private times: any = {};
private times: { [key: string]: ITimer } = {};
/** 服务器时间 */
private date_s: Date = new Date();
/** 服务器初始时间 */
@@ -25,7 +44,7 @@ export class TimerManager extends Component {
protected update(dt: number) {
for (let key in this.times) {
let data = this.times[key];
let timer = data.timer as Timer;
let timer = data.timer;
if (timer.update(dt)) {
if (data.object[data.field] > 0) {
data.object[data.field]--;
@@ -35,8 +54,8 @@ export class TimerManager extends Component {
this.onTimerComplete(data);
}
// 触发每秒回调事件
else if (data.onSecond) {
data.onSecond.call(data.object);
else if (data.onSeconds) {
data.onSeconds.forEach(fn => fn.call(data.object));
}
}
}
@@ -44,10 +63,8 @@ export class TimerManager extends Component {
}
/** 触发倒计时完成事件 */
private onTimerComplete(data: any) {
if (data.onComplete) data.onComplete.call(data.target, data.object);
if (data.event) this.node.dispatchEvent(data.event);
private onTimerComplete(data: ITimer) {
if (data.onCompletes) data.onCompletes.forEach(fn => fn.call(data.target, data.object));
delete this.times[data.id];
}
@@ -58,14 +75,14 @@ export class TimerManager extends Component {
* @param target 触发事件的对象
* @param onSecond 每秒事件
* @param onComplete 倒计时完成事件
* @returns
* @returns 倒计时编号
* @example
export class Test extends Component {
private timeId!: string;
start() {
// 在指定对象上注册一个倒计时的回调管理器
this.timeId = oops.timer.register(this, "countDown", this.onSecond, this.onComplete);
this.timeId = oops.timer.register(this, "countDown", this, this.onSecond, this.onComplete);
}
private onSecond() {
@@ -77,22 +94,41 @@ export class TimerManager extends Component {
}
}
*/
register(object: any, field: string, target: object, onSecond: Function, onComplete: Function): string {
register(object: any, field: string, target: object, onSecond?: Function, onComplete?: Function): string {
const timer = new Timer();
timer.step = 1;
let data: any = {};
data.id = StringUtil.guid();
data.timer = timer;
data.object = object; // 管理对象
data.field = field; // 时间字段
data.onSecond = onSecond; // 每秒事件
data.onComplete = onComplete; // 倒计时完成事件
data.target = target
let data: ITimer = {
id: StringUtil.guid(),
timer: timer,
object: object,
field: field,
onSeconds: [],
onCompletes: [],
target: target,
startTime: this.getTime()
};
if (onSecond) data.onSeconds.push(onSecond);
if (onComplete) data.onCompletes.push(onComplete);
this.times[data.id] = data;
return data.id;
}
/**
* 为指定倒计时添加回调事件
* @param id 倒计时编号
* @param onSecond 每秒事件
* @param onComplete 倒计时完成事件
*/
addCallback(id: string, onSecond?: Function, onComplete?: Function) {
let data = this.times[id];
if (data) {
if (onSecond) data.onSeconds.push(onSecond);
if (onComplete) data.onCompletes.push(onComplete);
}
}
/**
* 在指定对象上注销一个倒计时的回调管理器
* @param id 时间对象唯一表示
@@ -153,23 +189,24 @@ export class TimerManager extends Component {
/** 游戏最小化时记录时间数据 */
save(): void {
for (let key in this.times) {
this.times[key].startTime = this.getTime();
let data: ITimer = this.times[key];
data.startTime = this.getTime();
}
}
/** 游戏最大化时回复时间数据 */
load(): void {
for (let key in this.times) {
let interval = Math.floor((this.getTime() - (this.times[key].startTime || this.getTime())) / 1000);
let data = this.times[key];
let interval = Math.floor((this.getTime() - (data.startTime || this.getTime())) / 1000);
data.object[data.field] = data.object[data.field] - interval;
if (data.object[data.field] <= 0) {
data.object[data.field] = 0;
this.onTimerComplete(data);
}
else {
this.times[key].startTime = null;
data.object[data.field] = 0;
}
}
}
}
}

View File

@@ -54,7 +54,9 @@ export class GameManager {
}
// 自定义节点排序索引
if (params && params.siblingIndex) node.setSiblingIndex(params.siblingIndex);
if (params) {
if (params.siblingIndex) node.setSiblingIndex(params.siblingIndex);
}
resolve(node);
});
@@ -70,4 +72,4 @@ export class GameManager {
//@ts-ignore
return director.globalGameTimeScale;
}
}
}

49
assets/core/gui/Gui.ts Normal file
View File

@@ -0,0 +1,49 @@
import { UIConfigMap } from "./layer/LayerEnum";
import { UIConfig } from "./layer/UIConfig";
var configs: UIConfigMap = {};
export namespace gui {
/** 注册界面组件 */
export function register(key: string, config: UIConfig) {
return function (ctor: any) {
//@ts-ignore
ctor[gui.internal.GUI_KEY] = key;
internal.setConfig(key, config);
};
}
/** 框架内部使用方法 */
export namespace internal {
/** 界面唯一标记变量名 */
export const GUI_KEY = "OOPS_GUI_KEY";
/** 获取界面唯一关键字 */
export function getKey(ctor: any) {
return ctor[GUI_KEY];
}
/** 获取界面组件配置 */
export function getConfig(key: string) {
return configs[key];
}
/** 获取界面组件配置 */
export function setConfig(key: string, config: UIConfig) {
let c = getConfig(key);
if (c == null) {
configs[key] = config;
}
else {
console.error(`界面${key}重复注册`);
}
}
/** 初始化界面组件配置 */
export function initConfigs(uicm: UIConfigMap) {
for (const key in uicm) {
configs[key] = uicm[key];
}
}
}
}

View File

@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "dd2077e2-c862-4b7f-9afe-6e488c83076d",
"uuid": "51e09b11-d9ab-453b-b637-74d6aa615806",
"files": [],
"subMetas": {},
"userData": {}

View File

@@ -4,9 +4,9 @@
* @LastEditors: dgflash
* @LastEditTime: 2023-07-24 17:14:57
*/
import { Node } from "cc";
import { LayerPopUp } from "./LayerPopup";
import { UICallbacks, UIParams } from "./LayerUIElement";
import { UIParam, UIState } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
/** 模式弹窗数据 */
@@ -16,9 +16,7 @@ type DialogParam = {
/** 窗口配置 */
config: UIConfig;
/** 窗口附加参数 */
params?: any;
/** 窗口回调 */
callbacks?: UICallbacks;
params?: UIParam;
}
/*
@@ -27,56 +25,51 @@ type DialogParam = {
export class LayerDialog extends LayerPopUp {
/** 窗口调用参数队列 */
private params: Array<DialogParam> = [];
/** 当前打开的界面 */
private current: Node = null!;
add(uiid: string, config: UIConfig, params?: any, callbacks?: UICallbacks) {
// 控制同一时间只能显示一个模式窗口
if (this.ui_nodes.size > 0) {
this.params.push({
uiid: uiid,
config: config,
params: params,
callbacks: callbacks,
});
return;
}
this.show(uiid, config, params, callbacks);
/**
* 添加模式窗口
* 1. 同时添加多个模式窗口时,第一个之后的窗口会先队列起来,在第一个关闭后在加载与显示第二个;同时方法返回节点保持只返回当前显示的界面节点
*/
add(uiid: string, config: UIConfig, params?: UIParam): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
// 控制同一时间只能显示一个模式窗口
if (this.ui_nodes.size > 0) {
this.params.push({ uiid: uiid, config: config, params: params });
resolve(this.current);
}
else {
this.current = await this.showDialog(uiid, config, params);
resolve(this.current);
}
});
}
/** 显示模式弹窗 */
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;
}
uip.params = params || {};
uip.callbacks = callbacks ?? {};
this.ui_nodes.set(uip.config.prefab, uip);
this.load(uip, config.bundle);
private showDialog(uiid: string, config: UIConfig, param?: UIParam): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
let state = this.initUIConfig(uiid, config, param);
let node = await this.load(state);
resolve(node);
});
}
protected onCloseWindow(uip: UIParams) {
super.onCloseWindow(uip);
protected closeUi(state: UIState) {
super.closeUi(state);
setTimeout(this.next.bind(this), 0);
}
protected closeUI() {
protected closeBlack() {
if (this.params.length == 0) {
this.black.enabled = false;
this.closeVacancyRemove();
this.closeMask()
super.closeBlack();
}
}
private next() {
if (this.params.length > 0) {
let param = this.params.shift()!;
this.show(param.uiid, param.config, param.params, param.callbacks);
this.showDialog(param.uiid, param.config, param.params);
}
}
}

View File

@@ -1,7 +1,7 @@
import { UIConfig } from "./UIConfig";
/** 界面编号 */
export type Uiid = number | string | UIConfig;
export type Uiid = number | string | UIConfig | Function;
/** 界面配置集合 */
export type UIConfigMap = { [key: string]: UIConfig }
@@ -51,4 +51,4 @@ export enum LayerTypeCls {
Game = "Game",
/** 自定义节点层 */
Node = "Node"
}
}

View File

@@ -1,14 +1,15 @@
import { Camera, Layers, Node, ResolutionPolicy, SafeArea, Widget, screen, view, warn } from "cc";
import { resLoader } from "../../common/loader/ResLoader";
import { Camera, Node, ResolutionPolicy, SafeArea, screen, view, warn } from "cc";
import { oops } from "../../Oops";
import { gui } from "../Gui";
import { LayerDialog } from "./LayerDialog";
import { LayerCustomType, LayerTypeCls, UIConfigMap, Uiid } from "./LayerEnum";
import { LayerGame } from "./LayerGame";
import { LayerHelper } from "./LayerHelper";
import { LayerNotify } from "./LayerNotify";
import { LayerPopUp } from "./LayerPopup";
import { LayerUI } from "./LayerUI";
import { LayerUIElement, UICallbacks } from "./LayerUIElement";
import { LayerUIElement, UIParam } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
import { LayerGame } from "./LayerGame";
/** 界面层级管理器 */
export class LayerManager {
@@ -30,8 +31,6 @@ export class LayerManager {
/** 消息提示控制器请使用show方法来显示 */
private notify!: LayerNotify;
/** UI配置 */
private configs: UIConfigMap = {};
/** 界面层集合 - 无自定义类型 */
private uiLayers: Map<string, LayerUI> = new Map();
/** 界面层组件集合 */
@@ -137,7 +136,7 @@ export class LayerManager {
* @param configs 配置对象
*/
init(configs: UIConfigMap): void {
this.configs = configs;
gui.internal.initConfigs(configs);
}
/**
@@ -171,23 +170,30 @@ export class LayerManager {
this.notify.waitClose();
}
/** 获取界面信息 */
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];
config = gui.internal.getConfig(key);
if (config == null) {
config = uiid;
this.configs[key] = uiid;
gui.internal.setConfig(key, uiid);
}
}
// 界面对象 - 配合gui.register使用
else if (uiid instanceof Function) {
//@ts-ignore
key = uiid[gui.internal.GUI_KEY];
config = gui.internal.getConfig(key);
}
// 界面唯一标记
else {
key = uiid.toString();
config = this.configs[uiid];
config = gui.internal.getConfig(key);
if (config == null) {
console.error(`打开编号为【${uiid}】的界面失败,配置信息不存在`);
}
@@ -198,8 +204,7 @@ export class LayerManager {
/**
* 同步打开一个窗口
* @param uiid 窗口唯一编号
* @param uiArgs 窗口参数
* @param callbacks 回调对象
* @param param 窗口参数
* @example
var uic: UICallbacks = {
onAdded: (node: Node, params: any) => {
@@ -209,52 +214,62 @@ export class LayerManager {
}
};
oops.gui.open(UIID.Loading, null, uic);
oops.gui.open(UIID.Loading);
*/
open(uiid: Uiid, uiArgs: any = null, callbacks?: UICallbacks): void {
open(uiid: Uiid, param?: UIParam): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
let info = this.getInfo(uiid);
let layer = this.uiLayers.get(info.config.layer);
if (layer) {
let node = await layer.add(info.key, info.config, param);
resolve(node);
}
else {
console.error(`打开编号为【${uiid}】的界面失败,界面层不存在`);
}
});
}
/** 显示指定界面 */
show(uiid: Uiid) {
let info = this.getInfo(uiid);
let layer = this.uiLayers.get(info.config.layer);
if (layer) {
layer.add(info.key, info.config, uiArgs, callbacks);
layer.show(info.config.prefab);
}
else {
console.error(`打开编号为【${uiid}】的界面失败,界面层不存在`);
}
}
/**
* 异步打开一个窗口
* @param uiid 窗口唯一编号
* @param uiArgs 窗口参数
* @example
* var node = await oops.gui.openAsync(UIID.Loading);
*/
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) => {
resolve(node);
},
onLoadFailure: () => {
resolve(null);
}
};
this.open(uiid, uiArgs, callbacks);
});
}
/**
* 移除指定标识的窗口
* @param uiid 窗口唯一标识
* @param isDestroy 移除后是否释放(默认释放内存)
* @example
* oops.gui.remove(UIID.Loading);
*/
remove(uiid: Uiid, isDestroy: boolean = true) {
remove(uiid: Uiid) {
let info = this.getInfo(uiid);
let layer = this.uiLayers.get(info.config.layer);
if (layer) {
layer.remove(info.config.prefab, isDestroy);
layer.remove(info.config.prefab);
}
else {
console.error(`移除编号为【${uiid}】的界面失败,界面层不存在`);
}
}
/**
* 清理指定界面的缓存
* @param uiid 窗口唯一标识
* @example
* oops.gui.removeCache(UIID.Loading);
*/
removeCache(uiid: Uiid) {
let info = this.getInfo(uiid);
let layer = this.uiLayers.get(info.config.layer);
if (layer) {
layer.removeCache(info.config.prefab);
}
else {
console.error(`移除编号为【${uiid}】的界面失败,界面层不存在`);
@@ -264,25 +279,24 @@ export class LayerManager {
/**
* 通过界面节点移除
* @param node 窗口节点
* @param isDestroy 移除后是否释放资源(默认释放内存)
* @example
* oops.gui.removeByNode(cc.Node);
*/
removeByNode(node: Node, isDestroy: boolean = true) {
removeByNode(node: Node) {
if (node instanceof Node) {
let comp = node.getComponent(LayerUIElement);
if (comp && comp.params) {
if (comp && comp.state) {
// 释放显示的界面
if (node.parent) {
let uiid = this.configs[comp.params.uiid];
this.remove(uiid, isDestroy);
let uiid = gui.internal.getConfig(comp.state.uiid);
this.remove(uiid);
}
// 释放缓存中的界面
else if (isDestroy) {
let layer = this.uiLayers.get(comp.params.config.layer);
else {
let layer = this.uiLayers.get(comp.state.config.layer);
if (layer) {
// @ts-ignore 注:不对外使用
layer.removeCache(comp.params.config.prefab);
layer.removeCache(comp.state.config.prefab);
}
}
}
@@ -296,34 +310,14 @@ export class LayerManager {
/**
* 场景替换
* @param removeUiId 移除场景编号
* @param openUiId 新打开场景编号
* @param uiArgs 新打开场景参数
* @param openUiid 新打开场景编号
* @param param 新打开场景参数
*/
replace(removeUiId: Uiid, openUiId: Uiid, uiArgs: any = null) {
const callbacks: UICallbacks = {
onAdded: (node: Node, params: any) => {
this.remove(removeUiId);
}
};
this.open(openUiId, uiArgs, callbacks);
}
/**
* 异步场景替换
* @param removeUiId 移除场景编号
* @param openUiId 新打开场景编号
* @param uiArgs 新打开场景参数
*/
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) {
this.remove(removeUiId);
resolve(node);
}
else {
resolve(null);
}
replace(removeUiId: Uiid, openUiid: Uiid, param?: UIParam): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
let node = await this.open(openUiid, param);
this.remove(removeUiId);
resolve(node);
});
}
@@ -380,12 +374,7 @@ export class LayerManager {
private create_node(name: string) {
const node = new Node(name);
node.layer = Layers.Enum.UI_2D;
const w: Widget = node.addComponent(Widget);
w.isAlignLeft = w.isAlignRight = w.isAlignTop = w.isAlignBottom = true;
w.left = w.right = w.top = w.bottom = 0;
w.alignMode = 2;
w.enabled = true;
LayerHelper.setFullScreen(node);
return node;
}
}

View File

@@ -7,7 +7,7 @@ import { BlockInputEvents, EventTouch, Node } from "cc";
import { ViewUtil } from "../../utils/ViewUtil";
import { PromptResType } from "../GuiEnum";
import { LayerUI } from "./LayerUI";
import { UIParams } from "./LayerUIElement";
import { UIState } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
/* 弹窗层,允许同时弹出多个窗口 */
@@ -17,27 +17,21 @@ export class LayerPopUp extends LayerUI {
/** 半透明遮罩资源 */
protected mask!: Node;
constructor(name: string) {
super(name);
this.on(Node.EventType.CHILD_ADDED, this.onChildAdded, this);
this.on(Node.EventType.CHILD_REMOVED, this.onChildRemoved, this);
protected onChildAdded(child: Node) {
this.mask && this.mask.setSiblingIndex(this.children.length - 2);
}
private onChildAdded(child: Node) {
if (this.mask) this.mask.setSiblingIndex(this.children.length - 2);
protected onChildRemoved(child: Node) {
this.mask && this.mask.setSiblingIndex(this.children.length - 2);
super.onChildRemoved(child);
}
private onChildRemoved(child: Node) {
if (this.mask) this.mask.setSiblingIndex(this.children.length - 2);
}
protected showUi(uip: UIParams): Promise<boolean> {
protected uiInit(state: UIState): Promise<boolean> {
return new Promise(async (resolve) => {
const r = await super.showUi(uip);
const r = await super.uiInit(state);
if (r) {
// 界面加载完成显示时,启动触摸非窗口区域关闭
this.openVacancyRemove(uip.config);
this.openVacancyRemove(state.config);
// 界面加载完成显示时,层级事件阻挡
this.black.enabled = true;
@@ -46,15 +40,15 @@ export class LayerPopUp extends LayerUI {
});
}
protected onCloseWindow(uip: UIParams) {
super.onCloseWindow(uip);
protected closeUi(state: UIState) {
super.closeUi(state);
// 界面关闭后,关闭触摸事件阻挡、关闭触摸非窗口区域关闭、关闭遮罩
this.closeUI();
this.closeBlack();
}
/** 设置触摸事件阻挡 */
protected closeUI() {
protected closeBlack() {
// 所有弹窗关闭后,关闭事件阻挡功能
if (this.ui_nodes.size == 0) {
if (this.black) this.black.enabled = false;
@@ -120,13 +114,13 @@ export class LayerPopUp extends LayerUI {
if (this.ui_nodes.size > 0) {
let vp = this.ui_nodes.array[this.ui_nodes.size - 1];
if (vp.valid && vp.config.vacancy) {
this.remove(vp.config.prefab, vp.config.destroy);
this.remove(vp.config.prefab);
}
}
}
clear(isDestroy: boolean) {
super.clear(isDestroy)
this.closeUI();
this.closeBlack();
}
}

View File

@@ -1,19 +1,20 @@
import { instantiate, Node, Prefab, SafeArea } from "cc";
import { Collection } from "db://oops-framework/libs/collection/Collection";
import { resLoader } from "../../common/loader/ResLoader";
import { oops } from "../../Oops";
import { Uiid } from "./LayerEnum";
import { LayerHelper } from "./LayerHelper";
import { LayerUIElement, UICallbacks, UIParams } from "./LayerUIElement";
import { LayerUIElement, UIParam, UIState } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
/** 界面层对象 */
export class LayerUI extends Node {
/** 全局窗口打开失败 */
/** 全局窗口打开失败事件 */
onOpenFailure: Function = null!;
/** 显示界面节点集合 */
protected ui_nodes = new Collection<string, UIParams>();
protected ui_nodes = new Collection<string, UIState>();
/** 被移除的界面缓存数据 */
protected ui_cache = new Map<string, UIParams>();
protected ui_cache = new Map<string, UIState>();
/**
* UI基础层允许添加多个预制件节点
@@ -22,77 +23,122 @@ export class LayerUI extends Node {
constructor(name: string) {
super(name);
LayerHelper.setFullScreen(this);
this.on(Node.EventType.CHILD_ADDED, this.onChildAdded, this);
this.on(Node.EventType.CHILD_REMOVED, this.onChildRemoved, this);
}
protected onChildAdded(child: Node) {
}
protected onChildRemoved(child: Node) {
const comp = child.getComponent(LayerUIElement);
if (comp) {
this.closeUi(comp.state);
}
}
/**
* 添加一个预制件节点到层容器中,该方法将返回一个唯一`uuid`来标识该操作节点
* @param uiid 窗口唯一标识
* @param config 界面配置数据
* @param params 自定义参数
* @param callbacks 回调函数对象,可选
* @returns ture为成功,false为失败
*/
add(uiid: Uiid, config: UIConfig, params?: any, callbacks?: UICallbacks) {
if (this.ui_nodes.has(config.prefab)) {
console.warn(`路径为【${config.prefab}】的预制重复加载`);
return;
add(uiid: Uiid, config: UIConfig, params?: UIParam): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
if (this.ui_nodes.has(config.prefab)) {
console.warn(`路径为【${config.prefab}】的预制重复加载`);
return;
}
// 检查缓存中是否存界面
let state = this.initUIConfig(uiid, config, params);
await this.load(state);
resolve(state.node);
});
}
/** 初始化界面配置初始值 */
protected initUIConfig(uiid: Uiid, config: UIConfig, params?: UIParam) {
let state = this.ui_cache.get(config.prefab);
if (state == null) {
if (config.bundle == null) config.bundle = resLoader.defaultBundleName;
if (config.destroy == null) config.destroy = true;
if (config.vacancy == null) config.vacancy = false;
if (config.mask == null) config.mask = false;
if (config.safeArea == null) config.safeArea = false;
state = new UIState();
state.uiid = uiid.toString();
state.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, uip);
uip.params = params ?? {};
uip.callbacks = callbacks ?? {};
uip.valid = true;
this.load(uip, config.bundle)
state.params = params ?? {};
state.valid = true;
this.ui_nodes.set(config.prefab, state);
return state;
}
/**
* 加载界面资源
* @param uip 显示参数
* @param state 显示参数
* @param bundle 远程资源包名,如果为空就是默认本地资源包
*/
protected async load(uip: UIParams, bundle?: string) {
// 加载界面资源超时提示
const timerId = setTimeout(this.onLoadingTimeoutGui, oops.config.game.loadingTimeoutGui);
protected async load(state: UIState): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
// 加载界面资源超时提示
if (state.node == null) {
let timerId = setTimeout(this.onLoadingTimeoutGui, oops.config.game.loadingTimeoutGui);
if (uip && uip.node) {
await this.showUi(uip);
}
else {
// 优先加载配置的指定资源包中资源,如果没配置则加载默认资源包资源
bundle = bundle || oops.res.defaultBundleName;
const res = await oops.res.loadAsync(bundle, uip.config.prefab, Prefab);
if (res) {
uip.node = instantiate(res);
// 优先加载配置的指定资源包中资源,如果没配置则加载默认资源包资源
const res = await resLoader.loadAsync(state.config.bundle!, state.config.prefab, Prefab);
if (res) {
state.node = instantiate(res);
// 是否启动真机安全区域显示
if (uip.config.safeArea) uip.node.addComponent(SafeArea);
// 是否启动真机安全区域显示
if (state.config.safeArea) state.node.addComponent(SafeArea);
// 窗口事件委托
const dc = uip.node.addComponent(LayerUIElement);
dc.params = uip;
//@ts-ignore
dc.onCloseWindow = this.onCloseWindow.bind(this);
// 窗口事件委托
const comp = state.node.addComponent(LayerUIElement);
comp.state = state;
}
else {
console.warn(`路径为【${state.config.prefab}】的预制加载失败`);
this.failure(state);
}
// 显示界面
await this.showUi(uip);
// 关闭界面资源超时提示
oops.gui.waitClose();
clearTimeout(timerId);
}
await this.uiInit(state);
resolve(state.node);
});
}
/**
* 创建界面节点
* @param state 视图参数
*/
protected uiInit(state: UIState): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
const comp = state.node.getComponent(LayerUIElement)!;
const r: boolean = await comp.add();
if (r) {
state.valid = true; // 标记界面为使用状态
if (!state.params.preload) {
state.params.preload = false;
state.node.parent = this;
}
}
else {
console.warn(`路径为【${uip.config.prefab}】的预制加载失败`);
this.failure(uip);
console.warn(`路径为【${state.config.prefab}】的自定义预处理逻辑异常.检查预制上绑定的组件中 onAdded 方法,返回true才能正确完成窗口显示流程`);
this.failure(state);
}
}
// 关闭界面资源超时提示
oops.gui.waitClose();
clearTimeout(timerId);
resolve(r);
});
}
/** 加载超时事件*/
@@ -101,89 +147,57 @@ export class LayerUI extends Node {
}
/** 窗口关闭事件 */
protected onCloseWindow(vp: UIParams) {
this.ui_nodes.delete(vp.config.prefab);
}
/**
* 创建界面节点
* @param uip 视图参数
*/
protected async showUi(uip: UIParams): Promise<boolean> {
// 触发窗口添加事件
const comp = uip.node.getComponent(LayerUIElement)!;
const r: boolean = await comp.add();
if (r) {
uip.node.parent = this;
// 标记界面为使用状态
uip.valid = true;
}
else {
console.warn(`路径为【${uip.config.prefab}】的自定义预处理逻辑异常.检查预制上绑定的组件中 onAdded 方法,返回true才能正确完成窗口显示流程`);
this.failure(uip);
}
return r;
protected closeUi(state: UIState) {
this.ui_nodes.delete(state.config.prefab);
}
/** 打开窗口失败逻辑 */
protected failure(uip: UIParams) {
this.onCloseWindow(uip);
uip.callbacks && uip.callbacks.onLoadFailure && uip.callbacks.onLoadFailure();
protected failure(state: UIState) {
this.closeUi(state);
this.onOpenFailure && this.onOpenFailure();
}
/**
* 根据预制件路径删除,预制件如在队列中也会被删除,如果该预制件存在多个也会一起删除
* @param prefabPath 预制路径
* @param isDestroy 移除后是否释放
*/
remove(prefabPath: string, isDestroy?: boolean): void {
let release: any = undefined;
if (isDestroy !== undefined) release = isDestroy;
// 界面移出舞台
const uip = this.ui_nodes.get(prefabPath);
if (uip) {
// 优先使用参数中控制的释放条件,如果未传递参数则用配置中的释放条件,默认不缓存关闭的界面
if (release === undefined) {
release = uip.config.destroy !== undefined ? uip.config.destroy : true;
}
remove(prefabPath: string): void {
const state = this.ui_nodes.get(prefabPath);
if (state) {
let release: boolean = state.config.destroy!;
// 不释放界面,缓存起来待下次使用
if (release === false) {
this.ui_cache.set(uip.config.prefab, uip);
}
if (release === false) this.ui_cache.set(state.config.prefab, state);
const node = uip.node;
const comp = node.getComponent(LayerUIElement)!;
// 界面移出舞台
const comp = state.node.getComponent(LayerUIElement)!;
comp.remove(release);
}
// 验证是否删除后台缓存界面
if (release === true) this.removeCache(prefabPath);
}
/** 删除缓存的界面,当缓存界面被移除舞台时,可通过此方法删除缓存界面 */
private removeCache(prefabPath: string) {
let vp = this.ui_cache.get(prefabPath);
if (vp) {
this.onCloseWindow(vp);
/** 删除缓存的界面,当调用 remove 移除舞台时,可通过此方法删除缓存界面 */
removeCache(prefabPath: string) {
const state = this.ui_cache.get(prefabPath);
if (state) {
this.ui_cache.delete(prefabPath);
const node = vp.node;
const comp = node.getComponent(LayerUIElement)!;
const comp = state.node.getComponent(LayerUIElement)!;
comp.remove(true);
node.destroy();
}
}
/** 显示界面 */
show(prefabPath: string) {
const state = this.ui_nodes.get(prefabPath);
if (state) state.node.parent = this;
}
/**
* 根据预制路径获取已打开界面的节点对象
* @param prefabPath 预制路径
*/
get(prefabPath: string): Node {
const vp = this.ui_nodes.get(prefabPath);
if (vp) return vp.node;
const state = this.ui_nodes.get(prefabPath);
if (state) return state.node;
return null!;
}
@@ -204,13 +218,13 @@ export class LayerUI extends Node {
const length = this.ui_nodes.array.length - 1;
for (let i = length; i >= 0; i--) {
const uip = this.ui_nodes.array[i];
this.remove(uip.config.prefab, isDestroy);
this.remove(uip.config.prefab);
}
this.ui_nodes.clear();
// 清除缓存中的界面
if (isDestroy) {
this.ui_cache.forEach((value: UIParams, prefabPath: string) => {
this.ui_cache.forEach((value: UIState, prefabPath: string) => {
this.removeCache(prefabPath);
});
}

View File

@@ -18,13 +18,11 @@ const { ccclass } = _decorator;
@ccclass('LayerUIElement')
export class LayerUIElement extends Component {
/** 视图参数 */
params: UIParams = null!;
state: UIState = null!;
/** 关闭窗口之前 */
onCloseWindowBefore: Function = null!;
/** 界面关闭回调 - 包括关闭动画播放完(辅助框架内存业务流程使用) */
private onCloseWindow: Function = null!;
onClose: Function = null!;
/** 窗口添加 */
/** 添加界面且界面设置到父节点之前 */
add(): Promise<boolean> {
return new Promise(async (resolve, reject) => {
// 触发窗口组件上添加到父节点后的事件
@@ -32,7 +30,7 @@ export class LayerUIElement extends Component {
const component: any = this.node.components[i];
const func = component[EventOnAdded];
if (func) {
if (await func.call(component, this.params.params) == false) {
if (await func.call(component, this.state.params.data) == false) {
resolve(false);
return;
}
@@ -40,8 +38,8 @@ export class LayerUIElement extends Component {
}
// 触发外部窗口显示前的事件(辅助实现自定义动画逻辑)
if (typeof this.params.callbacks.onAdded === "function") {
this.params.callbacks.onAdded(this.node, this.params.params);
if (typeof this.state.params.onAdded === "function") {
this.state.params.onAdded(this.node, this.state.params.data);
}
resolve(true);
@@ -49,14 +47,14 @@ export class LayerUIElement extends Component {
}
/** 删除节点该方法只能调用一次将会触发onBeforeRemoved回调 */
remove(isDestroy?: boolean) {
if (this.params.valid) {
remove(isDestroy: boolean) {
if (this.state.valid) {
// 触发窗口移除舞台之前事件
this.applyComponentsFunction(this.node, EventOnBeforeRemove, this.params.params);
this.applyComponentsFunction(this.node, EventOnBeforeRemove, this.state.params.data);
// 通知外部对象窗口组件上移除之前的事件(关闭窗口前的关闭动画处理)
if (typeof this.params.callbacks.onBeforeRemove === "function") {
this.params.callbacks.onBeforeRemove(this.node, this.onBeforeRemoveNext.bind(this, isDestroy));
if (typeof this.state.params.onBeforeRemove === "function") {
this.state.params.onBeforeRemove(this.node, this.onBeforeRemoveNext.bind(this, isDestroy));
}
else {
this.onBeforeRemoveNext(isDestroy);
@@ -68,37 +66,31 @@ export class LayerUIElement extends Component {
}
/** 窗口关闭前动画处理完后的回调方法,主要用于释放资源 */
private onBeforeRemoveNext(isDestroy?: boolean) {
this.onCloseWindowBefore && this.onCloseWindowBefore();
this.removed(this.params, isDestroy);
}
private onBeforeRemoveNext(isDestroy: boolean) {
this.state.valid = false;
/** 窗口组件中触发移除事件与释放窗口对象 */
private removed(uip: UIParams, isDestroy?: boolean) {
uip.valid = false;
if (uip.callbacks && typeof uip.callbacks.onRemoved === "function") {
uip.callbacks.onRemoved(this.node, uip.params);
if (this.state.params && typeof this.state.params.onRemoved === "function") {
this.state.params.onRemoved(this.node, this.state.params.data);
}
// 界面移除舞台事件
this.onCloseWindow && this.onCloseWindow(uip);
// 关闭动画播放完后,界面移除舞台事件
this.onClose && this.onClose();
if (isDestroy) {
// 释放界面显示对象
this.node.destroy();
// 释放界面相关资源
oops.res.release(uip.config.prefab, uip.config.bundle);
oops.res.release(this.state.config.prefab, this.state.config.bundle);
oops.log.logView(`【界面管理】释放【${uip.config.prefab}】界面资源`);
// oops.log.logView(`【界面管理】释放【${uip.config.prefab}】界面资源`);
}
else {
this.node.removeFromParent();
}
// 触发窗口组件上窗口移除之后的事件
this.applyComponentsFunction(this.node, EventOnRemoved, this.params.params);
this.applyComponentsFunction(this.node, EventOnRemoved, this.state.params.data);
}
private applyComponentsFunction(node: Node, funName: string, params: any) {
@@ -112,30 +104,33 @@ export class LayerUIElement extends Component {
}
onDestroy() {
this.params = null!;
this.onCloseWindowBefore = null!;
this.onCloseWindow = null!;
this.state = null!;
this.onClose = null!;
}
}
/** 本类型仅供gui模块内部使用请勿在功能逻辑中使用 */
export class UIParams {
export class UIState {
/** 界面唯一编号 */
uiid: string = null!;
/** 界面配置 */
config: UIConfig = null!;
/** 传递给打开界面的参数 */
params: any = null!;
/** 窗口事件 */
callbacks: UICallbacks = null!;
params: UIParam = null!;
/** 是否在使用状态 */
valid: boolean = true;
/** 界面根节点 */
node: Node = null!;
}
/*** 界面回调参数对象定义 */
export interface UICallbacks {
/*** 界面打开参数 */
export interface UIParam {
/** 自定义传递参数 */
data?: any;
/** 是否开启预加载(默认不开启 - 开启后加载完不显示界面) */
preload?: boolean;
/**
* 节点添加到层级以后的回调
* @param node 当前界面节点
@@ -143,13 +138,6 @@ export interface UICallbacks {
*/
onAdded?: (node: Node, params: any) => void,
/**
* 窗口节点 destroy 之后回调
* @param node 当前界面节点
* @param params 外部传递参数
*/
onRemoved?: (node: Node | null, params: any) => void,
/**
* 如果指定onBeforeRemoved则next必须调用否则节点不会被正常删除。
*
@@ -159,6 +147,10 @@ export interface UICallbacks {
*/
onBeforeRemove?: (node: Node, next: Function) => void,
/** 网络异常时,窗口加载失败回调 */
onLoadFailure?: () => void;
/**
* 窗口节点 destroy 之后回调
* @param node 当前界面节点
* @param params 外部传递参数
*/
onRemoved?: (node: Node, params: any) => void
}

View File

@@ -31,7 +31,7 @@ export interface UIConfig {
layer: string;
/** 预制资源相对路径 */
prefab: string;
/** 是否自动施放(默认自动释放) */
/** 是否自动施放(默认自动释放) */
destroy?: boolean;
/** -----弹窗属性----- */
@@ -39,7 +39,7 @@ export interface UIConfig {
vacancy?: boolean,
/** 是否打开窗口后显示背景遮罩(默认关闭) */
mask?: boolean;
/** 是否启动真机安全区域显示 */
/** 是否启动真机安全区域显示(默认关闭) */
safeArea?: boolean;
/** 界面弹出时的节点排序索引 */
siblingIndex?: number;

View File

@@ -40,9 +40,6 @@ export class DeviceUtil {
/** 是否为字节小游戏 */
static get isByteDance() { return sys.platform === sys.Platform.BYTEDANCE_MINI_GAME; }
/** 是否为百度小游戏 */
static get isBaidu() { return sys.platform === sys.Platform.BAIDU_MINI_GAME; }
/** 是否为 vivo 小游戏 */
static get isVivo() { return sys.platform === sys.Platform.VIVO_MINI_GAME; }

View File

@@ -6,91 +6,91 @@
*/
import { JsonAsset } from "cc";
import { ZipLoader } from "db://oops-framework/core/common/loader/ZipLoader";
import { resLoader } from "../common/loader/ResLoader";
/** 资源路径 */
const path: string = "config/game/";
const pathJson: string = "config/game/";
/** 压缩包资源路径 */
const pathZip: string = "config/game/game";
/** 数据缓存 */
const data: Map<string, any> = new Map();
/** JSON数据表工具 */
export class JsonUtil {
/** 是否使用压缩包加载配置表 */
static zip: boolean = false;
/**
* 通知资源名从缓存中获取一个Json数据表
* @param name 资源名
*/
static get(name: string): any {
if (data.has(name))
return data.get(name);
}
/**
* 通知资源名加载Json数据表
* @param name 资源名
* @param callback 资源加载完成回调
*/
static load(name: string, callback: Function): void {
if (data.has(name))
callback(data.get(name));
else {
const url = path + name;
resLoader.load(url, JsonAsset, (err: Error | null, content: JsonAsset) => {
if (err) {
console.warn(err.message);
callback(null);
}
else {
data.set(name, content.json);
resLoader.release(url);
callback(content.json);
}
});
}
if (data.has(name)) return data.get(name);
}
/**
* 异步加载Json数据表
* @param name 资源名
*/
static loadAsync(name: string): Promise<any> {
return new Promise((resolve, reject) => {
static load(name: string): Promise<any> {
return new Promise(async (resolve, reject) => {
let content: any = null;
if (data.has(name)) {
resolve(data.get(name))
resolve(data.get(name));
}
else {
const url = path + name;
resLoader.load(url, JsonAsset, (err: Error | null, content: JsonAsset) => {
if (err) {
console.warn(err.message);
resolve(null);
}
else {
data.set(name, content.json);
resLoader.release(url);
resolve(content.json);
}
});
const url = pathJson + name;
if (this.zip) {
content = await ZipLoader.getJson(pathZip, `${name}.json`);
}
else {
content = await resLoader.loadAsync(url, JsonAsset);
}
if (content) {
data.set(name, content.json);
resLoader.release(url);
resolve(content.json);
}
else {
resolve(null);
}
}
});
}
/** 加载所有配置表数据到缓存中 */
static loadDirAsync(): Promise<boolean> {
return new Promise((resolve, reject) => {
resLoader.loadDir(path, (err: Error | null, assets: JsonAsset[]) => {
if (err) {
console.warn(err.message);
resolve(false);
}
else {
assets.forEach(asset => {
data.set(asset.name, asset.json);
});
resLoader.releaseDir(path);
resolve(true);
}
});
/**
* 加载所有配置表数据到缓存中
* @param isZip 是否为压缩包
* @param zipNames 压缩包内的资源名列表
*/
static loadDir(zipNames?: string[]): Promise<void> {
return new Promise(async (resolve, reject) => {
if (this.zip && zipNames) {
await ZipLoader.load(pathZip);
zipNames.forEach(name => {
data.set(name, ZipLoader.getJson(pathZip, `${name}.json`));
});
ZipLoader.release(pathZip);
resolve();
}
else {
resLoader.loadDir(pathJson, (err: Error | null, assets: JsonAsset[]) => {
if (err) {
console.error(err.message);
resolve();
}
else {
assets.forEach(asset => {
data.set(asset.name, asset.json);
});
resLoader.releaseDir(pathJson);
resolve();
}
});
}
});
}

View File

@@ -6,7 +6,7 @@ export class TimeUtil {
* @param time2 结束时间
* @returns
*/
public static daysBetween(time1: number | string | Date, time2: number | string | Date): number {
static daysBetween(time1: number | string | Date, time2: number | string | Date): number {
if (time2 == undefined) {
time2 = +new Date();
}
@@ -18,7 +18,7 @@ export class TimeUtil {
}
/** 间隔秒数,时间顺序无要求,最后会获取绝对值 */
public static secsBetween(time1: number, time2: number) {
static secsBetween(time1: number, time2: number) {
let dates = Math.abs((time2 - time1)) / (1000);
dates = Math.floor(dates) + 1;
return dates;
@@ -28,7 +28,7 @@ export class TimeUtil {
* 代码休眠时间
* @param ms 毫秒
*/
public static async sleep(ms: number) {
static async sleep(ms: number) {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();

View File

@@ -10,35 +10,35 @@ import { IControl } from './IControl';
export abstract class BTreeNode implements IControl {
protected _control!: IControl;
public title: string;
title: string;
public constructor() {
constructor() {
this.title = this.constructor.name;
}
public start(blackboard?: any) {
start(blackboard?: any) {
}
public end(blackboard?: any) {
end(blackboard?: any) {
}
public abstract run(blackboard?: any): void;
abstract run(blackboard?: any): void;
public setControl(control: IControl) {
setControl(control: IControl) {
this._control = control;
}
public running(blackboard?: any) {
running(blackboard?: any) {
this._control.running(this);
}
public success() {
success() {
this._control.success();
}
public fail() {
fail() {
this._control.fail();
}
}

View File

@@ -17,7 +17,7 @@ export class BehaviorTree implements IControl {
private _blackboard: any;
/** 是否已开始执行 */
public get started(): boolean {
get started(): boolean {
return this._started;
}
@@ -26,7 +26,7 @@ export class BehaviorTree implements IControl {
* @param node 根节点
* @param blackboard 外部参数对象
*/
public constructor(node: BTreeNode, blackboard?: any) {
constructor(node: BTreeNode, blackboard?: any) {
countUnnamed += 1;
this.title = node.constructor.name + '(btree_' + (countUnnamed) + ')';
this._root = node;
@@ -34,12 +34,12 @@ export class BehaviorTree implements IControl {
}
/** 设置行为逻辑中的共享数据 */
public setObject(blackboard: any) {
setObject(blackboard: any) {
this._blackboard = blackboard;
}
/** 执行行为树逻辑 */
public run() {
run() {
if (this._started) {
console.error(`行为树【${this.title}】未调用步骤,在最后一次调用步骤时有一个任务未完成`);
}
@@ -52,16 +52,16 @@ export class BehaviorTree implements IControl {
node.run(this._blackboard);
}
public running(node: BTreeNode) {
running(node: BTreeNode) {
this._started = false;
}
public success() {
success() {
this._current.end(this._blackboard);
this._started = false;
}
public fail() {
fail() {
this._current.end(this._blackboard);
this._started = false;
}

View File

@@ -10,7 +10,7 @@ import { BTreeNode } from './BTreeNode';
/** 复合节点 */
export abstract class BranchNode extends BTreeNode {
/** 子节点数组 */
public children: Array<BTreeNode>;
children: Array<BTreeNode>;
/** 当前任务索引 */
protected _actualTask!: number;
/** 正在运行的节点 */
@@ -19,17 +19,17 @@ export abstract class BranchNode extends BTreeNode {
/** 外部参数对象 */
protected _blackboard: any;
public constructor(nodes: Array<BTreeNode>) {
constructor(nodes: Array<BTreeNode>) {
super();
this.children = nodes || [];
}
public start() {
start() {
this._actualTask = 0;
super.start();
}
public run(blackboard?: any) {
run(blackboard?: any) {
if (this.children.length == 0) { // 没有子任务直接视为执行失败
this._control.fail();
}
@@ -53,17 +53,17 @@ export abstract class BranchNode extends BTreeNode {
node.run(this._blackboard);
}
public running(node: BTreeNode) {
running(node: BTreeNode) {
this._nodeRunning = node;
this._control.running(node);
}
public success() {
success() {
this._nodeRunning = null;
this._runningNode.end(this._blackboard);
}
public fail() {
fail() {
this._nodeRunning = null;
this._runningNode.end(this._blackboard);
}

View File

@@ -12,7 +12,7 @@ import { BTreeNode } from './BTreeNode';
* 如果装饰器是true 它所在的子树会被执行如果是false 所在的子树不会被执行
*/
export class Decorator extends BTreeNode {
public node!: BTreeNode;
node!: BTreeNode;
constructor(node?: string | BTreeNode) {
super()
@@ -25,17 +25,17 @@ export class Decorator extends BTreeNode {
this.node = BehaviorTree.getNode(node);
}
public start() {
start() {
this.node.setControl(this);
this.node.start();
super.start();
}
public end() {
end() {
this.node.end();
}
public run(blackboard: any) {
run(blackboard: any) {
this.node.run(blackboard);
}
}

View File

@@ -11,12 +11,12 @@ import { BranchNode } from './BranchNode';
* 只要子节点有一个返回true则停止执行其它子节点并且Selector返回true。如果所有子节点都返回false则Selector返回false。
*/
export class Selector extends BranchNode {
public success() {
success() {
super.success()
this._control.success();
}
public fail() {
fail() {
super.fail()
this._actualTask += 1;

View File

@@ -16,7 +16,7 @@ export class Sequence extends BranchNode {
super(nodes);
}
public success() {
success() {
super.success();
this._actualTask += 1;
@@ -28,7 +28,7 @@ export class Sequence extends BranchNode {
}
}
public fail() {
fail() {
super.fail();
this._control.fail();
}

View File

@@ -42,6 +42,7 @@ export namespace ecs {
export interface IComp {
canRecycle: boolean;
ent: Entity;
tid: number;
reset(): void;
}
@@ -145,17 +146,14 @@ export namespace ecs {
if (ctor.tid === -1) {
ctor.tid = ECSModel.compTid++;
ctor.compName = name;
ECSModel.compCtors.push(ctor); // 注册不同类型的组件
if (canNew) {
ECSModel.compCtors.push(ctor); // 注册不同类型的组件
ECSModel.compPools.set(ctor.tid, []);
}
else {
ECSModel.compCtors.push(null!);
}
ECSModel.compAddOrRemove.set(ctor.tid, []);
}
else {
throw new Error(`重复注册组件: ${name}.`);
throw new Error(`ECS 组件重复注册: ${name}.`);
}
}
}

View File

@@ -14,18 +14,18 @@ import { ECSEntity } from "./ECSEntity";
export abstract class ECSComp implements ecs.IComp {
/** 组件的类型编号,-1表示未给该组件分配编号 */
static tid: number = -1;
/** 组件名 */
static compName: string;
/** 拥有该组件的实体 */
ent!: ECSEntity;
/**
* 是否可回收组件对象,默认情况下都是可回收的
* 注如果该组件对象是由ecs系统外部创建的则不可回收需要用户自己手动进行回收
*/
canRecycle: boolean = true;
/** 拥有该组件的实体 */
ent!: ECSEntity;
/** 组件的类型编号 */
tid: number = -1;
/**
* 组件被回收时会调用这个接口。可以在这里重置数据,或者解除引用

View File

@@ -81,22 +81,30 @@ export class ECSEntity {
this._parent = value;
}
private _children: Map<number, ECSEntity> | null = null;
/** 子实体集合 */
get children(): Map<number, ECSEntity> {
if (this._children == null) {
this._children = new Map<number, ECSEntity>();
}
return this._children;
/** 子实体 */
private childs: Map<number, ECSEntity> = null!;
/** 获取子实体 */
getChild<T>(eid: number) {
return this.childs.get(eid) as T;
}
/**
* 添加子实体
* @param entity 被添加的实体对象
* @returns 子实体的唯一编号, -1表示添加失败
*/
addChild(entity: ECSEntity) {
addChild(entity: ECSEntity): number {
if (this.childs == null) this.childs = new Map<number, ECSEntity>();
if (this.childs.has(entity.eid)) {
console.warn(`子实体${entity.name}已存在`);
return -1;
}
entity._parent = this;
this.children.set(entity.eid, entity);
this.childs.set(entity.eid, entity);
return entity.eid;
}
/**
@@ -106,10 +114,10 @@ export class ECSEntity {
* @returns
*/
removeChild(entity: ECSEntity, isDestroy = true) {
if (this.children == null) return;
if (this.childs == null) return;
entity.parent = null;
this.children.delete(entity.eid);
this.childs.delete(entity.eid);
if (isDestroy) entity.destroy();
}
@@ -154,6 +162,7 @@ export class ECSEntity {
// @ts-ignore
this[ctor.compName] = comp;
this.compTid2Ctor.set(compTid, ctor);
comp.tid = compTid;
comp.ent = this;
// 广播实体添加组件的消息
broadcastCompAddOrRemove(this, compTid);
@@ -173,9 +182,11 @@ export class ECSEntity {
this[tmpCtor.compName] = ctor;
this.compTid2Ctor.set(compTid, tmpCtor);
//@ts-ignore
ctor.ent = this;
ctor.tid = compTid;
//@ts-ignore
ctor.canRecycle = false;
//@ts-ignore
ctor.ent = this;
broadcastCompAddOrRemove(this, compTid);
return this;
@@ -272,11 +283,11 @@ export class ECSEntity {
}
// 移除模块上所有子模块
if (this._children) {
this._children.forEach(e => {
if (this.childs) {
this.childs.forEach(e => {
this.removeChild(e);
});
this._children = null;
this.childs = null!;
}
// 移除实体上所有组件

View File

@@ -1,10 +1,26 @@
declare global {
interface Date {
/**
* 时间格式化
* @param format 时间格式例如yy-mm-dd hh:mm:ss
*/
format(format: string): string;
/**
* 时间加法
* @param addMillis 时间加法,单位毫秒
*/
addTime(addMillis: number): Date;
/**
* 验证时间是否在指定范围
* @param t1 范围开始时间
* @param t2 范围结束时间
*/
range(t1: number | Date, t2: number | Date): boolean;
}
}
/** 格式化时间字符串 */
Date.prototype.format = function (format: string): string {
const year: number = this.getFullYear();
const month: number = this.getMonth() + 1;
@@ -35,4 +51,27 @@ Date.prototype.format = function (format: string): string {
return r;
};
export { };
Date.prototype.addTime = function (addMillis: number): Date {
return new Date(this.getTime() + addMillis);
}
Date.prototype.range = function (d1: number | Date, d2: number | Date): boolean {
let t1: number = -1;
let t2: number = -1;
if (d1 instanceof Date)
t1 = d1.getTime();
else
t1 = d1;
if (d2 instanceof Date)
t2 = d2.getTime();
else
t2 = d2;
let now = this.getTime();
if (now >= t1 && now < t2) {
return true;
}
return false;
}
export { };

View File

@@ -59,7 +59,7 @@ export default class ButtonSimple extends Component {
}
/** 短按触摸音效 */
protected async playEffect() {
protected playEffect() {
if (ButtonSimple.effectPath) {
oops.audio.playEffect(ButtonSimple.effectPath);
}

View File

@@ -1,32 +1,32 @@
import { Label, _decorator } from "cc";
import { EDITOR } from "cc/env";
import { oops } from "../../../core/Oops";
import { EventMessage } from "../../../core/common/event/EventMessage";
import { TimeUtil } from "../../../core/utils/TimeUtils";
import { EDITOR } from "cc/env";
const { ccclass, property, menu } = _decorator;
/** 倒计时标签 */
@ccclass("LabelTime")
@menu('OopsFramework/Label/LabelTime (倒计时标签)')
@menu("OopsFramework/Label/LabelTime (倒计时标签)")
export default class LabelTime extends Label {
@property({
tooltip: "到计时间总时间(单位秒)"
tooltip: "到计时间总时间(单位秒)",
})
countDown: number = 1000;
@property({
tooltip: "天数数据格式化"
tooltip: "天数数据格式化",
})
dayFormat: string = "{0} day";
dayFormat: string = "{0}天{1}小时";
@property({
tooltip: "时间格式化"
tooltip: "时间格式化",
})
timeFormat: string = "{0}:{1}:{2}";
@property({
tooltip: "是否有00"
tooltip: "时间是否有固定二位数据",
})
zeroize: boolean = true;
@@ -35,9 +35,9 @@ export default class LabelTime extends Label {
})
paused: boolean = false;
private backStartTime: number = 0; // 进入后台开始时间
private dateDisable!: boolean; // 时间能否由天数显示
private result!: string; // 时间结果字符串
private backStartTime: number = 0; // 进入后台开始时间
private dateDisable!: boolean; // 时间能否由天数显示
private result!: string; // 时间结果字符串
/** 每秒触发事件 */
onSecond: Function = null!;
@@ -45,10 +45,9 @@ export default class LabelTime extends Label {
onComplete: Function = null!;
private replace(value: string, ...args: any): string {
return value.replace(/\{(\d+)\}/g,
function (m, i) {
return args[i];
});
return value.replace(/\{(\d+)\}/g, function (m, i) {
return args[i];
});
}
/** 格式化字符串 */
@@ -75,7 +74,7 @@ export default class LabelTime extends Label {
let dataFormat = this.dayFormat;
let index = dataFormat.indexOf("{1}");
if (hours == 0 && index > -1) {
dataFormat = dataFormat.substring(0, index - 1);
dataFormat = dataFormat.substring(0, index);
}
let df = dataFormat;
if (date > 1 && dataFormat.indexOf("days") < 0) {
@@ -84,23 +83,21 @@ export default class LabelTime extends Label {
if (date < 2) {
df = df.replace("days", "day");
}
this.result = this.replace(df, date, hours); // 如果天大于1则显示 "1 Day..."
if (this.zeroize) {
this.result = this.replace(df, date, this.coverString(hours));
}
else {
this.result = this.replace(df, date, hours); // 如果天大于1则显示 "1 Day..."
}
}
else {
hours += date * 24;
if (this.zeroize) {
this.result = this.replace(
this.timeFormat,
this.coverString(hours),
this.coverString(minutes),
this.coverString(seconds)); // 否则显示 "01:12:24"
this.result = this.replace(this.timeFormat, this.coverString(hours), this.coverString(minutes), this.coverString(seconds)); // 否则显示 "01:12:24"
}
else {
this.result = this.replace(
this.timeFormat,
hours,
minutes,
seconds);
this.result = this.replace(this.timeFormat, hours, minutes, seconds);
}
}
this.string = this.result;
@@ -108,8 +105,7 @@ export default class LabelTime extends Label {
/** 个位数的时间数据将字符串补位 */
private coverString(value: number) {
if (value < 10)
return "0" + value;
if (value < 10) return "0" + value;
return value.toString();
}
@@ -123,7 +119,7 @@ export default class LabelTime extends Label {
* @param second 倒计时时间(单位秒)
*/
setTime(second: number) {
this.countDown = second; // 倒计时,初始化显示字符串
this.countDown = second; // 倒计时,初始化显示字符串
this.timing_end();
this.timing_start();
this.format();
@@ -140,15 +136,33 @@ export default class LabelTime extends Label {
this.format();
}
start() {
onLoad() {
if (!EDITOR) {
oops.message.on(EventMessage.GAME_SHOW, this.onGameShow, this);
oops.message.on(EventMessage.GAME_HIDE, this.onGameHide, this);
}
}
start() {
if (this.countDown <= 0) return;
this.timing_start();
this.format();
}
onEnable() {
super.onEnable();
if (!EDITOR) {
this.onGameShow();
}
}
onDisable() {
super.onDisable();
if (!EDITOR) {
this.onGameHide();
}
}
onDestroy() {
if (!EDITOR) {
oops.message.off(EventMessage.GAME_SHOW, this.onGameShow, this);
@@ -175,6 +189,12 @@ export default class LabelTime extends Label {
}
private onScheduleSecond() {
if (this.countDown == 0) {
this.format();
this.onScheduleComplete();
return;
}
this.countDown--;
this.format();
if (this.onSecond) this.onSecond(this.node);
@@ -187,15 +207,17 @@ export default class LabelTime extends Label {
private onScheduleComplete() {
this.timing_end();
this.format();
this.unschedule(this.onScheduleSecond);
if (this.onComplete) this.onComplete(this.node);
}
/** 开始计时 */
private timing_start() {
timing_start() {
this.schedule(this.onScheduleSecond, 1);
}
private timing_end() {
/** 关闭计时 */
timing_end() {
this.unscheduleAllCallbacks();
}
}
}

View File

@@ -53,7 +53,7 @@ export class LanguageManager {
* @param language 语言名
* @param callback 多语言资源数据加载完成回调
*/
setLanguage(language: string, callback?: (success: boolean) => void) {
setLanguage(language: string, callback?: Function) {
if (language == null || language == "") {
language = this._defaultLanguage;
}
@@ -68,7 +68,7 @@ export class LanguageManager {
}
if (language === LanguageData.current) {
callback && callback(false);
callback && callback();
return;
}
@@ -78,7 +78,7 @@ export class LanguageManager {
LanguageData.current = language;
this._languagePack.updateLanguage(language);
this._languagePack.releaseLanguageAssets(oldLanguage);
callback && callback(true);
callback && callback();
});
}

View File

@@ -44,7 +44,7 @@ export class LanguagePack {
/** 多语言Excel配置表数据 */
private loadTable(lang: string): Promise<void> {
return new Promise(async (resolve, reject) => {
let json = await JsonUtil.loadAsync("Language");
let json = await JsonUtil.load("Language");
if (json) {
LanguageData.language.set(LanguageDataType.Excel, json);
Logger.instance.logConfig("config/game/Language", "下载语言包 table 资源");

View File

@@ -1,3 +1,5 @@
import { LanguageData } from "../gui/language/LanguageData";
/**
* 数值格式化函数, 通过语义解析自动设置值的范围
* //整数
@@ -93,19 +95,20 @@ class StringFormat {
}
/** 将数字缩短显示为KMBT单位 大写,目前只支持英文 */
private kmbt(value: number, lang: string = 'en') {
private kmbt(value: number) {
//10^4=万, 10^8=亿,10^12=兆,10^16=京,
let counts = [1000, 1000000, 1000000000, 1000000000000];
let units = ['', 'K', 'M', 'B', 'T'];
let counts: number[] = null!;
let units: string[] = null!;
switch (lang) {
switch (LanguageData.current) {
case 'zh':
//10^4=万, 10^8=亿,10^12=兆,10^16=京,
counts = [10000, 100000000, 1000000000000, 10000000000000000];
units = ['', '万', '亿', '兆', '京'];
break;
default:
counts = [1000, 1000000, 1000000000, 1000000000000];
units = ['', 'K', 'M', 'B', 'T'];
break;
}

View File

@@ -0,0 +1,85 @@
/*
* @Author: dgflash
* @Date: 2025-09-18 10:20:51
* @LastEditors: dgflash
* @LastEditTime: 2025-09-18 17:20:51
*/
import { EventDispatcher } from "../../core/common/event/EventDispatcher";
import { ListenerFunc } from "../../core/common/event/EventMessage";
import { CCEntity } from "./CCEntity";
/** 业务逻辑 */
export class CCBusiness<T extends CCEntity> {
ent!: T;
/** 业务逻辑初始化 */
protected init() {
}
destroy() {
// 释放消息对象
if (this._event) {
this._event.destroy();
this._event = null;
}
}
//#region 全局事件管理
private _event: EventDispatcher | null = null;
/** 全局事件管理器 */
private get event(): EventDispatcher {
if (this._event == null) this._event = new EventDispatcher();
return this._event;
}
/**
* 注册全局事件
* @param event 事件名
* @param listener 处理事件的侦听器函数
* @param object 侦听函数绑定的this对象
*/
on(event: string, listener: ListenerFunc, object: any) {
this.event.on(event, listener, object);
}
/**
* 移除全局事件
* @param event 事件名
*/
off(event: string) {
this.event.off(event);
}
/**
* 触发全局事件
* @param event 事件名
* @param args 事件参数
*/
dispatchEvent(event: string, ...args: any) {
this.event.dispatchEvent(event, ...args);
}
/**
* 批量设置全局事件
* @example
* this.setEvent("onGlobal");
* this.dispatchEvent("onGlobal", "全局事件");
*
* onGlobal(event: string, args: any) { console.log(args) };
*/
protected setEvent(...args: string[]) {
const self: any = this;
for (const name of args) {
const func = self[name];
if (func)
this.on(name, func, this);
else
console.error(`名为【${name}】的全局事方法不存在`);
}
}
//#endregion
}

View File

@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "5908a4e6-3359-48b6-95e0-a3b44ea50790",
"uuid": "4a753a57-2867-478e-b770-c05d96535c4e",
"files": [],
"subMetas": {},
"userData": {}

View File

@@ -0,0 +1,186 @@
import { __private, Node } from "cc";
import { resLoader } from "../../core/common/loader/ResLoader";
import { gui } from "../../core/gui/Gui";
import { LayerUIElement, UIParam } from "../../core/gui/layer/LayerUIElement";
import { oops } from "../../core/Oops";
import { ViewUtil } from "../../core/utils/ViewUtil";
import { ecs } from "../../libs/ecs/ECS";
import { ECSEntity } from "../../libs/ecs/ECSEntity";
import { CompType } from "../../libs/ecs/ECSModel";
import { CCBusiness } from "./CCBusiness";
import { CCView } from "./CCView";
import { CCViewVM } from "./CCViewVM";
export type ECSCtor<T extends ecs.Comp> = __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>;
export type ECSView = CCViewVM<CCEntity> | CCView<CCEntity>;
/** ECS 游戏模块实体 */
export abstract class CCEntity extends ecs.Entity {
//#region 子模块管理
/** 单例子实体 */
private singletons: Map<any, ECSEntity> = null!;
/** 添加单例子实体 */
addChildSingleton<T>(cls: any): T {
if (this.singletons == null) this.singletons = new Map();
if (this.singletons.has(cls)) {
console.error(`${cls.name} 单例子实体已存在`);
return null!;
}
let entity = cls.create();
this.singletons.set(cls, entity);
this.addChild(entity);
return entity as T;
}
/** 获取单例子实体 */
getChildSingleton<T>(cls: any): T {
return this.singletons.get(cls) as T;
}
/** 移除单例子实体 */
removeChildSingleton(cls: any) {
let entity = this.singletons.get(cls);
if (entity) {
this.singletons.delete(cls);
this.removeChild(entity);
}
}
//#endregion
//#region 游戏视图层管理
/**
* 通过资源内存中获取预制上的组件添加到ECS实体中
* @param ctor 界面逻辑组件
* @param parent 显示对象父级
* @param path 显示资源地址
* @param bundleName 资源包名称
*/
addPrefab<T extends ECSView>(ctor: ECSCtor<T>, parent: Node, path: string, bundleName: string = resLoader.defaultBundleName) {
const node = ViewUtil.createPrefabNode(path, bundleName);
const comp = node.getComponent(ctor)!;
this.add(comp);
node.parent = parent;
}
/**
* 添加视图层组件
* @param ctor 界面逻辑组件
* @param params 界面参数
* @returns 界面节点
*/
addUi<T extends ECSView>(ctor: ECSCtor<T>, params?: UIParam): Promise<Node> {
return new Promise<Node>(async (resolve, reject) => {
const key = gui.internal.getKey(ctor);
if (key) {
if (params == null) {
params = { preload: true };
}
else {
params.preload = true;
}
const node = await oops.gui.open(key, params);
const comp = node.getComponent(ctor) as ecs.Comp;
this.add(comp);
oops.gui.show(key);
resolve(node);
}
else {
console.error(`${key} 界面组件未使用 gui.register 注册`);
}
});
}
/**
* 移除视图层组件
* @param ctor 界面逻辑组件
*/
removeUi(ctor: CompType<ecs.IComp>) {
const key = gui.internal.getKey(ctor);
if (key) {
const node = oops.gui.get(key);
if (node == null) {
console.error(`${key} 界面重复关闭`);
return;
}
const comp = node.getComponent(LayerUIElement);
if (comp) {
// 处理界面关闭动画播放完成后移除ECS组件避免使用到组件实体数据还在动画播放时在使用导致的空对象问题
comp.onClose = this.remove.bind(this, ctor);
oops.gui.remove(key);
}
}
else {
this.remove(ctor);
}
}
//#endregion
//#region 游戏业务层管理
/** 模块业务逻辑组件 */
private businesss: Map<any, CCBusiness<CCEntity>> = null!;
/**
* 批量添加组件
* @param ctors 组件类
* @returns
*/
addBusinesss<T extends CCBusiness<CCEntity>>(...clss: any[]) {
for (let ctor of clss) {
this.addBusiness<T>(ctor);
}
}
/**
* 添加业务逻辑组件
* @param cls 业务逻辑组件类
* @returns 业务逻辑组件实例
*/
addBusiness<T extends CCBusiness<CCEntity>>(cls: any): T {
if (this.businesss == null) this.businesss = new Map();
if (this.businesss.has(cls)) {
console.error(`${cls.name} 业务逻辑组件已存在`);
return null!;
}
let business = new cls();
business.ent = this;
business.init();
this.businesss.set(cls, business);
return business as T;
}
/**
* 获取业务逻辑组件
* @param cls 业务逻辑组件类
* @returns 业务逻辑组件实例
*/
getBusiness<T extends CCBusiness<CCEntity>>(cls: any): T {
return this.businesss.get(cls) as T;
}
/**
* 移除业务逻辑组件
* @param cls 业务逻辑组件类
*/
removeBusiness(cls: any) {
let business = this.businesss.get(cls);
if (business) this.businesss.delete(cls);
}
//#endregion
destroy(): void {
if (this.singletons) {
this.singletons.clear();
this.singletons = null!;
}
if (this.businesss) {
this.businesss.forEach(business => business.destroy());
this.businesss.clear();
this.businesss = null!;
}
super.destroy();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "59805570-9a5d-4894-aac0-14a60c895c74",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,46 +1,56 @@
/*
* @Author: dgflash
* @Date: 2021-11-11 19:05:32
* @LastEditors: dgflash
* @LastEditTime: 2022-09-06 17:20:51
*/
import { _decorator } from 'cc';
import { GameComponent } from './GameComponent';
import { ecs } from '../../libs/ecs/ECS';
const { ccclass, property } = _decorator;
/**
*
*
*
* 1. cc.Component ecs.Comp
* 2.
* 3. cc.Node对象
*
*
* 1.
*
* @example
@ccclass('RoleViewComp')
@ecs.register('RoleView', false)
export class RoleViewComp extends CCComp {
@property({ type: sp.Skeleton, tooltip: '角色动画' })
spine: sp.Skeleton = null!;
onLoad(){
}
}
*/
@ccclass('CCComp')
export abstract class CCComp extends GameComponent implements ecs.IComp {
static tid: number = -1;
static compName: string;
canRecycle!: boolean;
ent!: ecs.Entity;
abstract reset(): void;
/*
* @Author: dgflash
* @Date: 2021-11-11 19:05:32
* @LastEditors: dgflash
* @LastEditTime: 2022-09-06 17:20:51
*/
import { ecs } from '../../libs/ecs/ECS';
import { ECSModel } from '../../libs/ecs/ECSModel';
import { CCEntity } from './CCEntity';
import { GameComponent } from './GameComponent';
/**
* ECS
*
*
* 1. cc.Component ecs.Comp
* 2.
* 3. cc.Node对象
*
*
* 1.
*
* @example
@ccclass('RoleViewComp')
@ecs.register('RoleView', false)
export class RoleViewComp extends CCView<Role> {
@property({ type: sp.Skeleton, tooltip: '角色动画' })
spine: sp.Skeleton = null!;
onLoad(){
}
}
*/
export abstract class CCView<T extends CCEntity> extends GameComponent implements ecs.IComp {
static tid: number = -1;
static compName: string;
canRecycle!: boolean;
ent!: T;
tid: number = -1;
/** 从父节点移除自己 */
remove() {
const cct = ECSModel.compCtors[this.tid];
if (this.ent) {
this.ent.removeUi(cct);
}
else {
console.error(`组件 ${this.name} 移除失败,组件未注册`);
}
}
abstract reset(): void;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7f4397f9-04cd-42e3-a323-d468c93b19c0",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,58 +1,68 @@
/*
* @Author: dgflash
* @Date: 2021-11-11 19:05:32
* @LastEditors: dgflash
* @LastEditTime: 2022-09-06 17:22:05
*/
import { _decorator } from 'cc';
import { ecs } from '../../libs/ecs/ECS';
import VMParent from '../../libs/model-view/VMParent';
const { ccclass, property } = _decorator;
/**
* MVVM
*
* 使
* 1. cc.Component ecs.Comp
* 2.
* 3. cc.Node对象
* 4. VMParent
*
*
* 1.
*
* @example
@ccclass('LoadingViewComp')
@ecs.register('LoadingView', false)
export class LoadingViewComp extends CCVMParentComp {
// VM 组件绑定数据
data: any = {
// 加载资源当前进度
finished: 0,
// 加载资源最大进度
total: 0,
// 加载资源进度比例值
progress: "0",
// 加载流程中提示文本
prompt: ""
};
private progress: number = 0;
reset(): void {
}
}
*/
@ccclass('CCVMParentComp')
export abstract class CCVMParentComp extends VMParent implements ecs.IComp {
static tid: number = -1;
static compName: string;
canRecycle!: boolean;
ent!: ecs.Entity;
abstract reset(): void;
/*
* @Author: dgflash
* @Date: 2021-11-11 19:05:32
* @LastEditors: dgflash
* @LastEditTime: 2022-09-06 17:22:05
*/
import { ecs } from '../../libs/ecs/ECS';
import { ECSModel } from '../../libs/ecs/ECSModel';
import VMParent from '../../libs/model-view/VMParent';
import { CCEntity } from './CCEntity';
/**
* MVVM ECS
*
* 使
* 1. cc.Component ecs.Comp
* 2.
* 3. cc.Node对象
* 4. VMParent
*
*
* 1.
*
* @example
@ccclass('LoadingViewComp')
@ecs.register('LoadingView', false)
export class LoadingViewComp extends CCViewVM<Initialize> {
// VM 组件绑定数据
data: any = {
// 加载资源当前进度
finished: 0,
// 加载资源最大进度
total: 0,
// 加载资源进度比例值
progress: "0",
// 加载流程中提示文本
prompt: ""
};
private progress: number = 0;
reset(): void {
}
}
*/
export abstract class CCViewVM<T extends CCEntity> extends VMParent implements ecs.IComp {
static tid: number = -1;
static compName: string;
canRecycle!: boolean;
ent!: T;
tid: number = -1;
/** 从父节点移除自己 */
remove() {
const cct = ECSModel.compCtors[this.tid];
if (this.ent) {
this.ent.removeUi(cct);
}
else {
console.error(`组件 ${this.name} 移除失败,组件未注册`);
}
}
abstract reset(): void;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "33d31b2d-c771-4759-9fc6-96bbd5bcfa15",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -480,6 +480,12 @@ export class GameComponent extends Component {
/** 游戏旋转屏幕事件回调 */
protected onGameOrientation(): void { }
//#endregion
/** 移除自己 */
remove() {
oops.gui.removeByNode(this.node);
}
protected onDestroy() {
// 释放消息对象
if (this._event) {

View File

@@ -1,97 +0,0 @@
import { Node, __private } from "cc";
import { oops } from "../../core/Oops";
import { resLoader } from "../../core/common/loader/ResLoader";
import { Uiid } from "../../core/gui/layer/LayerEnum";
import { LayerUIElement, UICallbacks } from "../../core/gui/layer/LayerUIElement";
import { ViewUtil } from "../../core/utils/ViewUtil";
import { ecs } from "../../libs/ecs/ECS";
import { CompType } from "../../libs/ecs/ECSModel";
import { CCComp } from "./CCComp";
import { CCVMParentComp } from "./CCVMParentComp";
export type ECSCtor<T extends ecs.Comp> = __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>;
export class ModuleUtil {
/**
* 添加界面组件
* @param ent 模块实体
* @param ctor 界面逻辑组件
* @param uiId 界面资源编号
* @param uiArgs 界面参数
*/
static addViewUi<T extends CCVMParentComp | CCComp>(ent: ecs.Entity, ctor: ECSCtor<T>, uiId: Uiid, uiArgs: any = null) {
const uic: UICallbacks = {
onAdded: (node: Node, params: any) => {
const comp = node.getComponent(ctor) as ecs.Comp;
//@ts-ignore
if (!ent.has(ctor)) ent.add(comp);
}
};
oops.gui.open(uiId, uiArgs, uic);
}
/**
* 异步添加视图层组件
* @param ent 模块实体
* @param ctor 界面逻辑组件
* @param uiId 界面资源编号
* @param uiArgs 界面参数
* @returns 界面节点
*/
static addViewUiAsync<T extends CCVMParentComp | CCComp>(ent: ecs.Entity, ctor: ECSCtor<T>, uiId: Uiid, uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>((resolve, reject) => {
const uic: UICallbacks = {
onAdded: (node: Node, params: any) => {
const comp = node.getComponent(ctor) as ecs.Comp;
ent.add(comp);
resolve(node);
},
onLoadFailure: () => {
resolve(null);
}
};
oops.gui.open(uiId, uiArgs, uic);
});
}
/**
* 通过资源内存中获取预制上的组件添加到ECS实体中
* @param ent 模块实体
* @param ctor 界面逻辑组件
* @param parent 显示对象父级
* @param url 显示资源地址
* @param bundleName 资源包名称
*/
static addView<T extends CCVMParentComp | CCComp>(ent: ecs.Entity, ctor: ECSCtor<T>, parent: Node, url: string, bundleName: string = resLoader.defaultBundleName) {
const node = ViewUtil.createPrefabNode(url, bundleName);
const comp = node.getComponent(ctor)!;
ent.add(comp);
node.parent = parent;
}
/**
* 业务实体上移除界面组件
* @param ent 模块实体
* @param ctor 界面逻辑组件
* @param uiId 界面资源编号
* @param isDestroy 是否释放界面缓存(默认为释放界面缓存)
* @param onRemoved 窗口关闭完成事件
*/
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(LayerUIElement);
if (comp) {
comp.onCloseWindowBefore = () => {
// 移除ECS显示组件
if (isDestroy) ent.remove(ctor, isDestroy);
if (onRemoved) onRemoved();
};
oops.gui.remove(uiId, isDestroy);
}
}
}

View File

@@ -10,7 +10,7 @@ const keys = (Object.keys(buildTimeConstants) as (keyof typeof buildTimeConstant
/* 游戏运行环境 */
export class BuildTimeConstants {
constructor() {
toString() {
const keyNameMaxLen = keys.reduce((len, key) => Math.max(len, key.length), 0);
const enviroment = `${keys.map((key) => {
const value = buildTimeConstants[key];

View File

@@ -5,13 +5,14 @@
* @LastEditTime: 2022-11-01 15:47:16
*/
import { BuildTimeConstants } from "./BuildTimeConstants";
import { GameConfig } from "./GameConfig";
import { GameQueryConfig } from "./GameQueryConfig";
/** 游戏配置静态访问类 */
export class Config {
/** 环境常量 */
// btc!: BuildTimeConstants;
btc: BuildTimeConstants = new BuildTimeConstants();
/** 游戏配置数据,版本号、支持语种等数据 */
game!: GameConfig;

View File

@@ -4,38 +4,71 @@
* @LastEditors: dgflash
* @LastEditTime: 2023-02-14 14:27:22
*/
import { oops } from "../../core/Oops";
/** 游戏自定义参数分组类型 */
export enum GameConfigCustomType {
/** 开发环境 */
Dev = "dev",
/** 测试环境 */
Test = "test",
/** 生产环境 */
Prod = "prod",
}
/* 游戏配置解析,对应 resources/config/config.json 配置 */
export class GameConfig {
/** 客户端版本号配置 */
get version(): string {
return this._data["config"]["version"];
}
/** 包名 */
get package(): string {
return this._data["config"]["package"];
}
/** 游戏每秒传输帧数 */
get frameRate(): number {
return this._data.config.frameRate;
return this.data.version;
}
/** 本地存储内容加密 key */
get localDataKey(): string {
return this._data.config.localDataKey;
return this.data.localDataKey;
}
/** 本地存储内容加密 iv */
get localDataIv(): string {
return this._data.config.localDataIv;
return this.data.localDataIv;
}
/** 游戏每秒传输帧数 */
get frameRate(): number {
return this.data.frameRate;
}
/** 是否开启移动设备安全区域适配 */
get mobileSafeArea(): boolean {
return this.data.mobileSafeArea || false;
}
/** 加载界面资源超时提示 */
get loadingTimeoutGui(): number {
return this.data.loadingTimeoutGui || 1000;
}
/** 是否显示统计信息 */
get stats(): number {
return this.data.stats;
}
/** Http 服务器地址 */
get httpServer(): string {
return this._data.config.httpServer;
return this.data.httpServer;
}
/** Http 请求超时时间 */
get httpTimeout(): number {
return this._data.config.httpTimeout;
return this.data.httpTimeout;
}
/** WebSocket 服务器地址 */
get webSocketServer(): string {
return this.data.webSocketServer;
}
/** WebSocket 心跳间隔时间(毫秒) */
get webSocketHeartTime(): number {
return this.data.webSocketHeartTime;
}
/** WebSocket 指定时间没收到消息就断开连接(毫秒) */
get webSocketReceiveTime(): number {
return this.data.webSocketReceiveTime;
}
/** WebSocket 重连间隔时间(毫秒) */
get webSocketReconnetTimeOut(): number {
return this.data.webSocketReconnetTimeOut;
}
/** 获取当前客户端支持的语言类型 */
@@ -55,42 +88,31 @@ export class GameConfig {
return this._data.language.default || "zh";
}
/** 是否启用远程资源 */
get bundleEnable(): string {
return this._data.bundle.enable;
}
/** 远程资源服务器地址 */
get bundleServer(): string {
return this._data.bundle.server;
}
/** 远程资源名 */
get bundleDefault(): string {
return this._data.bundle.default;
}
/** 远程所有资源包配置 */
get bundlePackages(): string {
return this._data.bundle.packages;
}
/** 加载界面资源超时提示 */
get loadingTimeoutGui(): number {
return this._data.config.loadingTimeoutGui || 1000;
}
/** 是否开启移动设备安全区域适配 */
get mobileSafeArea(): boolean {
return this._data.config.mobileSafeArea || false;
}
private readonly _data: any = null;
private _data: any = null;
/** 游戏配置数据 */
get data(): any {
return this._data;
return this._data.config[this._configType];
}
/** 当前游戏配置分组类型 */
private _configType: GameConfigCustomType = GameConfigCustomType.Prod;
constructor(config: any) {
this._data = Object.freeze(config.json);
this.setConfigType(this._data.type);
oops.log.logConfig(this._data, "游戏配置");
}
/**
* 设置游戏参数类型
* @param type
*/
setConfigType(type: GameConfigCustomType) {
this._configType = type;
}
}