1. CCEntity.addPrefab方法修改为返回节点

2. TimeUtili添加将秒数格式化为时间格式
3. 修复时间管理从后台恢复时计算错误问题
4. 修复DateExt时间格式化转化错误问题
5. 修复StorageSecuritySimple在真机上解码错误问题
6. 修复音效循环播放功能无效问题
7. 优化加载模块
8. 优化CCEntity.addUi错误提示信息
9. CommonPrompt对象修改为PromptBase,并优化代码,适合继承使用
This commit is contained in:
dgflash
2025-10-26 21:30:12 +08:00
parent b58a35a078
commit a8c3a1b7f4
45 changed files with 644 additions and 620 deletions

View File

@@ -22,7 +22,7 @@ import { GameManager } from "./game/GameManager";
import { LayerManager } from "./gui/layer/LayerManager";
/** 框架版本号 */
export var version: string = "2.0.0.20250514";
export var version: string = "2.1.0.20250921";
/** 框架核心模块访问入口 */
export class oops {

View File

@@ -12,7 +12,7 @@ import { AudioManager } from "./common/audio/AudioManager";
import { EventMessage } from "./common/event/EventMessage";
import { message } from "./common/event/MessageManager";
import { resLoader } from "./common/loader/ResLoader";
import { StorageManager } from "./common/storage/StorageManager";
import { IStorageSecurity, StorageManager } from "./common/storage/StorageManager";
import { StorageSecuritySimple } from "./common/storage/StorageSecuritySimple";
import { TimerManager } from "./common/timer/TimerManager";
import { GameManager } from "./game/GameManager";
@@ -52,7 +52,7 @@ export class Root extends Component {
// 创建持久根节点
this.persist = new Node("OopsFrameworkPersistNode");
director.addPersistRootNode(this.persist);
// oops.config.btc = new BuildTimeConstants();
// Web平台查询参数管理
oops.config.query = new GameQueryConfig();
// 资源管理模块
@@ -79,8 +79,10 @@ export class Root extends Component {
// 本地存储模块
oops.storage = new StorageManager();
oops.storage.init(new StorageSecuritySimple);
// oops.storage.init(new StorageSecurityCrypto);
let security: IStorageSecurity = new StorageSecuritySimple(); // new StorageSecurityCrypto();
security.key = oops.config.game.localDataKey;
security.iv = oops.config.game.localDataIv;
oops.storage.init(security);
// 创建音频模块
oops.audio = this.persist.addComponent(AudioManager);

View File

@@ -148,7 +148,7 @@ export class AudioEffectPool {
this.res_project.set(bundle, paths);
}
if (paths.indexOf(path) == -1) paths.push(path);
clip = await resLoader.loadAsync(bundle, path, AudioClip);
clip = await resLoader.load(bundle, path, AudioClip);
}
}
@@ -203,9 +203,13 @@ export class AudioEffectPool {
resLoader.release(ae.path, ae.params!.bundle);
}
}
ae.params && ae.params.onPlayComplete && ae.params.onPlayComplete(ae);
this.put(ae);
// console.log(`【音效】回收,池中剩余音效播放器【${this.pool.size()}】`);
// 循环播放的音效或自动释放音乐资源的音效,自动回收音乐播放器
if (!ae.params.loop || ae.params.destroy) {
ae.params && ae.params.onPlayComplete && ae.params.onPlayComplete(ae);
this.put(ae);
// console.log(`【音效】回收,池中剩余音效播放器【${this.pool.size()}】`);
}
}
/**

View File

@@ -5,8 +5,7 @@ import { AudioEffectPool } from "./AudioEffectPool";
import { AudioEffectType } from "./AudioEnum";
import { AudioMusic } from "./AudioMusic";
import { IAudioData, IAudioParams } from "./IAudio";
const LOCAL_STORE_KEY = "game_audio";
import { GameStorage } from "db://oops-framework/module/common/GameStorage";
/**
* 音频管理
@@ -66,7 +65,7 @@ export class AudioManager extends Component {
/** 保存音乐音效的音量、开关配置数据到本地 */
save() {
oops.storage.set(LOCAL_STORE_KEY, this.data);
oops.storage.set(GameStorage.Audio, this.data);
}
/** 本地加载音乐音效的音量、开关配置数据并设置到游戏中 */
@@ -74,7 +73,7 @@ export class AudioManager extends Component {
this.music = new AudioMusic();
this.music.parent = this.node;
this.data = oops.storage.getJson(LOCAL_STORE_KEY);
this.data = oops.storage.getJson(GameStorage.Audio);
if (this.data) {
this.setState();
}

View File

@@ -123,7 +123,7 @@ export class AudioMusic extends Node {
clip = await resLoader.loadRemote<AudioClip>(path, { ext: `.${extension}` });
}
else {
clip = await resLoader.loadAsync(params.bundle!, path, AudioClip);
clip = await resLoader.load(params.bundle!, path, AudioClip);
}
this._isLoading = false;

View File

@@ -1,4 +1,4 @@
import { __private, AnimationClip, Asset, AssetManager, assetManager, AudioClip, 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 } from "cc";
export type AssetType<T = Asset> = __private.__types_globals__Constructor<T> | null;
export type Paths = string | string[];
@@ -37,36 +37,37 @@ export class ResLoader {
defaultBundleName: string = "resources";
/** 下载时的最大并发数 - 项目设置 -> 项目数据 -> 资源下载并发数设置默认值初始值为15 */
get maxConcurrency() {
get maxConcurrency(): number {
return assetManager.downloader.maxConcurrency;
}
set maxConcurrency(value) {
set maxConcurrency(value: number) {
assetManager.downloader.maxConcurrency = value;
}
/** 下载时每帧可以启动的最大请求数 - 默认值为15 */
get maxRequestsPerFrame() {
get maxRequestsPerFrame(): number {
return assetManager.downloader.maxRequestsPerFrame;
}
set maxRequestsPerFrame(value) {
set maxRequestsPerFrame(value: number) {
assetManager.downloader.maxRequestsPerFrame = value;
}
/** 失败重试次数 - 默认值为0 */
get maxRetryCount() {
get maxRetryCount(): number {
return assetManager.downloader.maxRetryCount;
}
set maxRetryCount(value) {
set maxRetryCount(value: number) {
assetManager.downloader.maxRetryCount = value;
}
/** 重试的间隔时间,单位为毫秒 - 默认值为2000毫秒 */
get retryInterval() {
get retryInterval(): number {
return assetManager.downloader.retryInterval;
}
set retryInterval(value) {
set retryInterval(value: number) {
assetManager.downloader.retryInterval = value;
}
//#endregion
//#region 加载远程资源
/**
@@ -78,10 +79,10 @@ export class ResLoader {
var data = await oops.res.loadRemote<ImageAsset>(this.url, opt);
const texture = new Texture2D();
texture.image = data;
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
var sprite = this.sprite.addComponent(Sprite);
sprite.spriteFrame = spriteFrame;
*/
@@ -149,54 +150,40 @@ export class ResLoader {
* @param onProgress 加载进度回调
* @param onComplete 加载完成回调
*/
preload<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
preload<T extends Asset>(bundleName: string, paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
preload<T extends Asset>(bundleName: string, paths: Paths, onComplete?: CompleteCallback): void;
preload<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
preload<T extends Asset>(paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
preload<T extends Asset>(paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
preload<T extends Asset>(paths: Paths, onComplete?: CompleteCallback): void;
preload<T extends Asset>(paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
preload<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onProgress: ProgressCallback): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(bundleName: string, paths: Paths, onProgress: ProgressCallback): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(bundleName: string, paths: Paths): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(paths: Paths, type: AssetType<T>, onProgress: ProgressCallback): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(paths: Paths, onProgress: ProgressCallback): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(paths: Paths): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(paths: Paths, type: AssetType<T>): Promise<AssetManager.RequestItem>;
preload<T extends Asset>(
bundleName: string,
paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
type?: AssetType<T> | ProgressCallback | CompleteCallback,
onProgress?: ProgressCallback | CompleteCallback,
onComplete?: CompleteCallback,
paths?: Paths | AssetType<T> | ProgressCallback,
type?: AssetType<T> | ProgressCallback,
onProgress?: ProgressCallback
) {
let args: ILoadResArgs<Asset> | null = null;
if (typeof paths === "string" || paths instanceof Array) {
args = this.parseLoadResArgs(paths, type, onProgress, onComplete);
args.bundle = bundleName;
}
else {
args = this.parseLoadResArgs(bundleName, paths, type, onProgress);
args.bundle = this.defaultBundleName;
}
args.preload = true;
this.loadByArgs(args);
}
/**
* 异步加载一个资源
* @param bundleName 远程包名
* @param paths 资源路径
* @param type 资源类型
*/
preloadAsync<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>): Promise<AssetManager.RequestItem>;
preloadAsync<T extends Asset>(bundleName: string, paths: Paths): Promise<AssetManager.RequestItem>;
preloadAsync<T extends Asset>(paths: Paths, type: AssetType<T>): Promise<AssetManager.RequestItem>;
preloadAsync<T extends Asset>(paths: Paths): Promise<AssetManager.RequestItem>;
preloadAsync<T extends Asset>(bundleName: string,
paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
type?: AssetType<T> | ProgressCallback | CompleteCallback): Promise<AssetManager.RequestItem> {
return new Promise((resolve, reject) => {
this.preload(bundleName, paths, type, (err: Error | null, data: AssetManager.RequestItem) => {
let onComplete = (err: Error | null, data: AssetManager.RequestItem) => {
if (err) {
warn(err.message);
resolve(null!);
return;
}
resolve(data);
});
}
let args: ILoadResArgs<Asset> | null = null;
if (typeof paths === "string" || paths instanceof Array) {
args = this.parseLoadResArgs(paths, type, onProgress, onComplete);
args.bundle = bundleName;
}
else {
args = this.parseLoadResArgs(bundleName, paths, type, onComplete);
args.bundle = this.defaultBundleName;
}
args.preload = true;
this.loadByArgs(args);
});
}
@@ -244,61 +231,50 @@ export class ResLoader {
* @param bundleName 远程包名
* @param paths 资源路径
* @param type 资源类型
* @param onProgress 加载进度回调
* @param onComplete 加载完成回调
* @example
oops.res.load("spine_path", sp.SkeletonData, (err: Error | null, sd: sp.SkeletonData) => {
});
const sd = await oops.res.load("spine_path", sp.SkeletonData);
*/
load<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(bundleName: string, paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(bundleName: string, paths: Paths, onComplete?: CompleteCallback): void;
load<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
load<T extends Asset>(paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(paths: Paths, onComplete?: CompleteCallback): void;
load<T extends Asset>(paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
load<T extends Asset>(
bundleName: string,
paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
type?: AssetType<T> | ProgressCallback | CompleteCallback,
onProgress?: ProgressCallback | CompleteCallback,
onComplete?: CompleteCallback,
) {
let args: ILoadResArgs<T> | null = null;
if (typeof paths === "string" || paths instanceof Array) {
args = this.parseLoadResArgs(paths, type, onProgress, onComplete);
args.bundle = bundleName;
}
else {
args = this.parseLoadResArgs(bundleName, paths, type, onProgress);
args.bundle = this.defaultBundleName;
}
this.loadByArgs(args);
load<T extends Asset>(bundleName: string, paths: Paths | AssetType<T>, type?: AssetType<T>) {
return new Promise<T>((resolve, reject) => {
let onComplete = (err: Error | null, data: T) => {
if (err) {
resolve(null!);
return;
}
resolve(data);
}
let args: ILoadResArgs<T> | null = null;
if (typeof paths === "string" || paths instanceof Array) {
args = this.parseLoadResArgs(paths, type, onComplete);
args.bundle = bundleName;
}
else {
args = this.parseLoadResArgs(bundleName, paths, onComplete);
args.bundle = this.defaultBundleName;
}
this.loadByArgs(args);
});
}
/**
* 异步加载一个资源
* 加载指定资源包中的多个任意类型资源
* @param bundleName 远程包名
* @param paths 资源路径
* @param type 资源类型
* @param onProgress 加载进度回调
* @param onComplete 加载完成回调
*/
loadAsync<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>): Promise<T>;
loadAsync<T extends Asset>(bundleName: string, paths: Paths): Promise<T>;
loadAsync<T extends Asset>(paths: Paths, type: AssetType<T>): Promise<T>;
loadAsync<T extends Asset>(paths: Paths): Promise<T>;
loadAsync<T extends Asset>(bundleName: string,
paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
type?: AssetType<T> | ProgressCallback | CompleteCallback): Promise<T> {
return new Promise((resolve, reject) => {
this.load(bundleName, paths, type, (err: Error | null, asset: T) => {
if (err) {
warn(err.message);
}
resolve(asset);
});
});
loadAny<T extends Asset>(bundleName: string | string[], paths: string[] | ProgressCallback, onProgress?: ProgressCallback | CompleteCallback, onComplete?: CompleteCallback): void {
let args: ILoadResArgs<T> | null = null;
if (typeof bundleName === "string" && paths instanceof Array) {
args = this.parseLoadResArgs(paths, onProgress, onComplete);
args.bundle = bundleName;
}
else {
args = this.parseLoadResArgs(bundleName, paths, onProgress);
args.bundle = this.defaultBundleName;
}
this.loadByArgs(args);
}
/**
@@ -309,16 +285,16 @@ oops.res.load("spine_path", sp.SkeletonData, (err: Error | null, sd: sp.Skeleton
* @param onProgress 加载进度回调
* @param onComplete 加载完成回调
* @example
// 加载进度事件
var onProgressCallback = (finished: number, total: number, item: any) => {
console.log("资源加载进度", finished, total);
}
// 加载完成事件
var onCompleteCallback = () => {
console.log("资源加载完成");
}
oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
// 加载进度事件
var onProgressCallback = (finished: number, total: number, item: any) => {
console.log("资源加载进度", finished, total);
}
// 加载完成事件
var onCompleteCallback = () => {
console.log("资源加载完成");
}
oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
*/
loadDir<T extends Asset>(bundleName: string, dir: string, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
loadDir<T extends Asset>(bundleName: string, dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
@@ -360,7 +336,7 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
if (bundle) {
const asset = bundle.get(path);
if (asset) {
this.releasePrefabtDepsRecursively(bundleName, asset);
this.releasePrefabtDepsRecursively(asset);
}
}
}
@@ -370,13 +346,15 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
* @param path 资源文件夹路径
* @param bundleName 远程资源包名
*/
releaseDir(path: string, bundleName: string = this.defaultBundleName) {
releaseDir(path: string, bundleName?: string) {
if (bundleName == undefined) bundleName = this.defaultBundleName;
const bundle: AssetManager.Bundle | null = assetManager.getBundle(bundleName);
if (bundle) {
var infos = bundle.getDirWithPath(path);
if (infos) {
infos.map((info) => {
this.releasePrefabtDepsRecursively(bundleName, info.uuid);
this.releasePrefabtDepsRecursively(info.uuid);
});
}
@@ -400,64 +378,25 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
}
/** 释放预制依赖资源 */
private releasePrefabtDepsRecursively(bundleName: string, uuid: string | Asset) {
private releasePrefabtDepsRecursively(uuid: string | Asset) {
let asset: Asset | null | undefined;
if (uuid instanceof Asset) {
asset = uuid;
uuid.decRef();
// assetManager.releaseAsset(uuid);
// this.debugLogReleasedAsset(bundleName, uuid);
}
else {
const asset = assetManager.assets.get(uuid);
if (asset) {
asset.decRef();
// assetManager.releaseAsset(asset);
// this.debugLogReleasedAsset(bundleName, asset);
}
asset = assetManager.assets.get(uuid);
if (asset) asset.decRef();
}
}
private debugLogReleasedAsset(bundleName: string, asset: Asset) {
if (asset.refCount == 0) {
let path = this.getAssetPath(bundleName, asset.uuid);
let content: string = "";
if (asset instanceof JsonAsset) {
content = "【释放资源】Json【路径】" + path;
}
else if (asset instanceof Prefab) {
content = "【释放资源】Prefab【路径】" + path;
}
else if (asset instanceof SpriteFrame) {
content = "【释放资源】SpriteFrame【路径】" + path;
}
else if (asset instanceof Texture2D) {
content = "【释放资源】Texture2D【路径】" + path;
}
else if (asset instanceof ImageAsset) {
content = "【释放资源】ImageAsset【路径】" + path;
}
else if (asset instanceof AudioClip) {
content = "【释放资源】AudioClip【路径】" + path;
}
else if (asset instanceof AnimationClip) {
content = "【释放资源】AnimationClip【路径】" + path;
}
else if (asset instanceof Font) {
content = "【释放资源】Font【路径】" + path;
}
else if (asset instanceof Material) {
content = "【释放资源】Material【路径】" + path;
}
else if (asset instanceof Mesh) {
content = "【释放资源】Mesh【路径】" + path;
}
else if (asset instanceof sp.SkeletonData) {
content = "【释放资源】Spine【路径】" + path;
}
else {
content = "【释放资源】未知【路径】" + path;
}
console.log(content);
}
// 释放预制引用资源
// if (asset instanceof Prefab) {
// const uuids: string[] = assetManager.dependUtil.getDepsRecursively(asset.uuid)!;
// uuids.forEach(uuid => {
// const asset = assetManager.assets.get(uuid);
// if (asset) asset.decRef();
// });
// }
}
/**
@@ -472,12 +411,7 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
}
//#endregion
private parseLoadResArgs<T extends Asset>(
paths: Paths,
type?: AssetType<T> | ProgressCallback | CompleteCallback,
onProgress?: AssetType<T> | ProgressCallback | CompleteCallback,
onComplete?: ProgressCallback | CompleteCallback
) {
private parseLoadResArgs<T extends Asset>(paths: Paths, type?: AssetType<T> | ProgressCallback | CompleteCallback, onProgress?: AssetType<T> | ProgressCallback | CompleteCallback, onComplete?: ProgressCallback | CompleteCallback) {
let pathsOut: any = paths;
let typeOut: any = type;
let onProgressOut: any = onProgress;
@@ -525,15 +459,12 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
private async loadByArgs<T extends Asset>(args: ILoadResArgs<T>) {
if (args.bundle) {
let bundle = assetManager.bundles.get(args.bundle);
// 获取缓存中的资源包
if (bundle) {
this.loadByBundleAndArgs(bundle, args);
}
// 自动加载资源包
else {
bundle = await this.loadBundle(args.bundle);
if (bundle) this.loadByBundleAndArgs(bundle, args);
}
if (bundle == null) bundle = await this.loadBundle(args.bundle);
// 加载指定资源包中的资源
this.loadByBundleAndArgs(bundle, args);
}
// 默认资源包
else {
@@ -548,6 +479,50 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback);
})
console.log(`当前资源总数:${assetManager.assets.count}`);
}
private debugLogReleasedAsset(bundleName: string, asset: Asset) {
if (asset.refCount == 0) {
let path = this.getAssetPath(bundleName, asset.uuid);
let content: string = "";
if (asset instanceof JsonAsset) {
content = "【释放资源】Json【路径】" + path;
}
else if (asset instanceof Prefab) {
content = "【释放资源】Prefab【路径】" + path;
}
else if (asset instanceof SpriteFrame) {
content = "【释放资源】SpriteFrame【路径】" + path;
}
else if (asset instanceof Texture2D) {
content = "【释放资源】Texture2D【路径】" + path;
}
else if (asset instanceof ImageAsset) {
content = "【释放资源】ImageAsset【路径】" + path;
}
else if (asset instanceof AudioClip) {
content = "【释放资源】AudioClip【路径】" + path;
}
else if (asset instanceof AnimationClip) {
content = "【释放资源】AnimationClip【路径】" + path;
}
else if (asset instanceof Font) {
content = "【释放资源】Font【路径】" + path;
}
else if (asset instanceof Material) {
content = "【释放资源】Material【路径】" + path;
}
else if (asset instanceof Mesh) {
content = "【释放资源】Mesh【路径】" + path;
}
else if (asset instanceof sp.SkeletonData) {
content = "【释放资源】Spine【路径】" + path;
}
else {
content = "【释放资源】未知【路径】" + path;
}
console.log(content);
}
}
}
export const resLoader = new ResLoader();

View File

@@ -17,14 +17,11 @@ export class ZipLoader {
* @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);
})
return new Promise(async (resolve, reject) => {
let asset = await resLoader.load(url, BufferAsset);
var zip = await JSZip.loadAsync(asset.buffer());
this.zips.set(url, zip);
resolve(zip);
});
}

View File

@@ -2,6 +2,9 @@ import { sys } from "cc";
import { PREVIEW } from "cc/env";
export interface IStorageSecurity {
key: string;
iv: string;
init(): void;
decrypt(str: string): string;
encrypt(str: string): string;
encryptKey(str: string): string;
@@ -23,6 +26,7 @@ export class StorageManager {
/** 本地存储数据加密方式初始化 */
init(iis: IStorageSecurity) {
this.iss = iis;
this.iss.init();
}
/**

View File

@@ -1,4 +1,3 @@
// import { oops } from "../../Oops";
// import { EncryptUtil } from "../../utils/EncryptUtil";
// import { IStorageSecurity } from "./StorageManager";
@@ -12,25 +11,21 @@
// * 2、需要下载 CryptoES 加密库
// */
// export class StorageSecurityCrypto implements IStorageSecurity {
// private _key: string = null!;
// private _iv: string = null!;
// key: string = null!;
// iv: string = null!;
// constructor() {
// const key = oops.config.game.localDataKey;
// const iv = oops.config.game.localDataIv;
// EncryptUtil.initCrypto(key, iv);
// this._key = EncryptUtil.md5(key);
// this._iv = EncryptUtil.md5(iv);
// init() {
// this.key = EncryptUtil.md5(this.key);
// this.iv = EncryptUtil.md5(this.iv);
// EncryptUtil.initCrypto(this.key, this.iv);
// }
// decrypt(str: string): string {
// return EncryptUtil.aesDecrypt(str, this._key, this._iv);
// return EncryptUtil.aesDecrypt(str);
// }
// encrypt(str: string): string {
// return EncryptUtil.aesEncrypt(str, this._key, this._iv);
// return EncryptUtil.aesEncrypt(str);
// }
// encryptKey(str: string): string {

View File

@@ -1,4 +1,3 @@
import { oops } from '../../Oops';
import { IStorageSecurity } from './StorageManager';
/**
@@ -14,42 +13,34 @@ import { IStorageSecurity } from './StorageManager';
* 1、加密强度小
*/
export class StorageSecuritySimple implements IStorageSecurity {
key: string = null!;
iv: string = null!;
private secretkey: string = null!;
constructor() {
const key = oops.config.game.localDataKey;
const iv = oops.config.game.localDataIv;
this.secretkey = key + iv;
init() {
this.secretkey = this.key + this.iv;
}
/**
* 加密字符串
*/
encrypt(data: string): string {
if (!data) return '';
return this.xorEncrypt(data);
let encryptedText = '';
for (let i = 0; i < data.length; i++) {
let charCode = data.charCodeAt(i);
encryptedText += String.fromCharCode(charCode + this.secretkey.length);
}
return encryptedText;
}
/** 解密字符串 */
decrypt(encryptedData: string): string {
if (!encryptedData) return '';
return this.xorDecrypt(encryptedData);
}
/** 异或加密 */
private xorEncrypt(data: string): string {
let result = '';
for (let i = 0; i < data.length; i++) {
const keyChar = this.secretkey.charCodeAt(i % this.secretkey.length);
const dataChar = data.charCodeAt(i);
result += String.fromCharCode(dataChar ^ keyChar);
let decryptedText = '';
for (let i = 0; i < encryptedData.length; i++) {
let charCode = encryptedData.charCodeAt(i);
decryptedText += String.fromCharCode(charCode - this.secretkey.length);
}
return result;
}
/** 异或解密 */
private xorDecrypt(encryptedData: string): string {
return this.xorEncrypt(encryptedData); // 异或操作是可逆的
return decryptedText;
}
encryptKey(str: string): string {

View File

@@ -194,7 +194,7 @@ export class TimerManager extends Component {
}
}
/** 游戏最大化时复时间数据 */
/** 游戏最大化时复时间数据 */
load(): void {
for (let key in this.times) {
let data = this.times[key];
@@ -204,9 +204,6 @@ export class TimerManager extends Component {
data.object[data.field] = 0;
this.onTimerComplete(data);
}
else {
data.object[data.field] = 0;
}
}
}
}

View File

@@ -5,9 +5,9 @@
* @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';
import { resLoader } from '../common/loader/ResLoader';
import { ViewUtil } from '../utils/ViewUtil';
/** 游戏元素打开参数 */
export interface ElementParams {
@@ -41,10 +41,11 @@ export class GameManager {
else {
bundleName = resLoader.defaultBundleName;
}
let node: Node = null!;
// 自动内存管理
if (parent instanceof GameComponent) {
node = await parent.createPrefabNodeAsync(prefabPath, bundleName);
node = await parent.createPrefabNode(prefabPath, bundleName);
node.parent = parent.node;
}
// 手动内存管理

View File

@@ -4,7 +4,7 @@
* @LastEditors: dgflash
* @LastEditTime: 2025-08-15 10:06:47
*/
import { Node, NodePool, Prefab, Vec3, warn } from "cc";
import { Node, NodePool, Vec3, warn } from "cc";
import { resLoader } from "../../common/loader/ResLoader";
import { ViewUtil } from "../../utils/ViewUtil";
import { LayerCustomType } from "./LayerEnum";
@@ -22,74 +22,50 @@ export class LayerGame extends Node {
LayerHelper.setFullScreen(this);
}
/**
* 添加游戏元素
* @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> {
add(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);
let params = this.setParams(prefab, config, false);
let node = await ViewUtil.createPrefabNodeAsync(prefab, params.config.bundle);
if (node) {
// 设置自定义属性
this.setNode(node, config);
let lge = node.addComponent(LayerGameElement);
lge.params = params;
params.nodes.push(node);
}
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> {
addPool(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);
let params = this.setParams(prefab, config, true);
let node: Node = null!;
if (params.pool.size() > 0) {
node = params.pool.get()!;
}
else {
node = await ViewUtil.createPrefabNodeAsync(prefab, params.config.bundle);
node.addComponent(LayerGameElement);
}
// 设置自定义属性
this.setNode(node, config);
let lge = node.getComponent(LayerGameElement)!;
lge.params = params;
resolve(node);
});
}
@@ -123,7 +99,7 @@ export class LayerGame extends Node {
resLoader.release(lge.params.config.prefab!, lge.params.config.bundle);
}
}
node.removeFromParent();
node.destroy();
}
}
else {
@@ -149,6 +125,7 @@ export class LayerGame extends Node {
}
this.elements.set(uuid, params);
}
params.config.bundle = bundleName;
return params;
}

View File

@@ -92,7 +92,7 @@ export class LayerUI extends Node {
let timerId = setTimeout(this.onLoadingTimeoutGui, oops.config.game.loadingTimeoutGui);
// 优先加载配置的指定资源包中资源,如果没配置则加载默认资源包资源
const res = await resLoader.loadAsync(state.config.bundle!, state.config.prefab, Prefab);
const res = await resLoader.load(state.config.bundle!, state.config.prefab, Prefab);
if (res) {
state.node = instantiate(res);

View File

@@ -1,99 +0,0 @@
import { Component, _decorator } from "cc";
import { LanguageLabel } from "../../../libs/gui/language/LanguageLabel";
import { oops } from "../../Oops";
const { ccclass, property } = _decorator;
/** 公共提示窗口 */
@ccclass("CommonPrompt")
export class CommonPrompt extends Component {
/** 窗口标题多语言组件 */
@property(LanguageLabel)
private lab_title: LanguageLabel | null = null;
/** 提示内容多语言组件 */
@property(LanguageLabel)
private lab_content: LanguageLabel | null = null;
/** 确认按钮文本多语言组件 */
@property(LanguageLabel)
private lab_ok: LanguageLabel | null = null
/** 取消按钮文本多语言组件 */
@property(LanguageLabel)
private lab_cancel: LanguageLabel | null = null;
private config: any = {};
/**
*
*
* @param params 参数
* {
* title: 标题
* content: 内容
* okWord: ok按钮上的文字
* okFunc: 确认时执行的方法
* cancelWord: 取消按钮的文字
* cancelFunc: 取消时执行的方法
* needCancel: 是否需要取消按钮
* }
*/
onAdded(params: any): boolean {
this.config = params || {};
this.setTitle();
this.setContent();
this.setBtnOkLabel();
this.setBtnCancelLabel();
this.node.active = true;
return true;
}
private setTitle() {
this.lab_title!.dataID = this.config.title;
}
private setContent() {
this.lab_content!.dataID = this.config.content;
}
private setBtnOkLabel() {
this.lab_ok!.dataID = this.config.okWord;
}
private setBtnCancelLabel() {
if (this.lab_cancel) {
this.lab_cancel.dataID = this.config.cancelWord;
this.lab_cancel.node.parent!.active = this.config.needCancel || false;
}
}
private onOk() {
if (typeof this.config.okFunc == "function") {
this.config.okFunc();
}
this.close();
}
private onClose() {
if (typeof this.config.closeFunc == "function") {
this.config.closeFunc();
}
this.close();
}
private onCancel() {
if (typeof this.config.cancelFunc == "function") {
this.config.cancelFunc();
}
this.close();
}
private close() {
oops.gui.removeByNode(this.node);
}
onDestroy() {
this.config = null;
}
}

View File

@@ -1,13 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "653bff15-3c2e-459f-8f73-14916a5d5831",
"files": [],
"subMetas": {},
"userData": {
"moduleId": "project:///assets/script/core/gui/prompt/CommonPrompt.js",
"importerSettings": 4,
"simulateGlobals": []
}
}

View File

@@ -1,11 +1,4 @@
// /*
// * @Author: dgflash
// * @Date: 2022-09-02 09:28:00
// * @LastEditors: dgflash
// * @LastEditTime: 2022-10-21 09:46:39
// */
// import CryptoES from "crypto-es";
// import { AES, MD5, Utf8, WordArray } from 'crypto-es';
// /**
// * CryptoES 加密库封装
@@ -16,81 +9,47 @@
// * yarn add crypto-es
// */
// export class EncryptUtil {
// private static key: string = null!;
// private static iv: CryptoES.lib.WordArray = null!;
// // 将key和iv存储为WordArray类型这是CryptoES库需要的格式
// private static key: WordArray;
// private static iv: WordArray;
// /**
// * MD5加密
// * @param msg 加密信息
// */
// static md5(msg: string): string {
// return CryptoES.MD5(msg).toString();
// return MD5(msg).toString();
// }
// /** 初始化加密库 */
// static initCrypto(key: string, iv: string) {
// this.key = key;
// this.iv = CryptoES.enc.Hex.parse(iv);
// this.key = Utf8.parse(key);
// this.iv = Utf8.parse(iv);
// }
// /**
// * AES 加密
// * @param msg 加密信息
// * @param key aes加密的key
// * @param iv aes加密的iv
// * @param msg 加密的明文
// */
// static aesEncrypt(msg: string, key: string, iv: string): string {
// return CryptoES.AES.encrypt(
// msg,
// this.key,
// {
// iv: this.iv,
// format: this.JsonFormatter
// },
// ).toString();
// static aesEncrypt(msg: string): string {
// const encrypted = AES.encrypt(msg, this.key, {
// iv: this.iv
// });
// // 返回Base64格式的密文字符串
// return encrypted.toString();
// }
// /**
// * AES 解密
// * @param str 解密字符串
// * @param key aes加密的key
// * @param iv aes加密的iv
// * @param cipherText 待解密的密文
// */
// static aesDecrypt(str: string, key: string, iv: string): string {
// const decrypted = CryptoES.AES.decrypt(
// str,
// this.key,
// {
// iv: this.iv,
// format: this.JsonFormatter
// },
// );
// return decrypted.toString(CryptoES.enc.Utf8);
// }
// static aesDecrypt(cipherText: string): string {
// const decrypted = AES.decrypt(cipherText, this.key, {
// iv: this.iv
// });
// private static JsonFormatter = {
// stringify: function (cipherParams: any) {
// const jsonObj: any = { ct: cipherParams.ciphertext.toString(CryptoES.enc.Base64) };
// if (cipherParams.iv) {
// jsonObj.iv = cipherParams.iv.toString();
// }
// if (cipherParams.salt) {
// jsonObj.s = cipherParams.salt.toString();
// }
// return JSON.stringify(jsonObj);
// },
// parse: function (jsonStr: any) {
// const jsonObj = JSON.parse(jsonStr);
// const cipherParams = CryptoES.lib.CipherParams.create(
// { ciphertext: CryptoES.enc.Base64.parse(jsonObj.ct) },
// );
// if (jsonObj.iv) {
// cipherParams.iv = CryptoES.enc.Hex.parse(jsonObj.iv)
// }
// if (jsonObj.s) {
// cipherParams.salt = CryptoES.enc.Hex.parse(jsonObj.s)
// }
// return cipherParams;
// },
// };
// // 将解密结果从WordArray转换为UTF-8字符串
// return decrypted.toString(Utf8);
// }
// }

View File

@@ -46,7 +46,7 @@ export class JsonUtil {
content = await ZipLoader.getJson(pathZip, `${name}.json`);
}
else {
content = await resLoader.loadAsync(url, JsonAsset);
content = await resLoader.load(url, JsonAsset);
}
if (content) {
@@ -66,13 +66,14 @@ export class JsonUtil {
* @param isZip 是否为压缩包
* @param zipNames 压缩包内的资源名列表
*/
static loadDir(zipNames?: string[]): Promise<void> {
static loadDir(): Promise<void> {
return new Promise(async (resolve, reject) => {
if (this.zip && zipNames) {
await ZipLoader.load(pathZip);
zipNames.forEach(name => {
if (this.zip) {
let zip = await ZipLoader.load(pathZip);
for (let key in zip.files) {
let name = key.replace(".json", "");
data.set(name, ZipLoader.getJson(pathZip, `${name}.json`));
});
}
ZipLoader.release(pathZip);
resolve();
}

View File

@@ -35,4 +35,32 @@ export class TimeUtil {
}, ms)
});
}
/** 格式化字符串 */
static format(countDown: number) {
let result: string = "";
let c: number = countDown;
let date: number = Math.floor(c / 86400);
c = c - date * 86400;
let hours: number = Math.floor(c / 3600);
c = c - hours * 3600;
let minutes: number = Math.floor(c / 60);
c = c - minutes * 60;
let seconds: number = c;
if (date == 0 && hours == 0 && minutes == 0 && seconds == 0) {
result = "00:00:00";
}
else {
hours += date * 24;
result = `${this.coverString(hours)}:${this.coverString(minutes)}:${this.coverString(seconds)}`;
}
return result;
}
/** 个位数的时间数据将字符串补位 */
private static coverString(value: number) {
if (value < 10) return "0" + value;
return value.toString();
}
}

View File

@@ -107,7 +107,7 @@ export class ViewUtil {
*/
static createPrefabNodeAsync(path: string, bundleName: string = resLoader.defaultBundleName): Promise<Node> {
return new Promise(async (resolve, reject) => {
const p = await resLoader.loadAsync(bundleName, path, Prefab);
const p = await resLoader.load(bundleName, path, Prefab);
if (p) {
resolve(instantiate(p));
}

View File

@@ -5,7 +5,7 @@
* @LastEditTime: 2022-04-08 15:42:16
*/
import { Component, sp, _decorator } from 'cc';
import { _decorator, Component, sp } from 'cc';
import { oops } from '../../../core/Oops';
const { ccclass, property } = _decorator;
@@ -28,21 +28,21 @@ export class SpineFinishedRelease extends Component {
this.spine.setCompleteListener(this.onSpineComplete.bind(this));
if (this.resPath) {
oops.res.load(this.resPath, sp.SkeletonData, (err: Error | null, sd: sp.SkeletonData) => {
if (err) {
console.error(`加载【${this.resPath}】的 SPINE 资源不存在`);
return;
}
this.spine.skeletonData = sd;
this.spine.setAnimation(0, "animation", false);
});
this.loadSkeletonData();
}
else {
this.spine.setAnimation(0, "animation", false);
}
}
private async loadSkeletonData() {
let sd = await oops.res.load(this.resPath, sp.SkeletonData);
if (sd) {
this.spine.skeletonData = sd;
this.spine.setAnimation(0, "animation", false);
}
}
private onSpineComplete() {
if (this.isDestroy) {
this.node.destroy();

View File

@@ -87,7 +87,7 @@ export class EffectSingleCase {
}
this.res.set(path, bundleName);
await resLoader.loadAsync(bundleName, path, Prefab);
await resLoader.load(bundleName, path, Prefab);
for (let i = 0; i < count; i++) {
let node = ViewUtil.createPrefabNode(path, bundleName);
@@ -111,11 +111,11 @@ export class EffectSingleCase {
if (np == undefined) {
if (params && params.bundleName) {
this.res.set(path, params.bundleName);
await resLoader.loadAsync(params.bundleName, path, Prefab);
await resLoader.load(params.bundleName, path, Prefab);
}
else {
this.res.set(path, resLoader.defaultBundleName);
await resLoader.loadAsync(path, Prefab);
await resLoader.load(path, Prefab);
}
const node = this.show(path, parent, params);

View File

@@ -32,7 +32,7 @@ Date.prototype.format = function (format: string): string {
let r = format
.replace('yy', year.toString())
.replace('mm', (month < 10 ? '0' : '') + month)
.replace('MM', (month < 10 ? '0' : '') + month)
.replace('dd', (day < 10 ? '0' : '') + day)
.replace('hh', (hours < 10 ? '0' : '') + hours)
.replace('mm', (minutes < 10 ? '0' : '') + minutes)

View File

@@ -73,7 +73,7 @@ export class LanguagePack {
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);
const jsonAsset = await resLoader.load(path, JsonAsset);
if (jsonAsset) {
LanguageData.language.set(LanguageDataType.Json, jsonAsset.json);
Logger.instance.logConfig(path, "下载语言包 json 资源");
@@ -83,11 +83,12 @@ export class LanguagePack {
return;
}
resLoader.load(path, TTFFont, (err: Error | null, font: TTFFont) => {
if (err == null) Logger.instance.logConfig(path, "下载语言包 ttf 资源");
const font = await resLoader.load(path, TTFFont);
if (font) {
LanguageData.font = font;
resolve();
});
Logger.instance.logConfig(path, "下载语言包 ttf 资源");
}
resolve();
});
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "26bbcb1a-b407-4d25-b9e9-8676f614312a",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,89 @@
import { _decorator } from "cc";
import { GameComponent } from "db://oops-framework/module/common/GameComponent";
import { LanguageLabel } from "../language/LanguageLabel";
const { ccclass, property } = _decorator;
/**
* 基础提示窗口
* 1. 自定义提示标题、按钮名
* 2. 自定义确认、取消事件回调
* 3. 自定义提示内容
* 4. 支持文本多语言
*/
@ccclass("PromptBase")
export class PromptBase extends GameComponent {
/** 窗口标题多语言组件 */
@property(LanguageLabel)
private labTitle: LanguageLabel = null!;
/** 提示内容多语言组件 */
@property(LanguageLabel)
private labContent: LanguageLabel = null!;
/** 确认按钮文本多语言组件 */
@property(LanguageLabel)
private labOk: LanguageLabel = null!;
/** 取消按钮文本多语言组件 */
@property(LanguageLabel)
private labCancel: LanguageLabel = null!;
/** 窗口配置 */
protected config: any = null!;
/**
* 窗口打开事件
* @param params 参数
* {
* title: 标题
* content: 内容
* okWord: ok按钮上的文字
* okFunc: 确认时执行的方法
* cancelWord: 取消按钮的文字
* cancelFunc: 取消时执行的方法
* needCancel: 是否需要取消按钮
* }
*/
onAdded(params: any): boolean {
this.config = params;
if (this.config == null) return false;
this.labTitle.dataID = this.config.title; // 窗口标题
this.labContent.dataID = this.config.content; // 提示内容
this.labOk.dataID = this.config.okWord; // 确定按钮文字
if (this.labCancel) {
this.labCancel.dataID = this.config.cancelWord || ""; // 取消按钮文字
this.labCancel.node.parent!.active = this.config.needCancel || false;
}
this.node.active = true;
return true;
}
protected onLoad(): void {
this.setButton();
}
private btnOk() {
if (typeof this.config.onOk == "function") {
this.config.onOk();
}
this.remove();
}
private btnCancel() {
if (typeof this.config.onCancel == "function") {
this.config.onCancel();
}
this.remove();
}
private btnClose() {
this.remove();
}
onDestroy() {
this.config = null!;
super.onDestroy();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "62d1c492-8247-4e77-8216-462f64c6a296",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,47 @@
import { _decorator, Toggle } from "cc";
import { oops } from "db://oops-framework/core/Oops";
import { GameStorage } from "db://oops-framework/module/common/GameStorage";
import { PromptBase } from "./PromptBase";
const { ccclass, property } = _decorator;
/** 不同类型的提示窗口状态数据 */
var content: any = null;
/** 可设置指定时间内跳过提示 */
@ccclass("PromptSkip")
export class PromptSkip extends PromptBase {
/** 是否可提示 */
static isPrompt(id: string): boolean {
if (content == null) content = oops.storage.getJson(GameStorage.PromptSkip, {}); // 第一次打开窗口从本地数据中获取窗口状态信息
let r = content[id];
let c = oops.timer.getClientTime();
if (r == null || c > r) {
return true;
}
return false;
}
protected start(): void {
// 界面打开,删除昨天调协的不提示时间
if (content[this.config.id]) {
delete content[this.config.id];
oops.storage.set(GameStorage.PromptSkip, JSON.stringify(content));
}
}
/** 设置是否今天日内不提示 */
private onSetSkip(toggle: Toggle) {
if (toggle.isChecked) {
const t = oops.timer.getClientDate();
t.setDate(t.getDate() + this.config.skipDay);
t.setHours(0, 0, 0, 0);
content[this.config.id] = t.getTime();
}
else {
content[this.config.id] = null;
}
oops.storage.set(GameStorage.PromptSkip, JSON.stringify(content));
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ae0c193b-a70c-474f-8e66-eeb20024039e",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -30,7 +30,7 @@ export class JsonOb<T> {
private _callback;
/**对象属性劫持 */
/** 对象属性劫持 */
private observe<T>(obj: T, path?: any) {
if (OP.toString.call(obj) === types.array) {
this.overrideArrayProto(obj, path);
@@ -55,7 +55,7 @@ export class JsonOb<T> {
set: function (newVal) {
//cc.log(newVal);
if (oldVal !== newVal) {
if (OP.toString.call(newVal) === '[object Object]') {
if (OP.toString.call(newVal) === types.obj) {
self.observe(newVal, pathArray);
}
@@ -67,11 +67,11 @@ export class JsonOb<T> {
})
// @ts-ignore
if (OP.toString.call(obj[key]) === types.obj || OP.toString.call(obj[key]) === types.array) {
// @ts-ignore
this.observe(obj[key], pathArray);
const o = obj[key];
if (OP.toString.call(o) === types.obj || OP.toString.call(o) === types.array) {
this.observe(o, pathArray);
}
}, this)
}, this);
}
/**
@@ -92,9 +92,9 @@ export class JsonOb<T> {
Object.defineProperty(overrideProto, method, {
value: function () {
var oldVal = this.slice();
//调用原始原型上的方法
// 调用原始原型上的方法
result = originalProto[method].apply(this, arguments);
//继续监听新数组
// 继续监听新数组
self.observe(this, path);
self._callback(this, oldVal, path);
return result;

View File

@@ -2,15 +2,14 @@ import { LanguageData } from "../gui/language/LanguageData";
/**
* 数值格式化函数, 通过语义解析自动设置值的范围
* //整数
* 1:def(0)//显示一个默认值
* 1:def(0) // 显示一个默认值
*/
class StringFormat {
deal(value: number | string, format: string): string {
if (format === '') return value as string;
format = format.toLowerCase().trim(); // 不区分大小
let match_func = format.match(/^[a-z|A-Z]+/gi); // 匹配到 format 中的 函数名
let match_func = format.match(/^[a-z|A-Z]+/gi); // 匹配到 format 中的函数名
let match_num = format.match(/\d+$/gi); // 匹配到 format 中的参数
let func: string = '';
let num: number = 0;

View File

@@ -1,4 +1,4 @@
import { Component, log, _decorator } from 'cc';
import { _decorator, Component, log } from 'cc';
import { DEBUG } from 'cc/env';
import { VM } from './ViewModel';
import { VMEnv } from './VMEnv';

View File

@@ -1,4 +1,4 @@
import { _decorator, CCString, error } from 'cc';
import { _decorator, CCString, error, Node } from 'cc';
import { StringFormatFunction } from './StringFormat';
import { VMBase } from './VMBase';
import { VMEnv } from './VMEnv';
@@ -22,6 +22,22 @@ const LABEL_TYPE = {
@menu('OopsFramework/Mvvm/VM-Label (标签)')
@help('https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12037641&doc_id=2873565')
export default class VMLabel extends VMBase {
/**
* 动态绑定组件
* @param node 目标节点
* @param watchPath 监听数据路径
*/
static bind(node: Node, watchPath: string | string[]) {
let label = node.addComponent(VMLabel);
if (watchPath instanceof Array) {
label.templateMode = true;
label.watchPathArr = watchPath;
}
else {
label.watchPath = watchPath;
}
}
@property({
tooltip: '是否启用模板代码,只能在运行时之前设置,\n将会动态解析模板语法 {{0}},并且自动设置监听的路径'
})

View File

@@ -1,15 +1,16 @@
import { Component, _decorator } from 'cc';
import { GameComponent } from '../../module/common/GameComponent';
import { VM } from './ViewModel';
import { VMBase } from './VMBase';
const { ccclass, help, executionOrder } = _decorator;
/**
* 提供VM环境控制旗下所有VM节点
* 一般用于 非全局的 VM绑定,VM 环境与 组件紧密相连
* 一般用于 非全局的 VM绑定,VM 环境与组件紧密相连
* Prefab 模式绑定)
* VMParent 必须必其他组件优先执行
* v0.1 修复bug 现在可以支持 Parent 嵌套 (但是注意性能问题,不要频繁嵌套)
* 现在可以支持 Parent 嵌套(但是注意性能问题,不要频繁嵌套)
*/
@ccclass
@executionOrder(-1)
@@ -21,9 +22,6 @@ export default class VMParent extends GameComponent {
/** 需要绑定的私有数据 */
protected data: any = {};
/**VM 管理 */
public VM = VM;
/**
* [注意]不能直接覆盖此方法,如果需要覆盖。
* 只能在该方法内部调用父类的实现
@@ -32,7 +30,6 @@ export default class VMParent extends GameComponent {
* super.onLoad();
* }
* ```
*
*/
onLoad() {
if (this.data == null) return;
@@ -46,20 +43,16 @@ export default class VMParent extends GameComponent {
// console.group();
for (let i = 0; i < comps.length; i++) {
const comp = comps[i];
this.replaceVMPath(comp, this.tag)
this.replaceVMPath(comp, this.tag);
}
// console.groupEnd()
}
/**在 onLoad 完成 和 start() 之前调用,你可以在这里进行初始化数据等操作 */
protected onBind() {
}
protected onBind() { }
/**在 onDestroy() 后调用,此时仍然可以获取绑定的 data 数据*/
protected onUnBind() {
}
protected onUnBind() { }
private replaceVMPath(comp: Component, tag: string) {
// @ts-ignore
@@ -88,13 +81,13 @@ export default class VMParent extends GameComponent {
/** 未优化的遍历节点获取VM 组件 */
private getVMComponents() {
let comps = this.node.getComponentsInChildren('VMBase');
let parents = this.node.getComponentsInChildren('VMParent').filter(v => v.uuid !== this.uuid); // 过滤掉自己
let comps = this.node.getComponentsInChildren(VMBase);
let parents = this.node.getComponentsInChildren(VMParent).filter(v => v.uuid !== this.uuid); // 过滤掉自己
//过滤掉不能赋值的parent
let filters: any[] = [];
parents.forEach((node: Component) => {
filters = filters.concat(node.getComponentsInChildren('VMBase'));
filters = filters.concat(node.getComponentsInChildren(VMBase));
})
comps = comps.filter((v) => filters.indexOf(v) < 0);

View File

@@ -27,7 +27,7 @@ export default class VMProgress extends VMCustom {
})
protected watchPathArr: string[] = ['[min]', '[max]'];
public templateMode: boolean = true;
templateMode: boolean = true;
@property({
visible: function () {

View File

@@ -61,12 +61,12 @@ class ViewModel<T> {
if (this.emitToRootPath) director.emit(VM_EMIT_HEAD + this._tag, n, o, path); // 通知主路径
if (path.length >= 2) {
for (let i = 0; i < path.length - 1; i++) {
const e = path[i];
//log('中端路径');
}
}
// if (path.length >= 2) {
// for (let i = 0; i < path.length - 1; i++) {
// const e = path[i];
// log('中端路径', e);
// }
// }
}
}

View File

@@ -10,6 +10,7 @@ import { CompType } from "../../libs/ecs/ECSModel";
import { CCBusiness } from "./CCBusiness";
import { CCView } from "./CCView";
import { CCViewVM } from "./CCViewVM";
import { GameComponent } from "./GameComponent";
export type ECSCtor<T extends ecs.Comp> = __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>;
export type ECSView = CCViewVM<CCEntity> | CCView<CCEntity>;
@@ -20,21 +21,39 @@ export abstract class CCEntity extends ecs.Entity {
/** 单例子实体 */
private singletons: Map<any, ECSEntity> = null!;
/** 添加单例子实体 */
addChildSingleton<T>(cls: any): T {
/**
* 批量添加单例子实体
* @param clss 单例子实体类数组
*/
addChildSingletons<T extends CCEntity>(...clss: any[]) {
for (let ctor of clss) {
this.addChildSingleton<T>(ctor);
}
}
/**
* 添加单例子实体
* @param cls 单例子实体类
* @returns 单例子实体
*/
addChildSingleton<T extends CCEntity>(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();
let entity = ecs.getEntity<T>(cls);
this.singletons.set(cls, entity);
this.addChild(entity);
return entity as T;
}
/** 获取单例子实体 */
getChildSingleton<T>(cls: any): T {
/**
* 获取单例子实体
* @param cls 单例子实体类
* @returns 单例子实体
*/
getChildSingleton<T extends CCEntity>(cls: any): T {
return this.singletons.get(cls) as T;
}
@@ -56,11 +75,25 @@ export abstract class CCEntity extends ecs.Entity {
* @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;
addPrefab<T extends ECSView>(ctor: ECSCtor<T>, parent: Node | GameComponent, path: string, bundleName: string = resLoader.defaultBundleName): Promise<Node> {
return new Promise(async (resolve, reject) => {
let node: Node = null!;
// 跟随父节点施放自动释放当前资源
if (parent instanceof GameComponent) {
node = await parent.createPrefabNode(path, bundleName);
const comp = node.getComponent(ctor)!;
this.add(comp);
node.parent = parent.node;
}
// 手动内存管理
else {
node = await ViewUtil.createPrefabNodeAsync(path, bundleName);
const comp = node.getComponent(ctor)!;
this.add(comp);
node.parent = parent;
}
resolve(node);
});
}
/**
@@ -87,7 +120,7 @@ export abstract class CCEntity extends ecs.Entity {
resolve(node);
}
else {
console.error(`${key} 界面组件未使用 gui.register 注册`);
console.error(`${ctor.name} 界面组件未使用 gui.register 注册`);
}
});
}
@@ -157,6 +190,7 @@ export abstract class CCEntity extends ecs.Entity {
* @returns 业务逻辑组件实例
*/
getBusiness<T extends CCBusiness<CCEntity>>(cls: any): T {
if (this.businesss == null) return null!;
return this.businesss.get(cls) as T;
}
@@ -165,8 +199,10 @@ export abstract class CCEntity extends ecs.Entity {
* @param cls 业务逻辑组件类
*/
removeBusiness(cls: any) {
let business = this.businesss.get(cls);
if (business) this.businesss.delete(cls);
if (this.businesss) {
let business = this.businesss.get(cls);
if (business) this.businesss.delete(cls);
}
}
//#endregion

View File

@@ -4,7 +4,7 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-12-13 11:36:00
*/
import { Asset, Button, Component, EventHandler, EventKeyboard, EventTouch, Input, Node, Prefab, Sprite, SpriteFrame, __private, _decorator, input, isValid } from "cc";
import { Asset, Button, Component, EventHandler, EventKeyboard, EventTouch, Input, Node, Prefab, Sprite, SpriteFrame, __private, _decorator, input, instantiate, isValid } from "cc";
import { oops } from "../../core/Oops";
import { AudioEffect } from "../../core/common/audio/AudioEffect";
import { IAudioParams } from "../../core/common/audio/IAudio";
@@ -99,19 +99,10 @@ export class GameComponent extends Component {
* 从资源缓存中找到预制资源名并创建一个显示对象
* @param path 资源路径
*/
createPrefabNode(path: string, bundleName: string = oops.res.defaultBundleName): Node {
return ViewUtil.createPrefabNode(path, bundleName);
}
/**
* 加载预制并创建预制节点
* @param path 资源路径
* @param bundleName 资源包名
*/
createPrefabNodeAsync(path: string, bundleName: string = oops.res.defaultBundleName): Promise<Node> {
createPrefabNode(path: string, bundleName: string = oops.res.defaultBundleName): Promise<Node> {
return new Promise(async (resolve, reject) => {
await this.loadAsync(bundleName, path, Prefab);
let node = ViewUtil.createPrefabNode(path, bundleName);
const prefab = await this.load(bundleName, path, Prefab);
const node = instantiate(prefab);
resolve(node);
});
}
@@ -197,42 +188,27 @@ export class GameComponent extends Component {
* @param paths 资源路径
* @param type 资源类型
* @param onProgress 加载进度回调
* @param onComplete 加载完成回调
*/
load<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(bundleName: string, paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(bundleName: string, paths: Paths, onComplete?: CompleteCallback): void;
load<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
load<T extends Asset>(paths: Paths, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
load<T extends Asset>(paths: Paths, onComplete?: CompleteCallback): void;
load<T extends Asset>(paths: Paths, type: AssetType<T>, onComplete?: CompleteCallback): void;
load<T extends Asset>(
bundleName: string,
paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
type?: AssetType<T> | ProgressCallback | CompleteCallback,
onProgress?: ProgressCallback | CompleteCallback,
onComplete?: CompleteCallback,
) {
load<T extends Asset>(bundleName: string, paths: Paths | AssetType<T>, type?: AssetType<T>) {
this.addPathToRecord(ResType.Load, bundleName, paths);
oops.res.load(bundleName, paths, type, onProgress, onComplete);
return oops.res.load(bundleName, paths, type);
}
/**
* 异步加载一个资源
* 加载指定资源包中的多个任意类型资源
* @param bundleName 远程包名
* @param paths 资源路径
* @param type 资源类型
* @param onProgress 加载进度回调
* @param onComplete 加载完成回调
*/
loadAsync<T extends Asset>(bundleName: string, paths: Paths, type: AssetType<T>): Promise<T>;
loadAsync<T extends Asset>(bundleName: string, paths: Paths): Promise<T>;
loadAsync<T extends Asset>(paths: Paths, type: AssetType<T>): Promise<T>;
loadAsync<T extends Asset>(paths: Paths): Promise<T>;
loadAsync<T extends Asset>(bundleName: string,
paths?: Paths | AssetType<T> | ProgressCallback | CompleteCallback,
type?: AssetType<T> | ProgressCallback | CompleteCallback): Promise<T> {
this.addPathToRecord(ResType.Load, bundleName, paths);
return oops.res.loadAsync(bundleName, paths, type);
loadAny(bundleName: string | string[], paths: string[] | ProgressCallback, onProgress?: ProgressCallback | CompleteCallback, onComplete?: CompleteCallback): void {
if (typeof bundleName === "string" && paths instanceof Array) {
this.addPathToRecord(ResType.Load, bundleName, paths);
}
else {
this.addPathToRecord(ResType.Load, resLoader.defaultBundleName, bundleName);
}
oops.res.loadAny(bundleName, paths, onProgress, onComplete);
}
/**
@@ -307,7 +283,7 @@ export class GameComponent extends Component {
* @param bundle 资源包名
*/
async setSprite(target: Sprite, path: string, bundle: string = resLoader.defaultBundleName) {
const spriteFrame = await this.loadAsync(bundle, path, SpriteFrame);
const spriteFrame = await this.load(bundle, path, SpriteFrame);
if (!spriteFrame || !isValid(target)) {
const rps = this.resPaths.get(ResType.Load);
if (rps) {
@@ -337,13 +313,23 @@ export class GameComponent extends Component {
* @param url 资源地址
* @param params 音效播放参数
*/
async playEffect(url: string, params?: IAudioParams): Promise<AudioEffect> {
playEffect(url: string, params?: IAudioParams): Promise<AudioEffect> {
return new Promise(async (resolve, reject) => {
// 音效播放完,关闭正在播放状态的音乐效果
if (params == null) params = {};
if (params == null) {
params = { bundle: resLoader.defaultBundleName };
}
else if (params.bundle == null) {
params.bundle = resLoader.defaultBundleName;
}
let ae = await oops.audio.playEffect(url, params);
this.addPathToRecord(ResType.Load, ae.params!.bundle!, url);
resolve(ae);
if (ae) {
this.addPathToRecord(ResType.Load, ae.params.bundle!, url);
resolve(ae);
}
else {
resolve(null!);
}
});
}
//#endregion

View File

@@ -0,0 +1,7 @@
/** 框架内本地存储键值 */
export enum GameStorage {
/** 设置音频音量、开关 */
Audio = "OopsFrameworkAudio",
/** 可设置指定时间内跳过提示 */
PromptSkip = "OopsFrameworkPromptSkip",
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7cefb56b-a388-4cd0-9952-209b7554c044",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import path from 'path';
/** 写入文件 */
export function createView(directoryPath: string, fileName: string, content: string, isEcsComp: boolean = true): Promise<void> {
export function createView(directoryPath: string, fileName: string, content: string, moduleName: string, isEcsComp: boolean = true): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
// 创建脚本
let className = fileName + "View";
@@ -16,6 +16,7 @@ export function createView(directoryPath: string, fileName: string, content: str
if (!existsSync(scriptUrl)) {
content = content.replace(/<%Name%>/g, className);
content = content.replace(/<%ModuleName%>/g, moduleName);
await Editor.Message.request('asset-db', 'create-asset', scriptUrl, content);
}

View File

@@ -46,9 +46,11 @@ module.exports = Editor.Panel.define({
break;
case "View":
title = `i18n:oops-framework.createView`;
showModule = true;
break;
case "ViewMvvm":
title = `i18n:oops-framework.createViewMvvm`;
showModule = true;
break;
}
@@ -82,7 +84,7 @@ module.exports = Editor.Panel.define({
switch (type) {
case "GameComponent":
await createView(path, filename, TemplateGameComponent, false);
await createView(path, filename, TemplateGameComponent, null!, false);
break;
case "Module":
await createScriptModule(path, filename, TemplateModule);
@@ -94,10 +96,10 @@ module.exports = Editor.Panel.define({
await createScriptBll(path, filename, TemplateBll, moduleName);
break;
case "View":
await createView(path, filename, TemplateView);
await createView(path, filename, TemplateView, moduleName);
break;
case "ViewMvvm":
await createView(path, filename, TemplateViewMvvm);
await createView(path, filename, TemplateViewMvvm, moduleName);
break;
}
close();

View File

@@ -1,8 +1,9 @@
export const TemplateModule = `import { ecs } from "db://oops-framework/libs/ecs/ECS";
import { CCEntity } from 'db://oops-framework/module/common/CCEntity';
/** <%Name%> 模块 */
@ecs.register('<%Name%>')
export class <%Name%> extends ecs.Entity {
export class <%Name%> extends CCEntity {
/** ---------- 数据层 ---------- */
// <%Name%>Model!: <%Name%>ModelComp;
@@ -14,6 +15,6 @@ export class <%Name%> extends ecs.Entity {
/** 初始添加的数据层组件 */
protected init() {
// this.addComponents<ecs.Comp>();
// this.addComponents();
}
}`;

View File

@@ -1,20 +1,16 @@
export const TemplateView = `import { _decorator } from "cc";
import { ecs } from "db://oops-framework/libs/ecs/ECS";
import { CCComp } from "db://oops-framework/module/common/CCComp";
import { CCView } from "db://oops-framework/module/common/CCView";
const { ccclass, property } = _decorator;
/** 视图层对象 */
@ccclass('<%Name%>Comp')
@ecs.register('<%Name%>', false)
export class <%Name%>Comp extends CCComp {
/** 视图层逻辑代码分离演示 */
start() {
// const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
}
export class <%Name%>Comp extends CCView<<%ModuleName%>> {
/** 视图对象通过 ecs.Entity.remove(<%Name%>Comp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
}
}`;

View File

@@ -1,23 +1,19 @@
export const TemplateViewMvvm = `import { _decorator } from "cc";
import { gui } from "db://oops-framework/core/gui/Gui";
import { LayerType } from "db://oops-framework/core/gui/layer/LayerEnum";
import { ecs } from "db://oops-framework/libs/ecs/ECS";
import { CCVMParentComp } from "db://oops-framework/module/common/CCVMParentComp";
import { CCViewVM } from "db://oops-framework/module/common/CCViewVM";
const { ccclass, property } = _decorator;
/** 视图层对象 - 支持 MVVM 框架的数据绑定 */
@ccclass('<%Name%>Comp')
@ecs.register('<%Name%>', false)
export class <%Name%>Comp extends CCVMParentComp {
/** 脚本控制的界面 MVVM 框架绑定数据 */
@gui.register('<%Name%>', { layer: LayerType.UI, prefab: "界面预制路径" })
export class <%Name%>Comp extends CCViewVM<<%ModuleName%>> {
data: any = {};
/** 视图层逻辑代码分离演示 */
start() {
// const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
}
/** 视图对象通过 ecs.Entity.remove(<%Name%>Comp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
}
}`